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
409macro_rules! impl_token_metadata {
410 ($($token:tt),* $(,)?) => {
411 $(
412 impl css_parse::NodeWithMetadata<CssMetadata> for css_parse::T![$token] {
413 fn metadata(&self) -> CssMetadata {
414 CssMetadata::default()
415 }
416 }
417 )*
418 };
419}
420
421impl_token_metadata!(Ident, Number, Dimension, Hash, AtKeyword, String, Function, Url);
422
423impl css_parse::NodeWithMetadata<CssMetadata> for css_parse::token_macros::RightParen {
424 fn metadata(&self) -> CssMetadata {
425 CssMetadata::default()
426 }
427}
428
429impl<'a, T: css_parse::NodeWithMetadata<CssMetadata>> css_parse::NodeWithMetadata<CssMetadata>
430 for bumpalo::collections::Vec<'a, T>
431{
432 fn metadata(&self) -> CssMetadata {
433 self.iter().fold(CssMetadata::default(), |acc, item| NodeMetadata::merge(acc, item.metadata()))
434 }
435}
436
437impl<'a, T: css_parse::NodeWithMetadata<CssMetadata>, const MIN: usize> css_parse::NodeWithMetadata<CssMetadata>
438 for css_parse::CommaSeparated<'a, T, MIN>
439{
440 fn metadata(&self) -> CssMetadata {
441 self.into_iter().fold(CssMetadata::default(), |acc, (item, _comma)| NodeMetadata::merge(acc, item.metadata()))
442 }
443}
444
445macro_rules! impl_optionals_metadata {
446 ($name:ident, $($T:ident => $v:ident),+) => {
447 impl<$($T: css_parse::NodeWithMetadata<CssMetadata>),+>
448 css_parse::NodeWithMetadata<CssMetadata> for css_parse::$name<$($T),+>
449 {
450 fn metadata(&self) -> CssMetadata {
451 let css_parse::$name($($v),+) = self;
452 let mut meta = CssMetadata::default();
453 $(
454 if let Some(val) = $v {
455 meta = NodeMetadata::merge(meta, val.metadata());
456 }
457 )+
458 meta
459 }
460 }
461 };
462}
463
464impl_optionals_metadata!(Optionals2, A => a, B => b);
465impl_optionals_metadata!(Optionals3, A => a, B => b, C => c);
466impl_optionals_metadata!(Optionals4, A => a, B => b, C => c, D => d);
467impl_optionals_metadata!(Optionals5, A => a, B => b, C => c, D => d, E => e);
468
469macro_rules! impl_tuple_metadata {
470 ($($T:ident),+) => {
471 impl<$($T: css_parse::NodeWithMetadata<CssMetadata>),+>
472 css_parse::NodeWithMetadata<CssMetadata> for ($($T,)+)
473 {
474 #[allow(non_snake_case)]
475 fn metadata(&self) -> CssMetadata {
476 let ($($T,)+) = self;
477 let mut meta = CssMetadata::default();
478 $(
479 meta = NodeMetadata::merge(meta, $T.metadata());
480 )+
481 meta
482 }
483 }
484 };
485}
486
487impl_tuple_metadata!(A, B);
488impl_tuple_metadata!(A, B, C);
489impl_tuple_metadata!(A, B, C, D);
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use crate::{CssAtomSet, StyleSheet};
495 use css_lexer::Lexer;
496 use css_parse::{NodeMetadata, NodeWithMetadata, Parser};
497
498 #[test]
499 fn test_block_metadata_merge() {
500 let mut meta1 = CssMetadata::default();
501 meta1.property_groups = PropertyGroup::Color;
502 meta1.declaration_kinds = DeclarationKind::Important;
503
504 let mut meta2 = CssMetadata::default();
505 meta2.property_groups = PropertyGroup::Position;
506 meta2.declaration_kinds = DeclarationKind::Custom;
507
508 let merged = meta1.merge(meta2);
509
510 assert!(merged.property_groups.contains(PropertyGroup::Color));
511 assert!(merged.property_groups.contains(PropertyGroup::Position));
512 assert!(merged.declaration_kinds.contains(DeclarationKind::Important));
513 assert!(merged.declaration_kinds.contains(DeclarationKind::Custom));
514 }
515
516 #[test]
517 fn test_stylesheet_metadata_simple() {
518 let css = "body { color: red; width: 100px; }";
519 let bump = bumpalo::Bump::new();
520 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
521 let mut parser = Parser::new(&bump, css, lexer);
522 let stylesheet = parser.parse::<StyleSheet>().unwrap();
523
524 let metadata = stylesheet.metadata();
525
526 assert!(metadata.property_groups.contains(PropertyGroup::Color));
527 assert!(metadata.property_groups.contains(PropertyGroup::Sizing));
528 assert!(metadata.modifies_box());
529 assert!(metadata.has_longhands());
530 }
531
532 #[test]
533 fn test_stylesheet_metadata_with_important() {
534 let css = "body { color: red !important; }";
535 let bump = bumpalo::Bump::new();
536 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
537 let mut parser = Parser::new(&bump, css, lexer);
538 let stylesheet = parser.parse::<StyleSheet>().unwrap();
539
540 let metadata = stylesheet.metadata();
541
542 assert!(metadata.has_important());
543 assert!(metadata.property_groups.contains(PropertyGroup::Color));
544 }
545
546 #[test]
547 fn test_stylesheet_metadata_custom_properties() {
548 let css = "body { --custom: value; }";
549 let bump = bumpalo::Bump::new();
550 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
551 let mut parser = Parser::new(&bump, css, lexer);
552 let stylesheet = parser.parse::<StyleSheet>().unwrap();
553
554 let metadata = stylesheet.metadata();
555
556 assert!(metadata.has_custom_properties());
557 }
558
559 #[test]
560 fn test_stylesheet_metadata_nested_media() {
561 let css = "@media screen { body { color: red; } }";
562 let bump = bumpalo::Bump::new();
563 let lexer = Lexer::new(&CssAtomSet::ATOMS, css);
564 let mut parser = Parser::new(&bump, css, lexer);
565 let stylesheet = parser.parse::<StyleSheet>().unwrap();
566
567 let metadata = stylesheet.metadata();
568
569 assert!(metadata.property_groups.contains(PropertyGroup::Color));
570 assert!(metadata.used_at_rules.contains(AtRuleId::Media));
571 }
572
573 #[test]
574 fn test_vendor_prefixes_try_from() {
575 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitTransform), Ok(VendorPrefixes::WebKit));
577 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitAnimation), Ok(VendorPrefixes::WebKit));
578 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_WebkitLineClamp), Ok(VendorPrefixes::WebKit));
579
580 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MozAppearance), Ok(VendorPrefixes::Moz));
581 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MozAny), Ok(VendorPrefixes::Moz));
582
583 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MsFullscreen), Ok(VendorPrefixes::Ms));
584 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_MsBackdrop), Ok(VendorPrefixes::Ms));
585
586 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_OPlaceholder), Ok(VendorPrefixes::O));
587 assert_eq!(VendorPrefixes::try_from(CssAtomSet::_OScrollbar), Ok(VendorPrefixes::O));
588
589 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Px), Err(()));
591 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Em), Err(()));
592 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Auto), Err(()));
593 assert_eq!(VendorPrefixes::try_from(CssAtomSet::Transform), Err(()));
594 }
595}