1#[cfg(feature = "visitable")]
2use crate::visit::NodeId;
3use crate::{
4 CssAtomSet,
5 traits::{AppliesTo, BoxPortion, BoxSide, PropertyGroup},
6};
7use bitmask_enum::bitmask;
8use css_lexer::{Span, ToSpan};
9use css_parse::{NodeMetadata, SemanticEq, ToCursors};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub enum UnitlessZeroResolves {
27 #[default]
29 Length,
30 Number,
32}
33
34#[bitmask(u32)]
35#[bitmask_config(vec_debug)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub enum AtRuleId {
38 Charset,
39 ColorProfile,
40 Container,
41 CounterStyle,
42 FontFace,
43 FontFeatureValues,
44 FontPaletteValues,
45 Import,
46 Keyframes,
47 Layer,
48 Media,
49 Namespace,
50 Page,
51 Property,
52 Scope,
53 StartingStyle,
54 Supports,
55 Document,
56 WebkitKeyframes,
57 MozDocument,
58}
59
60#[cfg(feature = "visitable")]
61impl NodeId {
62 pub fn to_at_rule_id(self) -> Option<AtRuleId> {
65 match self {
66 Self::CharsetRule => Some(AtRuleId::Charset),
67 Self::ContainerRule => Some(AtRuleId::Container),
68 Self::CounterStyleRule => Some(AtRuleId::CounterStyle),
69 Self::DocumentRule => Some(AtRuleId::Document),
70 Self::FontFaceRule => Some(AtRuleId::FontFace),
71 Self::KeyframesRule => Some(AtRuleId::Keyframes),
72 Self::LayerRule => Some(AtRuleId::Layer),
73 Self::MediaRule => Some(AtRuleId::Media),
74 Self::MozDocumentRule => Some(AtRuleId::MozDocument),
75 Self::NamespaceRule => Some(AtRuleId::Namespace),
76 Self::PageRule => Some(AtRuleId::Page),
77 Self::PropertyRule => Some(AtRuleId::Property),
78 Self::StartingStyleRule => Some(AtRuleId::StartingStyle),
79 Self::SupportsRule => Some(AtRuleId::Supports),
80 Self::WebkitKeyframesRule => Some(AtRuleId::WebkitKeyframes),
81 _ => None,
82 }
83 }
84}
85
86#[bitmask(u8)]
87#[bitmask_config(vec_debug)]
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
89pub enum VendorPrefixes {
90 Moz,
91 WebKit,
92 O,
93 Ms,
94}
95
96impl TryFrom<CssAtomSet> for VendorPrefixes {
97 type Error = ();
98 fn try_from(atom: CssAtomSet) -> Result<Self, Self::Error> {
99 const VENDOR_FLAG: u32 = 0b00000000_10000000_00000000_00000000;
100 const VENDORS: [VendorPrefixes; 4] =
101 [VendorPrefixes::WebKit, VendorPrefixes::Moz, VendorPrefixes::Ms, VendorPrefixes::O];
102
103 let atom_bits = atom as u32;
104 if atom_bits & VENDOR_FLAG == 0 {
105 return Err(());
106 }
107 let index = (atom_bits >> 21) & 0b11;
108 Ok(VENDORS[index as usize])
109 }
110}
111
112#[bitmask(u8)]
113#[bitmask_config(vec_debug)]
114#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
115pub enum DeclarationKind {
116 Important,
118 CssWideKeywords,
120 Custom,
122 Computed,
124 Shorthands,
126 Longhands,
128}
129
130#[bitmask(u16)]
132#[bitmask_config(vec_debug)]
133#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
134pub enum NodeKinds {
135 Unknown,
137 StyleRule,
139 AtRule,
141 Declaration,
143 Function,
145 EmptyPrelude,
147 EmptyBlock,
149 Nested,
151 Deprecated,
153 Experimental,
155 NonStandard,
157 Dimension,
159 Custom,
161}
162
163#[bitmask(u8)]
166#[bitmask_config(vec_debug)]
167#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
168pub enum PropertyKind {
169 Name,
171}
172
173pub const PROPERTY_KIND_VARIANTS: &[PropertyKind] = &[PropertyKind::Name];
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181pub struct CssMetadata {
182 pub property_groups: PropertyGroup,
184 pub applies_to: AppliesTo,
186 pub box_sides: BoxSide,
188 pub box_portions: BoxPortion,
190 pub declaration_kinds: DeclarationKind,
192 pub used_at_rules: AtRuleId,
194 pub vendor_prefixes: VendorPrefixes,
196 pub node_kinds: NodeKinds,
198 pub property_kinds: PropertyKind,
200 pub unitless_zero_resolves: UnitlessZeroResolves,
202 pub size: u16,
204}
205
206impl Default for CssMetadata {
207 fn default() -> Self {
208 Self {
209 property_groups: PropertyGroup::none(),
210 applies_to: AppliesTo::none(),
211 box_sides: BoxSide::none(),
212 box_portions: BoxPortion::none(),
213 declaration_kinds: DeclarationKind::none(),
214 used_at_rules: AtRuleId::none(),
215 vendor_prefixes: VendorPrefixes::none(),
216 node_kinds: NodeKinds::none(),
217 property_kinds: PropertyKind::none(),
218 unitless_zero_resolves: UnitlessZeroResolves::default(),
219 size: 0,
220 }
221 }
222}
223
224impl CssMetadata {
225 #[inline]
227 pub fn is_empty(&self) -> bool {
228 self.property_groups == PropertyGroup::none()
229 && self.applies_to == AppliesTo::none()
230 && self.box_sides == BoxSide::none()
231 && self.box_portions == BoxPortion::none()
232 && self.declaration_kinds == DeclarationKind::none()
233 && self.used_at_rules == AtRuleId::none()
234 && self.vendor_prefixes == VendorPrefixes::none()
235 && self.node_kinds == NodeKinds::none()
236 && self.property_kinds == PropertyKind::none()
237 && self.unitless_zero_resolves == UnitlessZeroResolves::Length
238 && self.size == 0
239 }
240
241 #[inline]
243 pub fn modifies_box(&self) -> bool {
244 !self.box_portions.is_none()
245 }
246
247 #[inline]
249 pub fn has_important(&self) -> bool {
250 self.declaration_kinds.contains(DeclarationKind::Important)
251 }
252
253 #[inline]
255 pub fn has_custom_properties(&self) -> bool {
256 self.declaration_kinds.contains(DeclarationKind::Custom)
257 }
258
259 #[inline]
261 pub fn has_computed(&self) -> bool {
262 self.declaration_kinds.contains(DeclarationKind::Computed)
263 }
264
265 #[inline]
267 pub fn has_shorthands(&self) -> bool {
268 self.declaration_kinds.contains(DeclarationKind::Shorthands)
269 }
270
271 #[inline]
273 pub fn has_longhands(&self) -> bool {
274 self.declaration_kinds.contains(DeclarationKind::Longhands)
275 }
276
277 #[inline]
279 pub fn has_unknown(&self) -> bool {
280 self.node_kinds.contains(NodeKinds::Unknown)
281 }
282
283 #[inline]
285 pub fn has_vendor_prefixes(&self) -> bool {
286 !self.vendor_prefixes.is_none()
287 }
288
289 #[inline]
291 pub fn single_vendor_prefix(&self) -> Option<VendorPrefixes> {
292 if self.vendor_prefixes.is_none() || self.vendor_prefixes.bits().count_ones() != 1 {
293 None
294 } else {
295 Some(self.vendor_prefixes)
296 }
297 }
298
299 #[inline]
301 pub fn has_rules(&self) -> bool {
302 self.node_kinds.intersects(NodeKinds::StyleRule | NodeKinds::AtRule)
303 }
304
305 #[inline]
307 pub fn has_style_rules(&self) -> bool {
308 self.node_kinds.contains(NodeKinds::StyleRule)
309 }
310
311 #[inline]
313 pub fn has_at_rules(&self) -> bool {
314 self.node_kinds.contains(NodeKinds::AtRule)
315 }
316
317 #[inline]
319 pub fn has_functions(&self) -> bool {
320 self.node_kinds.contains(NodeKinds::Function)
321 }
322
323 #[inline]
325 pub fn is_deprecated(&self) -> bool {
326 self.node_kinds.contains(NodeKinds::Deprecated)
327 }
328
329 #[inline]
331 pub fn is_experimental(&self) -> bool {
332 self.node_kinds.contains(NodeKinds::Experimental)
333 }
334
335 #[inline]
337 pub fn is_non_standard(&self) -> bool {
338 self.node_kinds.contains(NodeKinds::NonStandard)
339 }
340
341 #[inline]
343 pub fn is_dimension(&self) -> bool {
344 self.node_kinds.contains(NodeKinds::Dimension)
345 }
346
347 #[inline]
349 pub fn has_property_kind(&self, kind: PropertyKind) -> bool {
350 self.property_kinds.contains(kind)
351 }
352
353 #[inline]
355 pub fn is_empty_container(&self) -> bool {
356 self.node_kinds.contains(NodeKinds::EmptyBlock)
357 }
358
359 #[inline]
361 pub fn can_be_empty(&self) -> bool {
362 self.node_kinds.intersects(NodeKinds::StyleRule | NodeKinds::AtRule)
363 }
364}
365
366impl NodeMetadata for CssMetadata {
367 #[inline]
368 fn merge(mut self, other: Self) -> Self {
369 self.property_groups |= other.property_groups;
370 self.applies_to |= other.applies_to;
371 self.box_sides |= other.box_sides;
372 self.box_portions |= other.box_portions;
373 self.declaration_kinds |= other.declaration_kinds;
374 self.used_at_rules |= other.used_at_rules;
375 self.vendor_prefixes |= other.vendor_prefixes;
376 self.node_kinds |= other.node_kinds;
377 self.property_kinds |= other.property_kinds;
378 if other.unitless_zero_resolves == UnitlessZeroResolves::Number {
380 self.unitless_zero_resolves = UnitlessZeroResolves::Number;
381 }
382 self.size = self.size.max(other.size);
383 self
384 }
385
386 #[inline]
387 fn with_size(mut self, size: u16) -> Self {
388 self.size = size;
389 self
390 }
391}
392
393impl ToCursors for CssMetadata {
395 fn to_cursors(&self, _: &mut impl css_parse::CursorSink) {}
396}
397impl ToSpan for CssMetadata {
398 fn to_span(&self) -> Span {
399 Span::DUMMY
400 }
401}
402
403impl SemanticEq for CssMetadata {
404 fn semantic_eq(&self, other: &Self) -> bool {
405 self == other
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412 use crate::{CssAtomSet, StyleSheet};
413 use css_lexer::Lexer;
414 use css_parse::{NodeMetadata, NodeWithMetadata, Parser};
415
416 #[test]
417 fn test_block_metadata_merge() {
418 let mut meta1 = CssMetadata::default();
419 meta1.property_groups = PropertyGroup::Color;
420 meta1.declaration_kinds = DeclarationKind::Important;
421
422 let mut meta2 = CssMetadata::default();
423 meta2.property_groups = PropertyGroup::Position;
424 meta2.declaration_kinds = DeclarationKind::Custom;
425
426 let merged = meta1.merge(meta2);
427
428 assert!(merged.property_groups.contains(PropertyGroup::Color));
429 assert!(merged.property_groups.contains(PropertyGroup::Position));
430 assert!(merged.declaration_kinds.contains(DeclarationKind::Important));
431 assert!(merged.declaration_kinds.contains(DeclarationKind::Custom));
432 }
433
434 #[test]
435 fn test_stylesheet_metadata_simple() {
436 let css = "body { color: red; width: 100px; }";
437 let bump = bumpalo::Bump::new();
438 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
439 let mut parser = Parser::new(&bump, css, lexer);
440 let stylesheet = parser.parse::<StyleSheet>().unwrap();
441
442 let metadata = stylesheet.metadata();
443
444 assert!(metadata.property_groups.contains(PropertyGroup::Color));
445 assert!(metadata.property_groups.contains(PropertyGroup::Sizing));
446 assert!(metadata.modifies_box());
447 assert!(metadata.has_longhands());
448 }
449
450 #[test]
451 fn test_stylesheet_metadata_with_important() {
452 let css = "body { color: red !important; }";
453 let bump = bumpalo::Bump::new();
454 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
455 let mut parser = Parser::new(&bump, css, lexer);
456 let stylesheet = parser.parse::<StyleSheet>().unwrap();
457
458 let metadata = stylesheet.metadata();
459
460 assert!(metadata.has_important());
461 assert!(metadata.property_groups.contains(PropertyGroup::Color));
462 }
463
464 #[test]
465 fn test_stylesheet_metadata_custom_properties() {
466 let css = "body { --custom: value; }";
467 let bump = bumpalo::Bump::new();
468 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
469 let mut parser = Parser::new(&bump, css, lexer);
470 let stylesheet = parser.parse::<StyleSheet>().unwrap();
471
472 let metadata = stylesheet.metadata();
473
474 assert!(metadata.has_custom_properties());
475 }
476
477 #[test]
478 fn test_stylesheet_metadata_nested_media() {
479 let css = "@media screen { body { color: red; } }";
480 let bump = bumpalo::Bump::new();
481 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
482 let mut parser = Parser::new(&bump, css, lexer);
483 let stylesheet = parser.parse::<StyleSheet>().unwrap();
484
485 let metadata = stylesheet.metadata();
486
487 assert!(metadata.property_groups.contains(PropertyGroup::Color));
488 assert!(metadata.used_at_rules.contains(AtRuleId::Media));
489 }
490
491 #[test]
492 fn test_vendor_prefixes_try_from() {
493 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitTransform), Ok(VendorPrefixes::WebKit));
495 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitAnimation), Ok(VendorPrefixes::WebKit));
496 assert_eq!(VendorPrefixes::try_from(CssAtomSet::WebkitLineClamp), Ok(VendorPrefixes::WebKit));
497
498 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MozAppearance), Ok(VendorPrefixes::Moz));
499 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MozAny), Ok(VendorPrefixes::Moz));
500
501 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MsFullscreen), Ok(VendorPrefixes::Ms));
502 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MsBackdrop), Ok(VendorPrefixes::Ms));
503
504 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_OPlaceholder), Ok(VendorPrefixes::O));
505 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_OScrollbar), Ok(VendorPrefixes::O));
506
507 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Px), Err(()));
509 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Em), Err(()));
510 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Auto), Err(()));
511 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Transform), Err(()));
512 }
513}