1use alloc::{string::String, vec::Vec};
12use core::fmt;
13
14use crate::{
15 corety::OptionString,
16 dynamic_selector::DynamicSelectorVec,
17 props::property::{CssProperty, CssPropertyType},
18 AzString,
19};
20
21#[derive(Debug, Default, PartialEq, Clone)]
29#[repr(C)]
30pub struct Css {
31 pub rules: CssRuleBlockVec,
34}
35
36impl_option!(
37 Css,
38 OptionCss,
39 copy = false,
40 [Debug, Clone, PartialEq, Eq, PartialOrd]
41);
42
43impl_vec!(Css, CssVec, CssVecDestructor, CssVecDestructorType, CssVecSlice, OptionCss);
44impl_vec_mut!(Css, CssVec);
45impl_vec_debug!(Css, CssVec);
46impl_vec_partialord!(Css, CssVec);
47impl_vec_clone!(Css, CssVec, CssVecDestructor);
48impl_vec_partialeq!(Css, CssVec);
49
50impl_vec!(CssRuleBlock, CssRuleBlockVec, CssRuleBlockVecDestructor, CssRuleBlockVecDestructorType, CssRuleBlockVecSlice, OptionCssRuleBlock);
51impl_vec_mut!(CssRuleBlock, CssRuleBlockVec);
52impl_vec_debug!(CssRuleBlock, CssRuleBlockVec);
53impl_vec_partialord!(CssRuleBlock, CssRuleBlockVec);
54impl_vec_clone!(CssRuleBlock, CssRuleBlockVec, CssRuleBlockVecDestructor);
55impl_vec_partialeq!(CssRuleBlock, CssRuleBlockVec);
56
57impl Css {
58 #[must_use] pub fn is_empty(&self) -> bool {
59 self.rules.as_ref().is_empty()
60 }
61
62 #[must_use] pub fn new(rules: Vec<CssRuleBlock>) -> Self {
63 Self {
64 rules: rules.into(),
65 }
66 }
67
68 #[cfg(feature = "parser")]
69 #[allow(clippy::needless_pass_by_value)]
72 #[must_use] pub fn from_string(s: AzString) -> Self {
73 crate::parser2::new_from_str(s.as_str()).0
74 }
75
76 #[cfg(feature = "parser")]
84 #[must_use] pub fn parse_inline(style: &str) -> Self {
85 use alloc::string::ToString;
86 let mut wrapped = String::with_capacity(style.len() + 6);
87 wrapped.push_str("* {\n");
88 wrapped.push_str(style);
89 wrapped.push_str("\n}");
90 let (mut css, _warnings) = crate::parser2::new_from_str(&wrapped);
91 css.rules.retain(|rule| {
97 matches!(
98 rule.path.selectors.as_ref().first(),
99 None | Some(CssPathSelector::Global)
100 )
101 });
102 for rule in css.rules.as_mut() {
103 rule.priority = rule_priority::INLINE;
104 }
105 css
106 }
107
108 #[cfg(feature = "parser")]
109 #[allow(clippy::needless_pass_by_value)]
112 #[must_use] pub fn from_string_with_warnings(
113 s: AzString,
114 ) -> (Self, Vec<crate::parser2::CssParseWarnMsgOwned>) {
115 let (css, warnings) = crate::parser2::new_from_str(s.as_str());
116 (
117 css,
118 warnings
119 .into_iter()
120 .map(|w| crate::parser2::CssParseWarnMsgOwned {
121 warning: w.warning.to_contained(),
122 location: w.location,
123 })
124 .collect(),
125 )
126 }
127}
128
129impl From<Vec<CssRuleBlock>> for Css {
130 fn from(rules: Vec<CssRuleBlock>) -> Self {
131 Self {
132 rules: rules.into(),
133 }
134 }
135}
136
137impl Eq for Css {}
141impl PartialOrd for Css {
144 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
145 Some(self.cmp(other))
146 }
147}
148impl Ord for Css {
149 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
150 self.rules.as_ref().len().cmp(&other.rules.as_ref().len())
151 }
152}
153impl Eq for CssRuleBlock {}
154impl Ord for CssRuleBlock {
155 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
156 self.path.cmp(&other.path).then_with(|| self.declarations.cmp(&other.declarations))
160 }
161}
162
163impl From<crate::dynamic_selector::CssPropertyWithConditionsVec> for Css {
172 fn from(props: crate::dynamic_selector::CssPropertyWithConditionsVec) -> Self {
173 let owned = props.into_library_owned_vec();
181 let mut rules: Vec<CssRuleBlock> = Vec::with_capacity(owned.len());
182 for p in owned {
183 rules.push(CssRuleBlock {
184 path: CssPath { selectors: Vec::new().into() },
185 declarations: alloc::vec![CssDeclaration::Static(p.property)].into(),
186 conditions: p.apply_if,
187 priority: rule_priority::INLINE,
188 });
189 }
190 Self { rules: rules.into() }
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
196#[repr(C, u8)]
197pub enum CssDeclaration {
198 Static(CssProperty),
200 Dynamic(DynamicCssProperty),
202}
203
204impl_option!(
205 CssDeclaration,
206 OptionCssDeclaration,
207 copy = false,
208 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
209);
210
211impl CssDeclaration {
212 #[must_use] pub const fn new_static(prop: CssProperty) -> Self {
213 Self::Static(prop)
214 }
215
216 #[must_use] pub const fn new_dynamic(prop: DynamicCssProperty) -> Self {
217 Self::Dynamic(prop)
218 }
219
220 #[must_use] pub const fn get_type(&self) -> CssPropertyType {
222 use self::CssDeclaration::{Static, Dynamic};
223 match self {
224 Static(s) => s.get_type(),
225 Dynamic(d) => d.default_value.get_type(),
226 }
227 }
228
229 #[must_use] pub const fn is_inheritable(&self) -> bool {
232 use self::CssDeclaration::{Static, Dynamic};
233 match self {
234 Static(s) => s.get_type().is_inheritable(),
235 Dynamic(d) => d.is_inheritable(),
236 }
237 }
238
239 #[must_use] pub const fn can_trigger_relayout(&self) -> bool {
242 use self::CssDeclaration::{Static, Dynamic};
243 match self {
244 Static(s) => s.get_type().can_trigger_relayout(),
245 Dynamic(d) => d.can_trigger_relayout(),
246 }
247 }
248
249 #[must_use] pub fn to_str(&self) -> String {
250 use self::CssDeclaration::{Static, Dynamic};
251 match self {
252 Static(s) => format!("{s:?}"),
253 Dynamic(d) => format!("var(--{}, {:?})", d.dynamic_id, d.default_value),
254 }
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
279#[repr(C)]
280pub struct DynamicCssProperty {
281 pub dynamic_id: AzString,
283 pub default_value: CssProperty,
285}
286
287#[repr(C, u8)]
297pub enum BoxOrStatic<T> {
298 Boxed(*mut T),
300 Static(*const T),
302}
303
304impl<T> BoxOrStatic<T> {
305 #[inline]
307 pub fn heap(value: T) -> Self {
308 Self::Boxed(Box::into_raw(Box::new(value)))
309 }
310
311 #[inline]
317 #[must_use] pub fn as_ref(&self) -> &T {
318 match self {
319 Self::Boxed(ptr) => unsafe {
320 debug_assert!(!ptr.is_null(), "BoxOrStatic::Boxed contained a null pointer");
321 &**ptr
322 },
323 Self::Static(ptr) => unsafe {
324 debug_assert!(!ptr.is_null(), "BoxOrStatic::Static contained a null pointer");
325 &**ptr
326 },
327 }
328 }
329
330 #[inline]
337 pub fn as_mut(&mut self) -> &mut T {
338 match self {
339 Self::Boxed(ptr) => unsafe { &mut **ptr },
340 Self::Static(_) => panic!("Cannot mutate a static BoxOrStatic value"),
341 }
342 }
343
344 #[inline]
346 #[must_use] pub fn into_inner(self) -> T where T: Clone {
347 self.as_ref().clone()
352 }
353}
354
355impl<T> Drop for BoxOrStatic<T> {
356 fn drop(&mut self) {
357 if let Self::Boxed(ptr) = self {
358 if !ptr.is_null() {
359 unsafe { drop(Box::from_raw(*ptr)); }
360 *ptr = core::ptr::null_mut();
361 }
362 }
363 }
364}
365
366impl<T: Clone> Clone for BoxOrStatic<T> {
367 fn clone(&self) -> Self {
368 match self {
369 Self::Boxed(ptr) => {
370 let val = unsafe { &**ptr }.clone();
371 Self::Boxed(Box::into_raw(Box::new(val)))
372 }
373 Self::Static(ptr) => Self::Static(*ptr),
374 }
375 }
376}
377
378impl<T: fmt::Debug> fmt::Debug for BoxOrStatic<T> {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 self.as_ref().fmt(f)
381 }
382}
383
384impl<T: fmt::Display> fmt::Display for BoxOrStatic<T> {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 self.as_ref().fmt(f)
387 }
388}
389
390impl<T: PartialEq> PartialEq for BoxOrStatic<T> {
391 fn eq(&self, other: &Self) -> bool {
392 self.as_ref() == other.as_ref()
393 }
394}
395
396impl<T: Eq> Eq for BoxOrStatic<T> {}
397
398impl<T: core::hash::Hash> core::hash::Hash for BoxOrStatic<T> {
399 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
400 self.as_ref().hash(state);
401 }
402}
403
404impl<T: PartialOrd> PartialOrd for BoxOrStatic<T> {
405 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
406 self.as_ref().partial_cmp(other.as_ref())
407 }
408}
409
410impl<T: Ord> Ord for BoxOrStatic<T> {
411 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
412 self.as_ref().cmp(other.as_ref())
413 }
414}
415
416impl<T> core::ops::Deref for BoxOrStatic<T> {
417 type Target = T;
418 #[inline]
419 fn deref(&self) -> &T {
420 self.as_ref()
421 }
422}
423
424impl<T: Default> Default for BoxOrStatic<T> {
425 fn default() -> Self {
426 Self::heap(T::default())
427 }
428}
429
430impl<T: PrintAsCssValue> PrintAsCssValue for BoxOrStatic<T> {
431 fn print_as_css_value(&self) -> String {
432 self.as_ref().print_as_css_value()
433 }
434}
435
436unsafe impl<T: Send + 'static> Send for BoxOrStatic<T> {}
438unsafe impl<T: Sync + 'static> Sync for BoxOrStatic<T> {}
440
441pub type BoxOrStaticStyleBoxShadow = BoxOrStatic<crate::props::style::box_shadow::StyleBoxShadow>;
443
444pub type BoxOrStaticString = BoxOrStatic<AzString>;
446
447#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
450#[repr(C, u8)] pub enum CssPropertyValue<T> {
452 Auto,
453 None,
454 Initial,
455 Inherit,
456 Revert,
457 Unset,
458 Exact(T),
459}
460
461pub trait PrintAsCssValue {
463 fn print_as_css_value(&self) -> String;
464}
465
466impl<T: PrintAsCssValue> CssPropertyValue<T> {
467 pub fn get_css_value_fmt(&self) -> String {
468 match self {
469 Self::Auto => "auto".to_string(),
470 Self::None => "none".to_string(),
471 Self::Initial => "initial".to_string(),
472 Self::Inherit => "inherit".to_string(),
473 Self::Revert => "revert".to_string(),
474 Self::Unset => "unset".to_string(),
475 Self::Exact(e) => e.print_as_css_value(),
476 }
477 }
478}
479
480impl<T: fmt::Display> fmt::Display for CssPropertyValue<T> {
481 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482 use self::CssPropertyValue::{Auto, None, Initial, Inherit, Revert, Unset, Exact};
483 match self {
484 Auto => write!(f, "auto"),
485 None => write!(f, "none"),
486 Initial => write!(f, "initial"),
487 Inherit => write!(f, "inherit"),
488 Revert => write!(f, "revert"),
489 Unset => write!(f, "unset"),
490 Exact(e) => write!(f, "{e}"),
491 }
492 }
493}
494
495impl<T> From<T> for CssPropertyValue<T> {
496 fn from(c: T) -> Self {
497 Self::Exact(c)
498 }
499}
500
501impl<T> CssPropertyValue<T> {
502 #[inline]
505 pub fn map_property<F: Fn(T) -> U, U>(self, map_fn: F) -> CssPropertyValue<U> {
506 match self {
507 Self::Exact(c) => CssPropertyValue::Exact(map_fn(c)),
508 Self::Auto => CssPropertyValue::Auto,
509 Self::None => CssPropertyValue::None,
510 Self::Initial => CssPropertyValue::Initial,
511 Self::Inherit => CssPropertyValue::Inherit,
512 Self::Revert => CssPropertyValue::Revert,
513 Self::Unset => CssPropertyValue::Unset,
514 }
515 }
516
517 #[inline]
518 pub const fn get_property(&self) -> Option<&T> {
519 match self {
520 Self::Exact(c) => Some(c),
521 _ => None,
522 }
523 }
524
525 #[inline]
526 pub fn get_property_owned(self) -> Option<T> {
527 match self {
528 Self::Exact(c) => Some(c),
529 _ => None,
530 }
531 }
532
533 #[inline]
534 pub const fn is_auto(&self) -> bool {
535 matches!(self, Self::Auto)
536 }
537
538 #[inline]
539 pub const fn is_none(&self) -> bool {
540 matches!(self, Self::None)
541 }
542
543 #[inline]
544 pub const fn is_initial(&self) -> bool {
545 matches!(self, Self::Initial)
546 }
547
548 #[inline]
549 pub const fn is_inherit(&self) -> bool {
550 matches!(self, Self::Inherit)
551 }
552
553 #[inline]
554 pub const fn is_revert(&self) -> bool {
555 matches!(self, Self::Revert)
556 }
557
558 #[inline]
559 pub const fn is_unset(&self) -> bool {
560 matches!(self, Self::Unset)
561 }
562}
563
564impl<T: Default> CssPropertyValue<T> {
565 #[inline]
566 pub fn get_property_or_default(self) -> Option<T> {
567 match self {
568 Self::Auto | Self::Initial => Some(T::default()),
569 Self::Exact(c) => Some(c),
570 Self::None
571 | Self::Inherit
572 | Self::Revert
573 | Self::Unset => None,
574 }
575 }
576}
577
578impl<T: Default> Default for CssPropertyValue<T> {
579 #[inline]
580 fn default() -> Self {
581 Self::Exact(T::default())
582 }
583}
584
585impl DynamicCssProperty {
586 #[must_use] pub const fn is_inheritable(&self) -> bool {
587 false
591 }
592
593 #[must_use] pub const fn can_trigger_relayout(&self) -> bool {
594 self.default_value.get_type().can_trigger_relayout()
595 }
596}
597
598pub mod rule_priority {
606 pub const UA: u8 = 0;
610
611 pub const SYSTEM: u8 = 10;
616
617 pub const AUTHOR: u8 = 20;
620
621 pub const INLINE: u8 = 30;
625
626 pub const RUNTIME: u8 = 50;
635}
636
637#[derive(Debug, Default, Clone, PartialEq)]
643#[repr(C)]
644pub struct CssRuleBlock {
645 pub path: CssPath,
647 pub declarations: CssDeclarationVec,
650 pub conditions: DynamicSelectorVec,
653 pub priority: u8,
657}
658
659impl_option!(
660 CssRuleBlock,
661 OptionCssRuleBlock,
662 copy = false,
663 [Debug, Clone, PartialEq, Eq, PartialOrd]
664);
665
666impl PartialOrd for CssRuleBlock {
667 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
668 match self.path.partial_cmp(&other.path) {
670 Some(core::cmp::Ordering::Equal) => self.declarations.partial_cmp(&other.declarations),
671 ord => ord,
672 }
673 }
674}
675
676impl_vec!(CssDeclaration, CssDeclarationVec, CssDeclarationVecDestructor, CssDeclarationVecDestructorType, CssDeclarationVecSlice, OptionCssDeclaration);
677impl_vec_mut!(CssDeclaration, CssDeclarationVec);
678impl_vec_debug!(CssDeclaration, CssDeclarationVec);
679impl_vec_partialord!(CssDeclaration, CssDeclarationVec);
680impl_vec_ord!(CssDeclaration, CssDeclarationVec);
681impl_vec_clone!(
682 CssDeclaration,
683 CssDeclarationVec,
684 CssDeclarationVecDestructor
685);
686impl_vec_partialeq!(CssDeclaration, CssDeclarationVec);
687impl_vec_eq!(CssDeclaration, CssDeclarationVec);
688impl_vec_hash!(CssDeclaration, CssDeclarationVec);
689
690impl CssRuleBlock {
691 #[must_use] pub fn new(path: CssPath, declarations: Vec<CssDeclaration>) -> Self {
692 Self {
693 path,
694 declarations: declarations.into(),
695 conditions: DynamicSelectorVec::from_const_slice(&[]),
696 priority: rule_priority::AUTHOR,
697 }
698 }
699
700 #[must_use] pub fn with_conditions(
701 path: CssPath,
702 declarations: Vec<CssDeclaration>,
703 conditions: Vec<crate::dynamic_selector::DynamicSelector>,
704 ) -> Self {
705 Self {
706 path,
707 declarations: declarations.into(),
708 conditions: conditions.into(),
709 priority: rule_priority::AUTHOR,
710 }
711 }
712}
713
714pub type CssContentGroup<'a> = Vec<&'a CssPathSelector>;
716
717#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
719#[repr(C)]
720pub enum NodeTypeTag {
721 Html,
723 Head,
724 Body,
725
726 Div,
728 P,
729 Article,
730 Section,
731 Nav,
732 Aside,
733 Header,
734 Footer,
735 Main,
736 Figure,
737 FigCaption,
738
739 H1,
741 H2,
742 H3,
743 H4,
744 H5,
745 H6,
746
747 Br,
749 Hr,
750 Pre,
751 BlockQuote,
752 Address,
753 Details,
754 Summary,
755 Dialog,
756
757 Ul,
759 Ol,
760 Li,
761 Dl,
762 Dt,
763 Dd,
764 Menu,
765 MenuItem,
766 Dir,
767
768 Table,
770 Caption,
771 THead,
772 TBody,
773 TFoot,
774 Tr,
775 Th,
776 Td,
777 ColGroup,
778 Col,
779
780 Form,
782 FieldSet,
783 Legend,
784 Label,
785 Input,
786 Button,
787 Select,
788 OptGroup,
789 SelectOption,
790 TextArea,
791 Output,
792 Progress,
793 Meter,
794 DataList,
795
796 Span,
798 A,
799 Em,
800 Strong,
801 B,
802 I,
803 U,
804 S,
805 Mark,
806 Del,
807 Ins,
808 Code,
809 Samp,
810 Kbd,
811 Var,
812 Cite,
813 Dfn,
814 Abbr,
815 Acronym,
816 Q,
817 Time,
818 Sub,
819 Sup,
820 Small,
821 Big,
822 Bdo,
823 Bdi,
824 Wbr,
825 Ruby,
826 Rt,
827 Rtc,
828 Rp,
829 Data,
830
831 Canvas,
833 Object,
834 Param,
835 Embed,
836 Audio,
837 Video,
838 Source,
839 Track,
840 Map,
841 Area,
842 Svg,
843 SvgPath,
845 SvgCircle,
847 SvgRect,
849 SvgEllipse,
851 SvgLine,
853 SvgPolygon,
855 SvgPolyline,
857 SvgG,
859
860 SvgDefs,
863 SvgSymbol,
865 SvgUse,
867 SvgSwitch,
869
870 SvgText,
873 SvgTspan,
875 SvgTextPath,
877
878 SvgLinearGradient,
881 SvgRadialGradient,
883 SvgStop,
885 SvgPattern,
887
888 SvgClipPathElement,
891 SvgMask,
893
894 SvgFilter,
897 SvgFeBlend,
899 SvgFeColorMatrix,
901 SvgFeComponentTransfer,
903 SvgFeComposite,
905 SvgFeConvolveMatrix,
907 SvgFeDiffuseLighting,
909 SvgFeDisplacementMap,
911 SvgFeDistantLight,
913 SvgFeDropShadow,
915 SvgFeFlood,
917 SvgFeFuncR,
919 SvgFeFuncG,
921 SvgFeFuncB,
923 SvgFeFuncA,
925 SvgFeGaussianBlur,
927 SvgFeImage,
929 SvgFeMerge,
931 SvgFeMergeNode,
933 SvgFeMorphology,
935 SvgFeOffset,
937 SvgFePointLight,
939 SvgFeSpecularLighting,
941 SvgFeSpotLight,
943 SvgFeTile,
945 SvgFeTurbulence,
947
948 SvgMarker,
951 SvgImage,
953 SvgForeignObject,
955
956 SvgTitle,
959 SvgDesc,
961 SvgMetadata,
963 SvgA,
965 SvgView,
967 SvgStyle,
969 SvgScript,
971
972 SvgAnimate,
975 SvgAnimateMotion,
977 SvgAnimateTransform,
979 SvgSet,
981 SvgMpath,
983
984 Title,
986 Meta,
987 Link,
988 Script,
989 Style,
990 Base,
991
992 Text,
994 Img,
995 VirtualView,
996 Icon,
998 GeolocationProbe,
1001
1002 Before,
1004 After,
1005 Marker,
1006 Placeholder,
1007}
1008
1009#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1011pub enum NodeTypeTagParseError<'a> {
1012 Invalid(&'a str),
1013}
1014
1015impl fmt::Display for NodeTypeTagParseError<'_> {
1016 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017 match &self {
1018 NodeTypeTagParseError::Invalid(e) => write!(f, "Invalid node type: {e}"),
1019 }
1020 }
1021}
1022
1023#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1025#[repr(C, u8)]
1026pub enum NodeTypeTagParseErrorOwned {
1027 Invalid(AzString),
1028}
1029
1030impl NodeTypeTagParseError<'_> {
1031 #[must_use] pub fn to_contained(&self) -> NodeTypeTagParseErrorOwned {
1032 match self {
1033 NodeTypeTagParseError::Invalid(s) => NodeTypeTagParseErrorOwned::Invalid((*s).to_string().into()),
1034 }
1035 }
1036}
1037
1038impl NodeTypeTagParseErrorOwned {
1039 #[must_use] pub fn to_shared(&self) -> NodeTypeTagParseError<'_> {
1040 match self {
1041 Self::Invalid(s) => NodeTypeTagParseError::Invalid(s),
1042 }
1043 }
1044}
1045
1046impl NodeTypeTag {
1048 #[allow(clippy::too_many_lines)] pub fn from_str(css_key: &str) -> Result<Self, NodeTypeTagParseError<'_>> {
1053 match css_key {
1054 "html" => Ok(Self::Html),
1056 "head" => Ok(Self::Head),
1057 "body" => Ok(Self::Body),
1058
1059 "div" => Ok(Self::Div),
1061 "p" => Ok(Self::P),
1062 "article" => Ok(Self::Article),
1063 "section" => Ok(Self::Section),
1064 "nav" => Ok(Self::Nav),
1065 "aside" => Ok(Self::Aside),
1066 "header" => Ok(Self::Header),
1067 "footer" => Ok(Self::Footer),
1068 "main" => Ok(Self::Main),
1069 "figure" => Ok(Self::Figure),
1070 "figcaption" => Ok(Self::FigCaption),
1071
1072 "h1" => Ok(Self::H1),
1074 "h2" => Ok(Self::H2),
1075 "h3" => Ok(Self::H3),
1076 "h4" => Ok(Self::H4),
1077 "h5" => Ok(Self::H5),
1078 "h6" => Ok(Self::H6),
1079
1080 "br" => Ok(Self::Br),
1082 "hr" => Ok(Self::Hr),
1083 "pre" => Ok(Self::Pre),
1084 "blockquote" => Ok(Self::BlockQuote),
1085 "address" => Ok(Self::Address),
1086 "details" => Ok(Self::Details),
1087 "summary" => Ok(Self::Summary),
1088 "dialog" => Ok(Self::Dialog),
1089
1090 "ul" => Ok(Self::Ul),
1092 "ol" => Ok(Self::Ol),
1093 "li" => Ok(Self::Li),
1094 "dl" => Ok(Self::Dl),
1095 "dt" => Ok(Self::Dt),
1096 "dd" => Ok(Self::Dd),
1097 "menu" => Ok(Self::Menu),
1098 "menuitem" => Ok(Self::MenuItem),
1099 "dir" => Ok(Self::Dir),
1100
1101 "table" => Ok(Self::Table),
1103 "caption" => Ok(Self::Caption),
1104 "thead" => Ok(Self::THead),
1105 "tbody" => Ok(Self::TBody),
1106 "tfoot" => Ok(Self::TFoot),
1107 "tr" => Ok(Self::Tr),
1108 "th" => Ok(Self::Th),
1109 "td" => Ok(Self::Td),
1110 "colgroup" => Ok(Self::ColGroup),
1111 "col" => Ok(Self::Col),
1112
1113 "form" => Ok(Self::Form),
1115 "fieldset" => Ok(Self::FieldSet),
1116 "legend" => Ok(Self::Legend),
1117 "label" => Ok(Self::Label),
1118 "input" => Ok(Self::Input),
1119 "button" => Ok(Self::Button),
1120 "select" => Ok(Self::Select),
1121 "optgroup" => Ok(Self::OptGroup),
1122 "option" => Ok(Self::SelectOption),
1123 "textarea" => Ok(Self::TextArea),
1124 "output" => Ok(Self::Output),
1125 "progress" => Ok(Self::Progress),
1126 "meter" => Ok(Self::Meter),
1127 "datalist" => Ok(Self::DataList),
1128
1129 "span" => Ok(Self::Span),
1131 "a" => Ok(Self::A),
1132 "em" => Ok(Self::Em),
1133 "strong" => Ok(Self::Strong),
1134 "b" => Ok(Self::B),
1135 "i" => Ok(Self::I),
1136 "u" => Ok(Self::U),
1137 "s" => Ok(Self::S),
1138 "mark" => Ok(Self::Mark),
1139 "del" => Ok(Self::Del),
1140 "ins" => Ok(Self::Ins),
1141 "code" => Ok(Self::Code),
1142 "samp" => Ok(Self::Samp),
1143 "kbd" => Ok(Self::Kbd),
1144 "var" => Ok(Self::Var),
1145 "cite" => Ok(Self::Cite),
1146 "dfn" => Ok(Self::Dfn),
1147 "abbr" => Ok(Self::Abbr),
1148 "acronym" => Ok(Self::Acronym),
1149 "q" => Ok(Self::Q),
1150 "time" => Ok(Self::Time),
1151 "sub" => Ok(Self::Sub),
1152 "sup" => Ok(Self::Sup),
1153 "small" => Ok(Self::Small),
1154 "big" => Ok(Self::Big),
1155 "bdo" => Ok(Self::Bdo),
1156 "bdi" => Ok(Self::Bdi),
1157 "wbr" => Ok(Self::Wbr),
1158 "ruby" => Ok(Self::Ruby),
1159 "rt" => Ok(Self::Rt),
1160 "rtc" => Ok(Self::Rtc),
1161 "rp" => Ok(Self::Rp),
1162 "data" => Ok(Self::Data),
1163
1164 "canvas" => Ok(Self::Canvas),
1166 "object" => Ok(Self::Object),
1167 "param" => Ok(Self::Param),
1168 "embed" => Ok(Self::Embed),
1169 "audio" => Ok(Self::Audio),
1170 "video" => Ok(Self::Video),
1171 "source" => Ok(Self::Source),
1172 "track" => Ok(Self::Track),
1173 "map" => Ok(Self::Map),
1174 "area" => Ok(Self::Area),
1175 "svg" => Ok(Self::Svg),
1176
1177 "path" => Ok(Self::SvgPath),
1179 "circle" => Ok(Self::SvgCircle),
1180 "rect" => Ok(Self::SvgRect),
1181 "ellipse" => Ok(Self::SvgEllipse),
1182 "line" => Ok(Self::SvgLine),
1183 "polygon" => Ok(Self::SvgPolygon),
1184 "polyline" => Ok(Self::SvgPolyline),
1185 "g" => Ok(Self::SvgG),
1186
1187 "defs" => Ok(Self::SvgDefs),
1189 "symbol" => Ok(Self::SvgSymbol),
1190 "use" => Ok(Self::SvgUse),
1191 "switch" => Ok(Self::SvgSwitch),
1192
1193 "svg:text" => Ok(Self::SvgText),
1195 "tspan" => Ok(Self::SvgTspan),
1196 "textpath" => Ok(Self::SvgTextPath),
1197
1198 "lineargradient" => Ok(Self::SvgLinearGradient),
1200 "radialgradient" => Ok(Self::SvgRadialGradient),
1201 "stop" => Ok(Self::SvgStop),
1202 "pattern" => Ok(Self::SvgPattern),
1203
1204 "clippath" => Ok(Self::SvgClipPathElement),
1206 "mask" => Ok(Self::SvgMask),
1207
1208 "filter" => Ok(Self::SvgFilter),
1210 "feblend" => Ok(Self::SvgFeBlend),
1211 "fecolormatrix" => Ok(Self::SvgFeColorMatrix),
1212 "fecomponenttransfer" => Ok(Self::SvgFeComponentTransfer),
1213 "fecomposite" => Ok(Self::SvgFeComposite),
1214 "feconvolvematrix" => Ok(Self::SvgFeConvolveMatrix),
1215 "fediffuselighting" => Ok(Self::SvgFeDiffuseLighting),
1216 "fedisplacementmap" => Ok(Self::SvgFeDisplacementMap),
1217 "fedistantlight" => Ok(Self::SvgFeDistantLight),
1218 "fedropshadow" => Ok(Self::SvgFeDropShadow),
1219 "feflood" => Ok(Self::SvgFeFlood),
1220 "fefuncr" => Ok(Self::SvgFeFuncR),
1221 "fefuncg" => Ok(Self::SvgFeFuncG),
1222 "fefuncb" => Ok(Self::SvgFeFuncB),
1223 "fefunca" => Ok(Self::SvgFeFuncA),
1224 "fegaussianblur" => Ok(Self::SvgFeGaussianBlur),
1225 "feimage" => Ok(Self::SvgFeImage),
1226 "femerge" => Ok(Self::SvgFeMerge),
1227 "femergenode" => Ok(Self::SvgFeMergeNode),
1228 "femorphology" => Ok(Self::SvgFeMorphology),
1229 "feoffset" => Ok(Self::SvgFeOffset),
1230 "fepointlight" => Ok(Self::SvgFePointLight),
1231 "fespecularlighting" => Ok(Self::SvgFeSpecularLighting),
1232 "fespotlight" => Ok(Self::SvgFeSpotLight),
1233 "fetile" => Ok(Self::SvgFeTile),
1234 "feturbulence" => Ok(Self::SvgFeTurbulence),
1235
1236 "image" | "svg:image" => Ok(Self::SvgImage),
1238 "svg:marker" => Ok(Self::SvgMarker),
1239 "foreignobject" => Ok(Self::SvgForeignObject),
1240
1241 "svg:title" => Ok(Self::SvgTitle),
1243 "svg:a" => Ok(Self::SvgA),
1244 "svg:style" => Ok(Self::SvgStyle),
1245 "svg:script" => Ok(Self::SvgScript),
1246 "desc" => Ok(Self::SvgDesc),
1247 "metadata" => Ok(Self::SvgMetadata),
1248 "view" => Ok(Self::SvgView),
1249
1250 "animate" => Ok(Self::SvgAnimate),
1252 "animatemotion" => Ok(Self::SvgAnimateMotion),
1253 "animatetransform" => Ok(Self::SvgAnimateTransform),
1254 "set" => Ok(Self::SvgSet),
1255 "mpath" => Ok(Self::SvgMpath),
1256
1257 "title" => Ok(Self::Title),
1259 "meta" => Ok(Self::Meta),
1260 "link" => Ok(Self::Link),
1261 "script" => Ok(Self::Script),
1262 "style" => Ok(Self::Style),
1263 "base" => Ok(Self::Base),
1264
1265 "text" => Ok(Self::Text), "img" => Ok(Self::Img),
1268 "virtual-view" | "iframe" => Ok(Self::VirtualView),
1269 "icon" => Ok(Self::Icon),
1270 "geolocation-probe" => Ok(Self::GeolocationProbe),
1271
1272 "before" | "::before" => Ok(Self::Before),
1274 "after" | "::after" => Ok(Self::After),
1275 "marker" | "::marker" => Ok(Self::Marker),
1276 "placeholder" | "::placeholder" => Ok(Self::Placeholder),
1277
1278 other => Err(NodeTypeTagParseError::Invalid(other)),
1279 }
1280 }
1281}
1282
1283impl fmt::Display for NodeTypeTag {
1284 #[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1286 match self {
1287 Self::Html => write!(f, "html"),
1289 Self::Head => write!(f, "head"),
1290 Self::Body => write!(f, "body"),
1291
1292 Self::Div => write!(f, "div"),
1294 Self::P => write!(f, "p"),
1295 Self::Article => write!(f, "article"),
1296 Self::Section => write!(f, "section"),
1297 Self::Nav => write!(f, "nav"),
1298 Self::Aside => write!(f, "aside"),
1299 Self::Header => write!(f, "header"),
1300 Self::Footer => write!(f, "footer"),
1301 Self::Main => write!(f, "main"),
1302 Self::Figure => write!(f, "figure"),
1303 Self::FigCaption => write!(f, "figcaption"),
1304
1305 Self::H1 => write!(f, "h1"),
1307 Self::H2 => write!(f, "h2"),
1308 Self::H3 => write!(f, "h3"),
1309 Self::H4 => write!(f, "h4"),
1310 Self::H5 => write!(f, "h5"),
1311 Self::H6 => write!(f, "h6"),
1312
1313 Self::Br => write!(f, "br"),
1315 Self::Hr => write!(f, "hr"),
1316 Self::Pre => write!(f, "pre"),
1317 Self::BlockQuote => write!(f, "blockquote"),
1318 Self::Address => write!(f, "address"),
1319 Self::Details => write!(f, "details"),
1320 Self::Summary => write!(f, "summary"),
1321 Self::Dialog => write!(f, "dialog"),
1322
1323 Self::Ul => write!(f, "ul"),
1325 Self::Ol => write!(f, "ol"),
1326 Self::Li => write!(f, "li"),
1327 Self::Dl => write!(f, "dl"),
1328 Self::Dt => write!(f, "dt"),
1329 Self::Dd => write!(f, "dd"),
1330 Self::Menu => write!(f, "menu"),
1331 Self::MenuItem => write!(f, "menuitem"),
1332 Self::Dir => write!(f, "dir"),
1333
1334 Self::Table => write!(f, "table"),
1336 Self::Caption => write!(f, "caption"),
1337 Self::THead => write!(f, "thead"),
1338 Self::TBody => write!(f, "tbody"),
1339 Self::TFoot => write!(f, "tfoot"),
1340 Self::Tr => write!(f, "tr"),
1341 Self::Th => write!(f, "th"),
1342 Self::Td => write!(f, "td"),
1343 Self::ColGroup => write!(f, "colgroup"),
1344 Self::Col => write!(f, "col"),
1345
1346 Self::Form => write!(f, "form"),
1348 Self::FieldSet => write!(f, "fieldset"),
1349 Self::Legend => write!(f, "legend"),
1350 Self::Label => write!(f, "label"),
1351 Self::Input => write!(f, "input"),
1352 Self::Button => write!(f, "button"),
1353 Self::Select => write!(f, "select"),
1354 Self::OptGroup => write!(f, "optgroup"),
1355 Self::SelectOption => write!(f, "option"),
1356 Self::TextArea => write!(f, "textarea"),
1357 Self::Output => write!(f, "output"),
1358 Self::Progress => write!(f, "progress"),
1359 Self::Meter => write!(f, "meter"),
1360 Self::DataList => write!(f, "datalist"),
1361
1362 Self::Span => write!(f, "span"),
1364 Self::A => write!(f, "a"),
1365 Self::Em => write!(f, "em"),
1366 Self::Strong => write!(f, "strong"),
1367 Self::B => write!(f, "b"),
1368 Self::I => write!(f, "i"),
1369 Self::U => write!(f, "u"),
1370 Self::S => write!(f, "s"),
1371 Self::Mark => write!(f, "mark"),
1372 Self::Del => write!(f, "del"),
1373 Self::Ins => write!(f, "ins"),
1374 Self::Code => write!(f, "code"),
1375 Self::Samp => write!(f, "samp"),
1376 Self::Kbd => write!(f, "kbd"),
1377 Self::Var => write!(f, "var"),
1378 Self::Cite => write!(f, "cite"),
1379 Self::Dfn => write!(f, "dfn"),
1380 Self::Abbr => write!(f, "abbr"),
1381 Self::Acronym => write!(f, "acronym"),
1382 Self::Q => write!(f, "q"),
1383 Self::Time => write!(f, "time"),
1384 Self::Sub => write!(f, "sub"),
1385 Self::Sup => write!(f, "sup"),
1386 Self::Small => write!(f, "small"),
1387 Self::Big => write!(f, "big"),
1388 Self::Bdo => write!(f, "bdo"),
1389 Self::Bdi => write!(f, "bdi"),
1390 Self::Wbr => write!(f, "wbr"),
1391 Self::Ruby => write!(f, "ruby"),
1392 Self::Rt => write!(f, "rt"),
1393 Self::Rtc => write!(f, "rtc"),
1394 Self::Rp => write!(f, "rp"),
1395 Self::Data => write!(f, "data"),
1396
1397 Self::Canvas => write!(f, "canvas"),
1399 Self::Object => write!(f, "object"),
1400 Self::Param => write!(f, "param"),
1401 Self::Embed => write!(f, "embed"),
1402 Self::Audio => write!(f, "audio"),
1403 Self::Video => write!(f, "video"),
1404 Self::Source => write!(f, "source"),
1405 Self::Track => write!(f, "track"),
1406 Self::Map => write!(f, "map"),
1407 Self::Area => write!(f, "area"),
1408 Self::Svg => write!(f, "svg"),
1409 Self::SvgPath => write!(f, "path"),
1410 Self::SvgCircle => write!(f, "circle"),
1411 Self::SvgRect => write!(f, "rect"),
1412 Self::SvgEllipse => write!(f, "ellipse"),
1413 Self::SvgLine => write!(f, "line"),
1414 Self::SvgPolygon => write!(f, "polygon"),
1415 Self::SvgPolyline => write!(f, "polyline"),
1416 Self::SvgG => write!(f, "g"),
1417
1418 Self::SvgDefs => write!(f, "defs"),
1420 Self::SvgSymbol => write!(f, "symbol"),
1421 Self::SvgUse => write!(f, "use"),
1422 Self::SvgSwitch => write!(f, "switch"),
1423
1424 Self::SvgText => write!(f, "svg:text"),
1426 Self::SvgTspan => write!(f, "tspan"),
1427 Self::SvgTextPath => write!(f, "textpath"),
1428
1429 Self::SvgLinearGradient => write!(f, "lineargradient"),
1431 Self::SvgRadialGradient => write!(f, "radialgradient"),
1432 Self::SvgStop => write!(f, "stop"),
1433 Self::SvgPattern => write!(f, "pattern"),
1434
1435 Self::SvgClipPathElement => write!(f, "clippath"),
1437 Self::SvgMask => write!(f, "mask"),
1438
1439 Self::SvgFilter => write!(f, "filter"),
1441 Self::SvgFeBlend => write!(f, "feblend"),
1442 Self::SvgFeColorMatrix => write!(f, "fecolormatrix"),
1443 Self::SvgFeComponentTransfer => write!(f, "fecomponenttransfer"),
1444 Self::SvgFeComposite => write!(f, "fecomposite"),
1445 Self::SvgFeConvolveMatrix => write!(f, "feconvolvematrix"),
1446 Self::SvgFeDiffuseLighting => write!(f, "fediffuselighting"),
1447 Self::SvgFeDisplacementMap => write!(f, "fedisplacementmap"),
1448 Self::SvgFeDistantLight => write!(f, "fedistantlight"),
1449 Self::SvgFeDropShadow => write!(f, "fedropshadow"),
1450 Self::SvgFeFlood => write!(f, "feflood"),
1451 Self::SvgFeFuncR => write!(f, "fefuncr"),
1452 Self::SvgFeFuncG => write!(f, "fefuncg"),
1453 Self::SvgFeFuncB => write!(f, "fefuncb"),
1454 Self::SvgFeFuncA => write!(f, "fefunca"),
1455 Self::SvgFeGaussianBlur => write!(f, "fegaussianblur"),
1456 Self::SvgFeImage => write!(f, "feimage"),
1457 Self::SvgFeMerge => write!(f, "femerge"),
1458 Self::SvgFeMergeNode => write!(f, "femergenode"),
1459 Self::SvgFeMorphology => write!(f, "femorphology"),
1460 Self::SvgFeOffset => write!(f, "feoffset"),
1461 Self::SvgFePointLight => write!(f, "fepointlight"),
1462 Self::SvgFeSpecularLighting => write!(f, "fespecularlighting"),
1463 Self::SvgFeSpotLight => write!(f, "fespotlight"),
1464 Self::SvgFeTile => write!(f, "fetile"),
1465 Self::SvgFeTurbulence => write!(f, "feturbulence"),
1466
1467 Self::SvgMarker => write!(f, "svg:marker"),
1469 Self::SvgImage => write!(f, "svg:image"),
1470 Self::SvgForeignObject => write!(f, "foreignobject"),
1471
1472 Self::SvgTitle => write!(f, "svg:title"),
1474 Self::SvgDesc => write!(f, "desc"),
1475 Self::SvgMetadata => write!(f, "metadata"),
1476 Self::SvgA => write!(f, "svg:a"),
1477 Self::SvgView => write!(f, "view"),
1478 Self::SvgStyle => write!(f, "svg:style"),
1479 Self::SvgScript => write!(f, "svg:script"),
1480
1481 Self::SvgAnimate => write!(f, "animate"),
1483 Self::SvgAnimateMotion => write!(f, "animatemotion"),
1484 Self::SvgAnimateTransform => write!(f, "animatetransform"),
1485 Self::SvgSet => write!(f, "set"),
1486 Self::SvgMpath => write!(f, "mpath"),
1487
1488 Self::Title => write!(f, "title"),
1490 Self::Meta => write!(f, "meta"),
1491 Self::Link => write!(f, "link"),
1492 Self::Script => write!(f, "script"),
1493 Self::Style => write!(f, "style"),
1494 Self::Base => write!(f, "base"),
1495
1496 Self::Text => write!(f, "text"),
1498 Self::Img => write!(f, "img"),
1499 Self::VirtualView => write!(f, "virtual-view"),
1500 Self::Icon => write!(f, "icon"),
1501 Self::GeolocationProbe => write!(f, "geolocation-probe"),
1502
1503 Self::Before => write!(f, "::before"),
1505 Self::After => write!(f, "::after"),
1506 Self::Marker => write!(f, "::marker"),
1507 Self::Placeholder => write!(f, "::placeholder"),
1508 }
1509 }
1510}
1511
1512#[derive(Clone, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
1524#[repr(C)]
1525pub struct CssPath {
1526 pub selectors: CssPathSelectorVec,
1527}
1528
1529impl_vec!(CssPathSelector, CssPathSelectorVec, CssPathSelectorVecDestructor, CssPathSelectorVecDestructorType, CssPathSelectorVecSlice, OptionCssPathSelector);
1530impl_vec_debug!(CssPathSelector, CssPathSelectorVec);
1531impl_vec_partialord!(CssPathSelector, CssPathSelectorVec);
1532impl_vec_ord!(CssPathSelector, CssPathSelectorVec);
1533impl_vec_clone!(
1534 CssPathSelector,
1535 CssPathSelectorVec,
1536 CssPathSelectorVecDestructor
1537);
1538impl_vec_partialeq!(CssPathSelector, CssPathSelectorVec);
1539impl_vec_eq!(CssPathSelector, CssPathSelectorVec);
1540impl_vec_hash!(CssPathSelector, CssPathSelectorVec);
1541
1542impl CssPath {
1543 #[must_use] pub fn new(selectors: Vec<CssPathSelector>) -> Self {
1544 Self {
1545 selectors: selectors.into(),
1546 }
1547 }
1548
1549 pub fn push_front_scope(&mut self, start: usize, end: usize) {
1563 let is_bare_global = self.selectors.as_ref().len() == 1
1564 && matches!(self.selectors.as_ref().first(), Some(CssPathSelector::Global));
1565 let range = if is_bare_global {
1566 CssScopeRange { start, end: start }
1567 } else {
1568 CssScopeRange { start, end }
1569 };
1570 let mut selectors = Vec::with_capacity(self.selectors.as_ref().len() + 1);
1571 selectors.push(CssPathSelector::Root(range));
1572 selectors.extend(self.selectors.as_ref().iter().cloned());
1573 self.selectors = selectors.into();
1574 }
1575}
1576
1577impl fmt::Display for CssPath {
1578 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1579 for selector in self.selectors.as_ref() {
1580 write!(f, "{selector}")?;
1581 }
1582 Ok(())
1583 }
1584}
1585
1586impl fmt::Debug for CssPath {
1587 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1588 write!(f, "{self}")
1589 }
1590}
1591
1592#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1600#[repr(C)]
1601pub struct CssScopeRange {
1602 pub start: usize,
1604 pub end: usize,
1606}
1607
1608impl CssScopeRange {
1609 #[inline]
1611 #[must_use] pub const fn contains(&self, node: usize) -> bool {
1612 self.start <= node && node <= self.end
1613 }
1614}
1615
1616#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1617#[repr(C, u8)]
1618#[derive(Default)]
1619pub enum CssPathSelector {
1620 #[default]
1622 Global,
1623 Root(CssScopeRange),
1632 Type(NodeTypeTag),
1634 Class(AzString),
1636 Id(AzString),
1638 PseudoSelector(CssPathPseudoSelector),
1640 Attribute(CssAttributeSelector),
1642 DirectChildren,
1644 Children,
1646 AdjacentSibling,
1648 GeneralSibling,
1650}
1651
1652#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1654#[repr(C)]
1655pub struct CssAttributeSelector {
1656 pub name: AzString,
1657 pub op: AttributeMatchOp,
1658 pub value: OptionString,
1659}
1660
1661impl Default for CssAttributeSelector {
1662 fn default() -> Self {
1663 Self {
1664 name: AzString::default(),
1665 op: AttributeMatchOp::Exists,
1666 value: OptionString::None,
1667 }
1668 }
1669}
1670
1671#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1673#[repr(C)]
1674#[derive(Default)]
1675pub enum AttributeMatchOp {
1676 #[default]
1678 Exists,
1679 Eq,
1681 Includes,
1683 DashMatch,
1685 Prefix,
1687 Suffix,
1689 Substring,
1691}
1692
1693
1694impl_option!(
1695 CssPathSelector,
1696 OptionCssPathSelector,
1697 copy = false,
1698 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1699);
1700
1701
1702impl fmt::Display for CssPathSelector {
1703 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1704 use self::CssPathSelector::{Global, Root, Type, Class, Id, PseudoSelector, Attribute, DirectChildren, Children, AdjacentSibling, GeneralSibling};
1705 match &self {
1706 Global => write!(f, "*"),
1707 Root(r) => write!(f, ":root({}..={})", r.start, r.end),
1708 Type(n) => write!(f, "{n}"),
1709 Class(c) => write!(f, ".{c}"),
1710 Id(i) => write!(f, "#{i}"),
1711 PseudoSelector(p) => write!(f, ":{p}"),
1712 Attribute(a) => write!(f, "{a}"),
1713 DirectChildren => write!(f, ">"),
1714 Children => write!(f, " "),
1715 AdjacentSibling => write!(f, "+"),
1716 GeneralSibling => write!(f, "~"),
1717 }
1718 }
1719}
1720
1721impl fmt::Display for CssAttributeSelector {
1722 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1723 match (&self.op, self.value.as_ref()) {
1724 (AttributeMatchOp::Exists, _) => write!(f, "[{}]", self.name),
1725 (op, Some(v)) => write!(f, "[{}{}=\"{}\"]", self.name, op.symbol_prefix(), v),
1726 (op, None) => write!(f, "[{}{}=\"\"]", self.name, op.symbol_prefix()),
1727 }
1728 }
1729}
1730
1731impl AttributeMatchOp {
1732 #[must_use] pub const fn symbol_prefix(&self) -> &'static str {
1735 match self {
1736 Self::Exists | Self::Eq => "",
1737 Self::Includes => "~",
1738 Self::DashMatch => "|",
1739 Self::Prefix => "^",
1740 Self::Suffix => "$",
1741 Self::Substring => "*",
1742 }
1743 }
1744}
1745
1746#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1747#[repr(C, u8)]
1748pub enum CssPathPseudoSelector {
1749 First,
1751 Last,
1753 NthChild(CssNthChildSelector),
1755 Hover,
1757 Active,
1759 Focus,
1761 Lang(AzString),
1763 Backdrop,
1765 Dragging,
1767 DragOver,
1769 Root,
1772}
1773
1774#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1776#[repr(C, u8)]
1777pub enum CssNthChildSelector {
1778 Number(u32),
1779 Even,
1780 Odd,
1781 Pattern(CssNthChildPattern),
1782}
1783
1784#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1786#[repr(C)]
1787pub struct CssNthChildPattern {
1788 pub pattern_repeat: u32,
1789 pub offset: u32,
1790}
1791
1792impl fmt::Display for CssNthChildSelector {
1793 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1794 use self::CssNthChildSelector::{Number, Even, Odd, Pattern};
1795 match &self {
1796 Number(u) => write!(f, "{u}"),
1797 Even => write!(f, "even"),
1798 Odd => write!(f, "odd"),
1799 Pattern(p) => write!(f, "{}n + {}", p.pattern_repeat, p.offset),
1800 }
1801 }
1802}
1803
1804impl fmt::Display for CssPathPseudoSelector {
1805 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1806 use self::CssPathPseudoSelector::{First, Last, NthChild, Hover, Active, Focus, Lang, Backdrop, Dragging, DragOver, Root};
1807 match &self {
1808 First => write!(f, "first"),
1809 Last => write!(f, "last"),
1810 NthChild(u) => write!(f, "nth-child({u})"),
1811 Hover => write!(f, "hover"),
1812 Active => write!(f, "active"),
1813 Focus => write!(f, "focus"),
1814 Lang(lang) => write!(f, "lang({})", lang.as_str()),
1815 Backdrop => write!(f, "backdrop"),
1816 Dragging => write!(f, "dragging"),
1817 DragOver => write!(f, "drag-over"),
1818 Root => write!(f, "root"),
1819 }
1820 }
1821}
1822
1823impl Css {
1824 #[must_use] pub fn empty() -> Self {
1826 Self::default()
1827 }
1828
1829 pub fn sort_by_specificity(&mut self) {
1834 self.rules.as_mut().sort_by(|a, b| {
1835 a.priority.cmp(&b.priority)
1836 .then_with(|| get_specificity(&a.path).cmp(&get_specificity(&b.path)))
1837 });
1838 }
1839
1840 pub fn rules(&self) -> core::slice::Iter<'_, CssRuleBlock> {
1841 self.rules.as_ref().iter()
1842 }
1843
1844 pub fn iter_inline_properties(
1852 &self,
1853 ) -> impl Iterator<
1854 Item = (
1855 &CssProperty,
1856 &DynamicSelectorVec,
1857 ),
1858 > + '_ {
1859 self.rules.as_ref().iter().flat_map(|r| {
1860 r.declarations.as_ref().iter().filter_map(move |d| match d {
1861 CssDeclaration::Static(p) => Some((p, &r.conditions)),
1862 CssDeclaration::Dynamic(_) => None,
1863 })
1864 })
1865 }
1866}
1867
1868#[cfg(test)]
1869mod root_scope_tests {
1870 use super::*;
1871
1872 #[test]
1873 fn scope_range_contains() {
1874 let r = CssScopeRange { start: 3, end: 7 };
1875 assert!(r.contains(3) && r.contains(5) && r.contains(7));
1876 assert!(!r.contains(2) && !r.contains(8));
1877 let leaf = CssScopeRange { start: 4, end: 4 };
1879 assert!(leaf.contains(4));
1880 assert!(!leaf.contains(3) && !leaf.contains(5));
1881 }
1882
1883 #[test]
1884 fn push_front_scope_compounds_with_wrapper() {
1885 let mut p = CssPath::new(vec![CssPathSelector::Global]);
1888 p.push_front_scope(5, 9);
1889 assert_eq!(
1890 p.selectors.as_ref(),
1891 &[
1892 CssPathSelector::Root(CssScopeRange { start: 5, end: 5 }),
1893 CssPathSelector::Global
1894 ][..]
1895 );
1896 let subtree = CssScopeRange { start: 5, end: 9 };
1898 let mut p2 = CssPath::new(vec![
1899 CssPathSelector::Global,
1900 CssPathSelector::Children,
1901 CssPathSelector::Class("foo".to_string().into()),
1902 ]);
1903 p2.push_front_scope(5, 9);
1904 assert_eq!(p2.selectors.as_ref()[0], CssPathSelector::Root(subtree));
1905 assert_eq!(p2.selectors.as_ref().len(), 4);
1906 }
1907
1908 #[test]
1909 fn root_display_roundtrips() {
1910 let s = CssPathSelector::Root(CssScopeRange { start: 2, end: 6 });
1911 assert_eq!(format!("{s}"), ":root(2..=6)");
1912 }
1913
1914 #[test]
1915 fn parse_inline_keeps_layout_and_style_decls() {
1916 let css = Css::parse_inline("width: 200px; height: 100px; background: red");
1919 let mut types = Vec::new();
1920 for r in css.rules.as_ref() {
1921 for d in r.declarations.as_ref() {
1922 if let CssDeclaration::Static(p) = d {
1923 types.push(alloc::format!("{:?}", p.get_type()));
1924 }
1925 }
1926 }
1927 println!("INLINE PROP TYPES: {types:?}");
1928 assert!(
1929 types.iter().any(|t| t.contains("width")),
1930 "width must survive parse_inline as a Static decl; got {types:?}"
1931 );
1932 assert!(
1933 types.iter().any(|t| t.contains("height")),
1934 "height must survive parse_inline; got {types:?}"
1935 );
1936 }
1937}
1938
1939#[cfg(test)]
1940mod priority_sort_tests {
1941 use super::*;
1942 use crate::css::rule_priority;
1943
1944 fn rule_with(priority: u8, selectors: Vec<CssPathSelector>) -> CssRuleBlock {
1945 CssRuleBlock {
1946 path: CssPath { selectors: selectors.into() },
1947 declarations: Vec::new().into(),
1948 conditions: DynamicSelectorVec::from_const_slice(&[]),
1949 priority,
1950 }
1951 }
1952
1953 #[test]
1956 fn sort_by_priority_then_specificity() {
1957 let mut css = Css::new(vec![
1958 rule_with(rule_priority::AUTHOR, vec![CssPathSelector::Global]),
1960 rule_with(rule_priority::UA, vec![
1962 CssPathSelector::Id("ua-id".to_string().into()),
1963 CssPathSelector::Class("ua-class".to_string().into()),
1964 ]),
1965 rule_with(rule_priority::AUTHOR, vec![
1967 CssPathSelector::Id("a-id".to_string().into()),
1968 ]),
1969 rule_with(rule_priority::SYSTEM, vec![CssPathSelector::Global]),
1971 ]);
1972 css.sort_by_specificity();
1973 let priorities: Vec<u8> = css.rules.as_ref().iter().map(|r| r.priority).collect();
1974 assert_eq!(
1975 priorities,
1976 vec![rule_priority::UA, rule_priority::SYSTEM, rule_priority::AUTHOR, rule_priority::AUTHOR],
1977 "rules must sort by layer first; specificity only breaks ties within a layer"
1978 );
1979 let last_two_specificity: Vec<_> = css.rules.as_ref().iter()
1981 .filter(|r| r.priority == rule_priority::AUTHOR)
1982 .map(|r| get_specificity(&r.path))
1983 .collect();
1984 assert!(last_two_specificity[0] < last_two_specificity[1]);
1985 }
1986}
1987
1988#[must_use] pub fn get_specificity(path: &CssPath) -> (usize, usize, usize, usize) {
1991 let id_count = path
1992 .selectors
1993 .iter()
1994 .filter(|x| matches!(x, CssPathSelector::Id(_)))
1995 .count();
1996 let class_count = path
1997 .selectors
1998 .iter()
1999 .filter(|x| {
2000 matches!(
2001 x,
2002 CssPathSelector::Class(_)
2003 | CssPathSelector::PseudoSelector(_)
2004 | CssPathSelector::Attribute(_)
2005 )
2006 })
2007 .count();
2008 let div_count = path
2009 .selectors
2010 .iter()
2011 .filter(|x| matches!(x, CssPathSelector::Type(_)))
2012 .count();
2013 (id_count, class_count, div_count, path.selectors.len())
2014}
2015
2016#[cfg(test)]
2017#[allow(clippy::pedantic, clippy::nursery)]
2018mod autotest_generated {
2019 use core::hash::{Hash, Hasher};
2020 use std::collections::hash_map::DefaultHasher;
2021
2022 use super::*;
2023 use crate::{
2024 dynamic_selector::DynamicSelector,
2025 props::{
2026 basic::{color::ColorU, pixel::PixelValue},
2027 layout::dimensions::LayoutWidth,
2028 style::text::StyleTextColor,
2029 },
2030 };
2031
2032 fn prop_width(px: f32) -> CssProperty {
2038 CssProperty::width(LayoutWidth::Px(PixelValue::px(px)))
2039 }
2040
2041 fn prop_text_color(r: u8) -> CssProperty {
2043 CssProperty::const_text_color(StyleTextColor {
2044 inner: ColorU::new(r, 0, 0, 255),
2045 })
2046 }
2047
2048 fn dyn_prop(id: &str, default_value: CssProperty) -> DynamicCssProperty {
2049 DynamicCssProperty {
2050 dynamic_id: id.to_string().into(),
2051 default_value,
2052 }
2053 }
2054
2055 fn rule_at(priority: u8, selectors: Vec<CssPathSelector>) -> CssRuleBlock {
2056 let mut r = CssRuleBlock::new(
2057 CssPath::new(selectors),
2058 vec![CssDeclaration::Static(prop_width(1.0))],
2059 );
2060 r.priority = priority;
2061 r
2062 }
2063
2064 fn hash_of<T: Hash>(t: &T) -> u64 {
2065 let mut h = DefaultHasher::new();
2066 t.hash(&mut h);
2067 h.finish()
2068 }
2069
2070 #[derive(Debug, Copy, Clone, PartialEq, Default)]
2073 struct TestVal(f32);
2074
2075 impl PrintAsCssValue for TestVal {
2076 fn print_as_css_value(&self) -> String {
2077 format!("{}", self.0)
2078 }
2079 }
2080
2081 impl fmt::Display for TestVal {
2082 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2083 write!(f, "{}", self.0)
2084 }
2085 }
2086
2087 fn keyword_values() -> Vec<CssPropertyValue<TestVal>> {
2089 vec![
2090 CssPropertyValue::Auto,
2091 CssPropertyValue::None,
2092 CssPropertyValue::Initial,
2093 CssPropertyValue::Inherit,
2094 CssPropertyValue::Revert,
2095 CssPropertyValue::Unset,
2096 ]
2097 }
2098
2099 #[test]
2104 fn css_empty_is_the_neutral_element() {
2105 let e = Css::empty();
2106 assert!(e.is_empty());
2107 assert_eq!(e.rules().count(), 0);
2108 assert_eq!(e.iter_inline_properties().count(), 0);
2109 assert_eq!(e, Css::default());
2111 assert_eq!(e, Css::new(Vec::new()));
2112 assert_eq!(e, Css::from(Vec::<CssRuleBlock>::new()));
2113 }
2114
2115 #[test]
2116 fn css_new_preserves_length_and_order() {
2117 let rules = vec![
2118 rule_at(rule_priority::UA, vec![CssPathSelector::Global]),
2119 rule_at(
2120 rule_priority::AUTHOR,
2121 vec![CssPathSelector::Id("a".to_string().into())],
2122 ),
2123 rule_at(rule_priority::INLINE, vec![CssPathSelector::Children]),
2124 ];
2125 let css = Css::new(rules.clone());
2126 assert!(!css.is_empty());
2127 assert_eq!(css.rules().count(), 3);
2128 assert_eq!(css.rules.as_ref(), &rules[..]);
2129 assert_eq!(css.rules().count(), css.rules.as_ref().len());
2131 }
2132
2133 #[test]
2134 fn css_new_with_many_rules_does_not_panic() {
2135 let rules: Vec<CssRuleBlock> = (0..10_000)
2136 .map(|i| rule_at((i % 256) as u8, vec![CssPathSelector::Global]))
2137 .collect();
2138 let css = Css::new(rules);
2139 assert_eq!(css.rules.as_ref().len(), 10_000);
2140 assert!(!css.is_empty());
2141 assert_eq!(css.iter_inline_properties().count(), 10_000);
2143 }
2144
2145 #[test]
2146 fn css_sort_by_specificity_on_empty_and_singleton_is_a_noop() {
2147 let mut empty = Css::empty();
2148 empty.sort_by_specificity();
2149 assert!(empty.is_empty());
2150
2151 let one = rule_at(rule_priority::AUTHOR, vec![CssPathSelector::Global]);
2152 let mut css = Css::new(vec![one.clone()]);
2153 css.sort_by_specificity();
2154 assert_eq!(css.rules.as_ref(), &[one][..]);
2155 }
2156
2157 #[test]
2158 fn css_sort_by_specificity_is_idempotent() {
2159 let mut css = Css::new(vec![
2160 rule_at(rule_priority::RUNTIME, vec![CssPathSelector::Global]),
2161 rule_at(
2162 rule_priority::UA,
2163 vec![
2164 CssPathSelector::Id("x".to_string().into()),
2165 CssPathSelector::Class("y".to_string().into()),
2166 ],
2167 ),
2168 rule_at(rule_priority::INLINE, vec![CssPathSelector::Global]),
2169 rule_at(
2170 rule_priority::AUTHOR,
2171 vec![CssPathSelector::Type(NodeTypeTag::Div)],
2172 ),
2173 rule_at(rule_priority::SYSTEM, vec![CssPathSelector::Global]),
2174 ]);
2175 css.sort_by_specificity();
2176 let once = css.clone();
2177 css.sort_by_specificity();
2178 assert_eq!(css, once, "sort_by_specificity must be idempotent");
2179
2180 let priorities: Vec<u8> = css.rules().map(|r| r.priority).collect();
2182 assert_eq!(
2183 priorities,
2184 vec![
2185 rule_priority::UA,
2186 rule_priority::SYSTEM,
2187 rule_priority::AUTHOR,
2188 rule_priority::INLINE,
2189 rule_priority::RUNTIME,
2190 ]
2191 );
2192 }
2193
2194 #[test]
2195 fn css_sort_by_specificity_keeps_equal_keys_in_source_order() {
2196 let a = CssRuleBlock::new(
2198 CssPath::new(vec![CssPathSelector::Global]),
2199 vec![CssDeclaration::Static(prop_width(1.0))],
2200 );
2201 let b = CssRuleBlock::new(
2202 CssPath::new(vec![CssPathSelector::Global]),
2203 vec![CssDeclaration::Static(prop_width(2.0))],
2204 );
2205 let mut css = Css::new(vec![a.clone(), b.clone()]);
2206 css.sort_by_specificity();
2207 assert_eq!(
2208 css.rules.as_ref(),
2209 &[a, b][..],
2210 "ties must keep source order (last-wins cascade depends on it)"
2211 );
2212 }
2213
2214 #[test]
2215 fn css_iter_inline_properties_skips_dynamic_declarations() {
2216 let css = Css::new(vec![CssRuleBlock::with_conditions(
2217 CssPath::new(vec![CssPathSelector::Global]),
2218 vec![
2219 CssDeclaration::Static(prop_width(10.0)),
2220 CssDeclaration::Dynamic(dyn_prop("d", prop_text_color(1))),
2221 CssDeclaration::Static(prop_text_color(2)),
2222 ],
2223 vec![DynamicSelector::ContainerName("c".to_string().into())],
2224 )]);
2225
2226 let collected: Vec<_> = css.iter_inline_properties().collect();
2227 assert_eq!(collected.len(), 2, "Dynamic declarations must be skipped");
2228 assert_eq!(collected[0].0.get_type(), CssPropertyType::Width);
2229 assert_eq!(collected[1].0.get_type(), CssPropertyType::TextColor);
2230 for (_, conds) in &collected {
2232 assert_eq!(conds.as_ref().len(), 1);
2233 }
2234 }
2235
2236 #[test]
2237 fn css_iter_inline_properties_on_rule_without_declarations() {
2238 let css = Css::new(vec![CssRuleBlock::new(
2239 CssPath::new(vec![CssPathSelector::Global]),
2240 Vec::new(),
2241 )]);
2242 assert!(!css.is_empty(), "a rule with 0 declarations is still a rule");
2243 assert_eq!(css.iter_inline_properties().count(), 0);
2244 }
2245
2246 #[test]
2247 fn css_ord_is_length_based_by_design() {
2248 let a = Css::new(vec![rule_at(
2253 rule_priority::UA,
2254 vec![CssPathSelector::Global],
2255 )]);
2256 let b = Css::new(vec![rule_at(
2257 rule_priority::RUNTIME,
2258 vec![CssPathSelector::Type(NodeTypeTag::Div)],
2259 )]);
2260 assert_ne!(a, b);
2261 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
2262 assert_eq!(a.partial_cmp(&b), Some(core::cmp::Ordering::Equal));
2263 let longer = Css::new(vec![
2265 rule_at(rule_priority::UA, vec![CssPathSelector::Global]),
2266 rule_at(rule_priority::UA, vec![CssPathSelector::Global]),
2267 ]);
2268 assert_eq!(a.cmp(&longer), core::cmp::Ordering::Less);
2269 assert_eq!(Css::empty().cmp(&a), core::cmp::Ordering::Less);
2270 }
2271
2272 #[test]
2277 fn css_declaration_new_static_matches_the_wrapped_property() {
2278 let p = prop_width(42.0);
2279 let d = CssDeclaration::new_static(p.clone());
2280 assert_eq!(d, CssDeclaration::Static(p.clone()));
2281 assert_eq!(d.get_type(), p.get_type());
2282 assert_eq!(d.get_type(), CssPropertyType::Width);
2283 }
2284
2285 #[test]
2286 fn css_declaration_new_dynamic_takes_its_type_from_the_default_value() {
2287 let dp = dyn_prop("my_id", prop_text_color(7));
2288 let d = CssDeclaration::new_dynamic(dp.clone());
2289 assert_eq!(d, CssDeclaration::Dynamic(dp));
2290 assert_eq!(
2291 d.get_type(),
2292 CssPropertyType::TextColor,
2293 "a Dynamic declaration's type is its default value's type"
2294 );
2295 }
2296
2297 #[test]
2298 fn css_declaration_is_inheritable_matrix() {
2299 assert!(CssDeclaration::new_static(prop_text_color(1)).is_inheritable());
2301 assert!(!CssDeclaration::new_static(prop_width(1.0)).is_inheritable());
2302 assert!(
2305 !CssDeclaration::new_dynamic(dyn_prop("c", prop_text_color(1))).is_inheritable(),
2306 "Dynamic declarations must never inherit, even wrapping an inheritable prop"
2307 );
2308 assert!(!CssDeclaration::new_dynamic(dyn_prop("w", prop_width(1.0))).is_inheritable());
2309 }
2310
2311 #[test]
2312 fn css_declaration_can_trigger_relayout_matrix() {
2313 assert!(CssDeclaration::new_static(prop_width(1.0)).can_trigger_relayout());
2314 assert!(!CssDeclaration::new_static(prop_text_color(1)).can_trigger_relayout());
2315 assert!(CssDeclaration::new_dynamic(dyn_prop("w", prop_width(1.0))).can_trigger_relayout());
2317 assert!(
2318 !CssDeclaration::new_dynamic(dyn_prop("c", prop_text_color(1))).can_trigger_relayout()
2319 );
2320 }
2321
2322 #[test]
2323 fn css_declaration_to_str_static_is_non_empty_for_edge_floats() {
2324 for px in [
2325 0.0_f32,
2326 -0.0,
2327 f32::MIN,
2328 f32::MAX,
2329 f32::NAN,
2330 f32::INFINITY,
2331 f32::NEG_INFINITY,
2332 f32::EPSILON,
2333 ] {
2334 let s = CssDeclaration::new_static(prop_width(px)).to_str();
2335 assert!(
2336 !s.is_empty(),
2337 "to_str() must render something for width: {px:?}"
2338 );
2339 }
2340 }
2341
2342 #[test]
2343 fn css_declaration_to_str_dynamic_renders_var_syntax() {
2344 let s = CssDeclaration::new_dynamic(dyn_prop("my_id", prop_width(5.0))).to_str();
2345 assert!(
2346 s.starts_with("var(--my_id, "),
2347 "dynamic to_str must render CSS var() syntax, got {s:?}"
2348 );
2349 assert!(s.ends_with(')'));
2350 }
2351
2352 #[test]
2353 fn css_declaration_to_str_dynamic_with_hostile_ids_does_not_panic() {
2354 let long = "x".repeat(100_000);
2355 for id in [
2356 "",
2357 " ",
2358 "😀",
2359 "a\u{0301}\u{0301}",
2360 "--)",
2361 "\u{0}",
2362 "a\nb",
2363 long.as_str(),
2364 ] {
2365 let s = CssDeclaration::new_dynamic(dyn_prop(id, prop_width(1.0))).to_str();
2366 assert!(s.starts_with("var(--"));
2367 }
2368 }
2369
2370 #[test]
2375 fn dynamic_css_property_is_never_inheritable() {
2376 for p in [
2377 prop_text_color(0),
2378 prop_width(0.0),
2379 prop_width(f32::NAN),
2380 CssProperty::const_none(CssPropertyType::FontSize),
2381 CssProperty::const_inherit(CssPropertyType::TextColor),
2382 ] {
2383 assert!(
2384 !dyn_prop("id", p).is_inheritable(),
2385 "DynamicCssProperty::is_inheritable is unconditionally false"
2386 );
2387 }
2388 }
2389
2390 #[test]
2391 fn dynamic_css_property_relayout_follows_the_default_value_type() {
2392 assert!(dyn_prop("a", prop_width(1.0)).can_trigger_relayout());
2393 assert!(!dyn_prop("a", prop_text_color(1)).can_trigger_relayout());
2394 assert!(dyn_prop("a", CssProperty::const_auto(CssPropertyType::Width)).can_trigger_relayout());
2396 assert!(
2397 !dyn_prop("a", CssProperty::const_none(CssPropertyType::TextColor))
2398 .can_trigger_relayout()
2399 );
2400 }
2401
2402 static STATIC_U32: u32 = 0xDEAD_BEEF;
2407
2408 #[test]
2409 fn box_or_static_heap_round_trips_through_as_ref_and_deref() {
2410 let b = BoxOrStatic::heap(123_u32);
2411 assert_eq!(*b.as_ref(), 123);
2412 assert_eq!(*b, 123, "Deref must agree with as_ref");
2413 assert_eq!(*BoxOrStatic::heap(u32::MAX).as_ref(), u32::MAX);
2414 assert_eq!(*BoxOrStatic::heap(0_u32).as_ref(), 0);
2415 let z = BoxOrStatic::heap(());
2418 let _: &() = z.as_ref();
2419 let () = *z;
2420 let big = BoxOrStatic::heap(vec![0_u8; 1_000_000]);
2421 assert_eq!(big.as_ref().len(), 1_000_000);
2422 }
2423
2424 #[test]
2425 fn box_or_static_static_variant_reads_through_as_ref() {
2426 let b: BoxOrStatic<u32> = BoxOrStatic::Static(&STATIC_U32 as *const u32);
2427 assert_eq!(*b.as_ref(), 0xDEAD_BEEF);
2428 assert_eq!(*b, 0xDEAD_BEEF);
2429 }
2430
2431 #[test]
2432 fn box_or_static_as_mut_mutates_the_boxed_value() {
2433 let mut b = BoxOrStatic::heap(1_u32);
2434 *b.as_mut() = 9;
2435 assert_eq!(*b.as_ref(), 9);
2436 }
2437
2438 #[test]
2439 #[should_panic(expected = "Cannot mutate a static BoxOrStatic value")]
2440 fn box_or_static_as_mut_on_static_panics_as_documented() {
2441 let mut b: BoxOrStatic<u32> = BoxOrStatic::Static(&STATIC_U32 as *const u32);
2442 let _ = b.as_mut();
2443 }
2444
2445 #[test]
2446 fn box_or_static_clone_is_deep_for_boxed() {
2447 let a = BoxOrStatic::heap(5_u32);
2448 let mut b = a.clone();
2449 *b.as_mut() = 6;
2450 assert_eq!(*a.as_ref(), 5, "cloning a Boxed value must not alias it");
2451 assert_eq!(*b.as_ref(), 6);
2452 assert_ne!(a, b);
2453 }
2454
2455 #[test]
2456 fn box_or_static_eq_ord_hash_all_delegate_to_the_inner_value() {
2457 let heap = BoxOrStatic::heap(0xDEAD_BEEF_u32);
2458 let stat: BoxOrStatic<u32> = BoxOrStatic::Static(&STATIC_U32 as *const u32);
2459 assert_eq!(heap, stat);
2461 assert_eq!(hash_of(&heap), hash_of(&stat));
2462 assert_eq!(heap.cmp(&stat), core::cmp::Ordering::Equal);
2463 assert_eq!(heap.partial_cmp(&stat), Some(core::cmp::Ordering::Equal));
2464
2465 let smaller = BoxOrStatic::heap(1_u32);
2466 assert!(smaller < heap);
2467 }
2468
2469 #[test]
2470 fn box_or_static_debug_and_display_render_the_inner_value() {
2471 let b = BoxOrStatic::heap(42_u32);
2472 assert_eq!(format!("{b:?}"), "42");
2473 assert_eq!(format!("{b}"), "42");
2474 let s: BoxOrStaticString = BoxOrStatic::heap(String::new().into());
2475 assert!(
2476 !format!("{s:?}").is_empty(),
2477 "Debug of an empty string payload is still well-formed"
2478 );
2479 for f in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, f64::MIN] {
2481 let bf = BoxOrStatic::heap(f);
2482 assert!(!format!("{bf:?}").is_empty());
2483 assert!(!format!("{bf}").is_empty());
2484 }
2485 }
2486
2487 #[test]
2488 fn box_or_static_default_is_a_heap_allocated_default() {
2489 let b: BoxOrStatic<u32> = BoxOrStatic::default();
2490 assert_eq!(*b.as_ref(), 0);
2491 assert!(matches!(b, BoxOrStatic::Boxed(_)));
2492 let s: BoxOrStaticString = BoxOrStatic::default();
2493 assert_eq!(s.as_ref().as_str(), "");
2494 }
2495
2496 #[test]
2497 fn box_or_static_into_inner_returns_the_payload() {
2498 assert_eq!(BoxOrStatic::heap(7_u32).into_inner(), 7);
2499 let stat: BoxOrStatic<u32> = BoxOrStatic::Static(&STATIC_U32 as *const u32);
2500 assert_eq!(stat.into_inner(), 0xDEAD_BEEF);
2501 let s: BoxOrStaticString = BoxOrStatic::heap("hello".to_string().into());
2502 assert_eq!(s.into_inner().as_str(), "hello");
2503 }
2504
2505 #[test]
2506 fn box_or_static_into_inner_must_not_leak_the_box() {
2507 use core::sync::atomic::{AtomicIsize, Ordering};
2508
2509 static LIVE: AtomicIsize = AtomicIsize::new(0);
2512
2513 struct Tracked(u32);
2514 impl Tracked {
2515 fn new(v: u32) -> Self {
2516 LIVE.fetch_add(1, Ordering::SeqCst);
2517 Self(v)
2518 }
2519 }
2520 impl Clone for Tracked {
2521 fn clone(&self) -> Self {
2522 LIVE.fetch_add(1, Ordering::SeqCst);
2523 Self(self.0)
2524 }
2525 }
2526 impl Drop for Tracked {
2527 fn drop(&mut self) {
2528 LIVE.fetch_sub(1, Ordering::SeqCst);
2529 }
2530 }
2531
2532 let boxed = BoxOrStatic::heap(Tracked::new(7));
2533 assert_eq!(LIVE.load(Ordering::SeqCst), 1);
2534
2535 let inner = boxed.into_inner();
2536 assert_eq!(inner.0, 7);
2537 drop(inner);
2538
2539 assert_eq!(
2540 LIVE.load(Ordering::SeqCst),
2541 0,
2542 "into_inner() on a Boxed variant leaks: it clones the payload and then \
2543 mem::forget(self), so `Drop for BoxOrStatic` never runs and the Box \
2544 (plus the T inside it) is never freed"
2545 );
2546 }
2547
2548 #[test]
2549 #[cfg(target_pointer_width = "64")]
2550 fn box_or_static_is_the_documented_16_bytes() {
2551 assert_eq!(size_of::<BoxOrStatic<u32>>(), 16);
2552 assert_eq!(size_of::<BoxOrStaticString>(), 16);
2553 }
2554
2555 #[test]
2560 fn css_property_value_predicates_are_mutually_exclusive() {
2561 for v in keyword_values() {
2562 let flags = [
2563 v.is_auto(),
2564 v.is_none(),
2565 v.is_initial(),
2566 v.is_inherit(),
2567 v.is_revert(),
2568 v.is_unset(),
2569 ];
2570 assert_eq!(
2571 flags.iter().filter(|b| **b).count(),
2572 1,
2573 "exactly one predicate must fire for {v:?}"
2574 );
2575 assert!(
2576 v.get_property().is_none(),
2577 "a keyword variant has no property"
2578 );
2579 }
2580
2581 let exact = CssPropertyValue::Exact(TestVal(1.0));
2582 assert!(
2583 !exact.is_auto()
2584 && !exact.is_none()
2585 && !exact.is_initial()
2586 && !exact.is_inherit()
2587 && !exact.is_revert()
2588 && !exact.is_unset(),
2589 "Exact must answer false to every keyword predicate"
2590 );
2591 }
2592
2593 #[test]
2594 fn css_property_value_predicates_pick_the_right_variant() {
2595 assert!(CssPropertyValue::<TestVal>::Auto.is_auto());
2596 assert!(CssPropertyValue::<TestVal>::None.is_none());
2597 assert!(CssPropertyValue::<TestVal>::Initial.is_initial());
2598 assert!(CssPropertyValue::<TestVal>::Inherit.is_inherit());
2599 assert!(CssPropertyValue::<TestVal>::Revert.is_revert());
2600 assert!(CssPropertyValue::<TestVal>::Unset.is_unset());
2601 }
2602
2603 #[test]
2604 fn css_property_value_get_property_and_get_property_owned_agree() {
2605 let exact = CssPropertyValue::Exact(TestVal(3.5));
2606 assert_eq!(exact.get_property(), Some(&TestVal(3.5)));
2607 assert_eq!(exact.get_property_owned(), Some(TestVal(3.5)));
2608 for v in keyword_values() {
2609 assert_eq!(v.get_property(), None);
2610 assert_eq!(v.get_property_owned(), None);
2611 }
2612 }
2613
2614 #[test]
2615 fn css_property_value_get_property_or_default_substitutes_only_auto_and_initial() {
2616 assert_eq!(
2619 CssPropertyValue::<TestVal>::Auto.get_property_or_default(),
2620 Some(TestVal::default())
2621 );
2622 assert_eq!(
2623 CssPropertyValue::<TestVal>::Initial.get_property_or_default(),
2624 Some(TestVal::default())
2625 );
2626 assert_eq!(CssPropertyValue::<TestVal>::None.get_property_or_default(), None);
2627 assert_eq!(
2628 CssPropertyValue::<TestVal>::Inherit.get_property_or_default(),
2629 None
2630 );
2631 assert_eq!(
2632 CssPropertyValue::<TestVal>::Revert.get_property_or_default(),
2633 None
2634 );
2635 assert_eq!(CssPropertyValue::<TestVal>::Unset.get_property_or_default(), None);
2636 assert_eq!(
2637 CssPropertyValue::Exact(TestVal(9.0)).get_property_or_default(),
2638 Some(TestVal(9.0))
2639 );
2640 }
2641
2642 #[test]
2643 fn css_property_value_default_is_exact_default() {
2644 assert_eq!(
2645 CssPropertyValue::<TestVal>::default(),
2646 CssPropertyValue::Exact(TestVal::default())
2647 );
2648 assert!(!CssPropertyValue::<TestVal>::default().is_auto());
2649 }
2650
2651 #[test]
2652 fn css_property_value_from_wraps_into_exact() {
2653 let v: CssPropertyValue<TestVal> = TestVal(2.0).into();
2654 assert_eq!(v, CssPropertyValue::Exact(TestVal(2.0)));
2655 }
2656
2657 #[test]
2658 fn css_property_value_keyword_serialization_is_the_css_keyword() {
2659 let cases: [(CssPropertyValue<TestVal>, &str); 6] = [
2660 (CssPropertyValue::Auto, "auto"),
2661 (CssPropertyValue::None, "none"),
2662 (CssPropertyValue::Initial, "initial"),
2663 (CssPropertyValue::Inherit, "inherit"),
2664 (CssPropertyValue::Revert, "revert"),
2665 (CssPropertyValue::Unset, "unset"),
2666 ];
2667 for (v, expected) in cases {
2668 assert_eq!(v.get_css_value_fmt(), expected);
2669 assert_eq!(
2670 format!("{v}"),
2671 expected,
2672 "Display and get_css_value_fmt must not diverge for keywords"
2673 );
2674 }
2675 let exact = CssPropertyValue::Exact(TestVal(1.5));
2676 assert_eq!(exact.get_css_value_fmt(), "1.5");
2677 assert_eq!(format!("{exact}"), "1.5");
2678 }
2679
2680 #[test]
2681 fn css_property_value_serializes_hostile_floats_without_panicking() {
2682 for f in [
2683 f32::NAN,
2684 f32::INFINITY,
2685 f32::NEG_INFINITY,
2686 f32::MIN,
2687 f32::MAX,
2688 -0.0,
2689 f32::MIN_POSITIVE,
2690 ] {
2691 let v = CssPropertyValue::Exact(TestVal(f));
2692 assert!(!v.get_css_value_fmt().is_empty());
2693 assert!(!format!("{v}").is_empty());
2694 }
2695 }
2696
2697 #[test]
2698 fn css_property_value_map_property_preserves_keyword_variants() {
2699 for v in keyword_values() {
2701 let before = format!("{v}");
2702 let mapped: CssPropertyValue<u32> =
2703 v.map_property(|_| panic!("map_fn must not run on a keyword variant"));
2704 assert_eq!(format!("{mapped}"), before, "the keyword must survive the map");
2705 }
2706 let mapped = CssPropertyValue::Exact(TestVal(2.0)).map_property(|t| t.0 as u32);
2708 assert_eq!(mapped, CssPropertyValue::Exact(2_u32));
2709 }
2710
2711 #[test]
2712 fn css_property_value_map_property_handles_nan_and_type_changing_maps() {
2713 let mapped = CssPropertyValue::Exact(TestVal(f32::NAN)).map_property(|t| t.0.is_nan());
2714 assert_eq!(mapped, CssPropertyValue::Exact(true));
2715 let mapped = CssPropertyValue::Exact(TestVal(f32::INFINITY)).map_property(|t| t.0 as i64);
2717 assert_eq!(mapped, CssPropertyValue::Exact(i64::MAX));
2718 }
2719
2720 #[test]
2725 fn css_rule_block_new_defaults_to_author_priority_and_no_conditions() {
2726 let decls = vec![
2727 CssDeclaration::Static(prop_width(1.0)),
2728 CssDeclaration::Static(prop_text_color(2)),
2729 ];
2730 let r = CssRuleBlock::new(
2731 CssPath::new(vec![CssPathSelector::Type(NodeTypeTag::Div)]),
2732 decls.clone(),
2733 );
2734 assert_eq!(r.priority, rule_priority::AUTHOR);
2735 assert!(r.conditions.as_ref().is_empty());
2736 assert_eq!(r.declarations.as_ref(), &decls[..]);
2737 assert_eq!(r.path.selectors.as_ref().len(), 1);
2738 }
2739
2740 #[test]
2741 fn css_rule_block_with_conditions_keeps_every_condition() {
2742 let conds: Vec<DynamicSelector> = (0..1_000)
2743 .map(|i| DynamicSelector::ContainerName(format!("c{i}").into()))
2744 .collect();
2745 let r = CssRuleBlock::with_conditions(
2746 CssPath::default(),
2747 Vec::new(),
2748 conds.clone(),
2749 );
2750 assert_eq!(r.conditions.as_ref().len(), 1_000);
2751 assert_eq!(r.conditions.as_ref(), &conds[..]);
2752 assert_eq!(
2753 r.priority,
2754 rule_priority::AUTHOR,
2755 "with_conditions must not change the layer"
2756 );
2757 assert!(r.declarations.as_ref().is_empty());
2758 assert!(r.path.selectors.as_ref().is_empty());
2759 }
2760
2761 #[test]
2762 fn css_rule_block_default_is_empty_and_ua_priority() {
2763 let r = CssRuleBlock::default();
2764 assert!(r.path.selectors.as_ref().is_empty());
2765 assert!(r.declarations.as_ref().is_empty());
2766 assert!(r.conditions.as_ref().is_empty());
2767 assert_eq!(r.priority, rule_priority::UA, "u8::default() == 0 == UA");
2768 }
2769
2770 #[test]
2771 fn rule_priority_slots_are_strictly_ordered() {
2772 const _: () = assert!(rule_priority::UA < rule_priority::SYSTEM);
2773 const _: () = assert!(rule_priority::SYSTEM < rule_priority::AUTHOR);
2774 const _: () = assert!(rule_priority::AUTHOR < rule_priority::INLINE);
2775 const _: () = assert!(rule_priority::INLINE < rule_priority::RUNTIME);
2776 }
2777
2778 const ALL_TAGS: &[NodeTypeTag] = {
2783 use NodeTypeTag::*;
2784 &[
2785 Html, Head, Body, Div, P, Article, Section, Nav, Aside, Header, Footer, Main, Figure,
2786 FigCaption, H1, H2, H3, H4, H5, H6, Br, Hr, Pre, BlockQuote, Address, Details, Summary,
2787 Dialog, Ul, Ol, Li, Dl, Dt, Dd, Menu, MenuItem, Dir, Table, Caption, THead, TBody,
2788 TFoot, Tr, Th, Td, ColGroup, Col, Form, FieldSet, Legend, Label, Input, Button, Select,
2789 OptGroup, SelectOption, TextArea, Output, Progress, Meter, DataList, Span, A, Em,
2790 Strong, B, I, U, S, Mark, Del, Ins, Code, Samp, Kbd, Var, Cite, Dfn, Abbr, Acronym, Q,
2791 Time, Sub, Sup, Small, Big, Bdo, Bdi, Wbr, Ruby, Rt, Rtc, Rp, Data, Canvas, Object,
2792 Param, Embed, Audio, Video, Source, Track, Map, Area, Svg, SvgPath, SvgCircle, SvgRect,
2793 SvgEllipse, SvgLine, SvgPolygon, SvgPolyline, SvgG, SvgDefs, SvgSymbol, SvgUse,
2794 SvgSwitch, SvgText, SvgTspan, SvgTextPath, SvgLinearGradient, SvgRadialGradient,
2795 SvgStop, SvgPattern, SvgClipPathElement, SvgMask, SvgFilter, SvgFeBlend,
2796 SvgFeColorMatrix, SvgFeComponentTransfer, SvgFeComposite, SvgFeConvolveMatrix,
2797 SvgFeDiffuseLighting, SvgFeDisplacementMap, SvgFeDistantLight, SvgFeDropShadow,
2798 SvgFeFlood, SvgFeFuncR, SvgFeFuncG, SvgFeFuncB, SvgFeFuncA, SvgFeGaussianBlur,
2799 SvgFeImage, SvgFeMerge, SvgFeMergeNode, SvgFeMorphology, SvgFeOffset, SvgFePointLight,
2800 SvgFeSpecularLighting, SvgFeSpotLight, SvgFeTile, SvgFeTurbulence, SvgMarker, SvgImage,
2801 SvgForeignObject, SvgTitle, SvgDesc, SvgMetadata, SvgA, SvgView, SvgStyle, SvgScript,
2802 SvgAnimate, SvgAnimateMotion, SvgAnimateTransform, SvgSet, SvgMpath, Title, Meta, Link,
2803 Script, Style, Base, Text, Img, VirtualView, Icon, GeolocationProbe, Before, After,
2804 Marker, Placeholder,
2805 ]
2806 };
2807
2808 #[test]
2809 fn node_type_tag_variant_list_is_complete_and_unique() {
2810 assert_eq!(
2813 ALL_TAGS.len(),
2814 182,
2815 "ALL_TAGS is out of sync with the NodeTypeTag enum"
2816 );
2817 let mut seen: Vec<NodeTypeTag> = Vec::new();
2818 for t in ALL_TAGS {
2819 assert!(!seen.contains(t), "duplicate entry in ALL_TAGS: {t:?}");
2820 seen.push(*t);
2821 }
2822 }
2823
2824 #[test]
2825 fn node_type_tag_display_names_are_all_distinct() {
2826 let mut names: Vec<String> = ALL_TAGS.iter().map(ToString::to_string).collect();
2827 names.sort();
2828 let before = names.len();
2829 names.dedup();
2830 assert_eq!(
2831 names.len(),
2832 before,
2833 "two NodeTypeTag variants serialize to the same CSS tag name — \
2834 the string is then ambiguous on the way back in"
2835 );
2836 }
2837
2838 #[test]
2839 fn node_type_tag_display_then_from_str_round_trips_every_variant() {
2840 let mut broken: Vec<(NodeTypeTag, String)> = Vec::new();
2841 for tag in ALL_TAGS {
2842 let serialized = tag.to_string();
2843 let round_trips = matches!(NodeTypeTag::from_str(&serialized), Ok(t) if t == *tag);
2846 if !round_trips {
2847 broken.push((*tag, serialized));
2848 }
2849 }
2850 assert!(
2851 broken.is_empty(),
2852 "from_str(Display(tag)) must yield tag back, but these variants do not \
2853 round-trip: {broken:?}"
2854 );
2855 }
2856
2857 #[test]
2858 fn node_type_tag_serialize_parse_serialize_is_stable() {
2859 for tag in ALL_TAGS {
2862 let once = tag.to_string();
2863 if let Ok(parsed) = NodeTypeTag::from_str(&once) {
2864 assert_eq!(
2865 parsed.to_string(),
2866 once,
2867 "serialize(parse(serialize({tag:?}))) drifted"
2868 );
2869 }
2870 }
2871 }
2872
2873 #[test]
2874 fn node_type_tag_from_str_valid_minimal() {
2875 assert_eq!(NodeTypeTag::from_str("div"), Ok(NodeTypeTag::Div));
2876 assert_eq!(NodeTypeTag::from_str("p"), Ok(NodeTypeTag::P));
2877 assert_eq!(NodeTypeTag::from_str("a"), Ok(NodeTypeTag::A));
2878 }
2879
2880 #[test]
2881 fn node_type_tag_from_str_accepts_documented_aliases() {
2882 assert_eq!(NodeTypeTag::from_str("image"), Ok(NodeTypeTag::SvgImage));
2884 assert_eq!(NodeTypeTag::from_str("svg:image"), Ok(NodeTypeTag::SvgImage));
2885 assert_eq!(NodeTypeTag::SvgImage.to_string(), "svg:image");
2886
2887 assert_eq!(NodeTypeTag::from_str("iframe"), Ok(NodeTypeTag::VirtualView));
2888 assert_eq!(
2889 NodeTypeTag::from_str("virtual-view"),
2890 Ok(NodeTypeTag::VirtualView)
2891 );
2892
2893 for (bare, prefixed, tag) in [
2894 ("before", "::before", NodeTypeTag::Before),
2895 ("after", "::after", NodeTypeTag::After),
2896 ("marker", "::marker", NodeTypeTag::Marker),
2897 ("placeholder", "::placeholder", NodeTypeTag::Placeholder),
2898 ] {
2899 assert_eq!(NodeTypeTag::from_str(bare), Ok(tag));
2900 assert_eq!(NodeTypeTag::from_str(prefixed), Ok(tag));
2901 assert_eq!(
2902 tag.to_string(),
2903 prefixed,
2904 "pseudo-elements must serialize in their `::` form"
2905 );
2906 }
2907 }
2908
2909 #[test]
2910 fn node_type_tag_from_str_rejects_hostile_input_without_panicking() {
2911 let long = "a".repeat(1_000_000);
2912 let nested = "<".repeat(10_000);
2913 let hostile = [
2914 "", " ", " \t\n\r ", " div", "div ", " div ", "div;garbage", "DIV", "Div",
2923 "0",
2924 "-0",
2925 "9223372036854775807", "1e400",
2927 "NaN",
2928 "inf",
2929 "-inf",
2930 "\u{1F600}", "e\u{0301}", "\u{0}", "\u{FEFF}div", "div\u{0}",
2935 "*",
2936 "::",
2937 ":::before",
2938 "<script>",
2939 long.as_str(),
2940 nested.as_str(),
2941 ];
2942 for input in hostile {
2943 match NodeTypeTag::from_str(input) {
2944 Ok(t) => panic!("{input:?} must not parse, but produced {t:?}"),
2945 Err(NodeTypeTagParseError::Invalid(echoed)) => {
2946 assert_eq!(echoed, input, "the error must echo the input verbatim");
2947 }
2948 }
2949 }
2950 }
2951
2952 #[test]
2953 fn node_type_tag_parse_error_display_names_the_offending_input() {
2954 let e = NodeTypeTagParseError::Invalid("wat");
2955 assert_eq!(format!("{e}"), "Invalid node type: wat");
2956 assert_eq!(format!("{}", NodeTypeTagParseError::Invalid("")), "Invalid node type: ");
2958 assert!(format!("{}", NodeTypeTagParseError::Invalid("😀")).contains('😀'));
2959 }
2960
2961 #[test]
2962 fn node_type_tag_parse_error_to_contained_to_shared_round_trips() {
2963 let long = "x".repeat(10_000);
2964 for s in ["", " ", "div", "😀", "e\u{0301}", "\u{0}", long.as_str()] {
2965 let shared = NodeTypeTagParseError::Invalid(s);
2966 let owned = shared.to_contained();
2967 assert_eq!(owned, NodeTypeTagParseErrorOwned::Invalid(s.to_string().into()));
2968 assert_eq!(
2969 owned.to_shared(),
2970 shared,
2971 "to_shared(to_contained(x)) must equal x"
2972 );
2973 assert_eq!(format!("{}", owned.to_shared()), format!("{shared}"));
2975 }
2976 }
2977
2978 #[test]
2983 fn css_path_new_preserves_selectors_including_empty() {
2984 assert!(CssPath::new(Vec::new()).selectors.as_ref().is_empty());
2985 assert_eq!(CssPath::new(Vec::new()), CssPath::default());
2986
2987 let sels = vec![
2988 CssPathSelector::Type(NodeTypeTag::Div),
2989 CssPathSelector::DirectChildren,
2990 CssPathSelector::Class("c".to_string().into()),
2991 ];
2992 let p = CssPath::new(sels.clone());
2993 assert_eq!(p.selectors.as_ref(), &sels[..]);
2994 }
2995
2996 #[test]
2997 fn css_path_display_and_debug_agree_and_compose() {
2998 let p = CssPath::new(vec![
2999 CssPathSelector::Type(NodeTypeTag::Div),
3000 CssPathSelector::Id("id".to_string().into()),
3001 CssPathSelector::Class("cls".to_string().into()),
3002 CssPathSelector::PseudoSelector(CssPathPseudoSelector::Hover),
3003 ]);
3004 assert_eq!(format!("{p}"), "div#id.cls:hover");
3005 assert_eq!(format!("{p:?}"), format!("{p}"), "Debug delegates to Display");
3006 assert_eq!(format!("{}", CssPath::default()), "");
3008 }
3009
3010 #[test]
3011 fn push_front_scope_scopes_a_bare_star_rule_to_the_node_only() {
3012 let mut p = CssPath::new(vec![CssPathSelector::Global]);
3014 p.push_front_scope(5, 9);
3015 assert_eq!(
3016 p.selectors.as_ref(),
3017 &[
3018 CssPathSelector::Root(CssScopeRange { start: 5, end: 5 }),
3019 CssPathSelector::Global,
3020 ][..],
3021 "inline style must not leak past the owner node (#47)"
3022 );
3023 }
3024
3025 #[test]
3026 fn push_front_scope_scopes_a_real_selector_to_the_whole_subtree() {
3027 let mut p = CssPath::new(vec![CssPathSelector::Class("menu-item".to_string().into())]);
3028 p.push_front_scope(5, 9);
3029 assert_eq!(
3030 p.selectors.as_ref()[0],
3031 CssPathSelector::Root(CssScopeRange { start: 5, end: 9 })
3032 );
3033 assert_eq!(p.selectors.as_ref().len(), 2);
3034 }
3035
3036 #[test]
3037 fn push_front_scope_on_an_empty_path_uses_the_subtree_range() {
3038 let mut p = CssPath::default();
3040 p.push_front_scope(2, 8);
3041 assert_eq!(
3042 p.selectors.as_ref(),
3043 &[CssPathSelector::Root(CssScopeRange { start: 2, end: 8 })][..]
3044 );
3045 }
3046
3047 #[test]
3048 fn push_front_scope_at_numeric_boundaries_does_not_panic() {
3049 for (start, end) in [
3052 (0_usize, 0_usize),
3053 (0, usize::MAX),
3054 (usize::MAX, usize::MAX),
3055 (usize::MAX, 0), (9, 5), ] {
3058 let mut p = CssPath::new(vec![CssPathSelector::Class("c".to_string().into())]);
3059 p.push_front_scope(start, end);
3060 assert_eq!(
3061 p.selectors.as_ref()[0],
3062 CssPathSelector::Root(CssScopeRange { start, end })
3063 );
3064 }
3065 let mut g = CssPath::new(vec![CssPathSelector::Global]);
3067 g.push_front_scope(usize::MAX, 0);
3068 assert_eq!(
3069 g.selectors.as_ref()[0],
3070 CssPathSelector::Root(CssScopeRange {
3071 start: usize::MAX,
3072 end: usize::MAX
3073 })
3074 );
3075 }
3076
3077 #[test]
3078 fn push_front_scope_applied_twice_stacks_root_selectors() {
3079 let mut p = CssPath::new(vec![CssPathSelector::Global]);
3080 p.push_front_scope(5, 9);
3081 p.push_front_scope(1, 20);
3084 assert_eq!(
3085 p.selectors.as_ref(),
3086 &[
3087 CssPathSelector::Root(CssScopeRange { start: 1, end: 20 }),
3088 CssPathSelector::Root(CssScopeRange { start: 5, end: 5 }),
3089 CssPathSelector::Global,
3090 ][..]
3091 );
3092 }
3093
3094 #[test]
3095 fn push_front_scope_on_a_long_path_preserves_order_and_length() {
3096 let sels: Vec<CssPathSelector> = (0..5_000)
3097 .map(|i| CssPathSelector::Class(format!("c{i}").into()))
3098 .collect();
3099 let mut p = CssPath::new(sels.clone());
3100 p.push_front_scope(3, 4);
3101 assert_eq!(p.selectors.as_ref().len(), 5_001);
3102 assert_eq!(
3103 p.selectors.as_ref()[0],
3104 CssPathSelector::Root(CssScopeRange { start: 3, end: 4 })
3105 );
3106 assert_eq!(&p.selectors.as_ref()[1..], &sels[..], "the tail must be untouched");
3107 }
3108
3109 #[test]
3110 fn scope_range_contains_at_zero_and_usize_max() {
3111 let zero = CssScopeRange { start: 0, end: 0 };
3112 assert!(zero.contains(0));
3113 assert!(!zero.contains(1));
3114 assert!(!zero.contains(usize::MAX));
3115
3116 let full = CssScopeRange {
3117 start: 0,
3118 end: usize::MAX,
3119 };
3120 assert!(full.contains(0));
3121 assert!(full.contains(usize::MAX));
3122 assert!(full.contains(usize::MAX / 2));
3123
3124 let top = CssScopeRange {
3125 start: usize::MAX,
3126 end: usize::MAX,
3127 };
3128 assert!(top.contains(usize::MAX));
3129 assert!(!top.contains(usize::MAX - 1));
3130 assert!(!top.contains(0));
3131 }
3132
3133 #[test]
3134 fn scope_range_inverted_contains_nothing() {
3135 let inverted = CssScopeRange { start: 9, end: 5 };
3137 for n in [0_usize, 4, 5, 7, 9, 10, usize::MAX] {
3138 assert!(!inverted.contains(n), "inverted range must never match {n}");
3139 }
3140 }
3141
3142 #[test]
3147 fn css_path_selector_display_covers_every_variant() {
3148 let cases: Vec<(CssPathSelector, String)> = vec![
3149 (CssPathSelector::Global, "*".to_string()),
3150 (
3151 CssPathSelector::Root(CssScopeRange { start: 2, end: 6 }),
3152 ":root(2..=6)".to_string(),
3153 ),
3154 (
3155 CssPathSelector::Type(NodeTypeTag::Div),
3156 "div".to_string(),
3157 ),
3158 (
3159 CssPathSelector::Class("c".to_string().into()),
3160 ".c".to_string(),
3161 ),
3162 (CssPathSelector::Id("i".to_string().into()), "#i".to_string()),
3163 (
3164 CssPathSelector::PseudoSelector(CssPathPseudoSelector::Focus),
3165 ":focus".to_string(),
3166 ),
3167 (
3168 CssPathSelector::Attribute(CssAttributeSelector::default()),
3169 "[]".to_string(),
3170 ),
3171 (CssPathSelector::DirectChildren, ">".to_string()),
3172 (CssPathSelector::Children, " ".to_string()),
3173 (CssPathSelector::AdjacentSibling, "+".to_string()),
3174 (CssPathSelector::GeneralSibling, "~".to_string()),
3175 ];
3176 for (sel, expected) in cases {
3177 assert_eq!(format!("{sel}"), expected);
3178 }
3179 assert_eq!(CssPathSelector::default(), CssPathSelector::Global);
3180 }
3181
3182 #[test]
3183 fn css_path_selector_display_at_scope_range_boundaries() {
3184 let s = CssPathSelector::Root(CssScopeRange {
3185 start: 0,
3186 end: usize::MAX,
3187 });
3188 assert_eq!(format!("{s}"), format!(":root(0..={})", usize::MAX));
3189 }
3190
3191 #[test]
3192 fn css_path_selector_display_with_empty_and_unicode_names() {
3193 assert_eq!(
3195 format!("{}", CssPathSelector::Class(String::new().into())),
3196 "."
3197 );
3198 assert_eq!(format!("{}", CssPathSelector::Id(String::new().into())), "#");
3199 assert_eq!(
3200 format!("{}", CssPathSelector::Class("😀".to_string().into())),
3201 ".😀"
3202 );
3203 let long = "x".repeat(100_000);
3204 assert_eq!(
3205 format!("{}", CssPathSelector::Id(long.clone().into())).len(),
3206 long.len() + 1
3207 );
3208 }
3209
3210 #[test]
3211 fn attribute_match_op_symbol_prefix_matrix() {
3212 assert_eq!(AttributeMatchOp::Exists.symbol_prefix(), "");
3213 assert_eq!(AttributeMatchOp::Eq.symbol_prefix(), "");
3214 assert_eq!(AttributeMatchOp::Includes.symbol_prefix(), "~");
3215 assert_eq!(AttributeMatchOp::DashMatch.symbol_prefix(), "|");
3216 assert_eq!(AttributeMatchOp::Prefix.symbol_prefix(), "^");
3217 assert_eq!(AttributeMatchOp::Suffix.symbol_prefix(), "$");
3218 assert_eq!(AttributeMatchOp::Substring.symbol_prefix(), "*");
3219 assert_eq!(AttributeMatchOp::default(), AttributeMatchOp::Exists);
3220 }
3221
3222 #[test]
3223 fn css_attribute_selector_display_renders_each_operator() {
3224 let ops = [
3225 (AttributeMatchOp::Eq, "[a=\"v\"]"),
3226 (AttributeMatchOp::Includes, "[a~=\"v\"]"),
3227 (AttributeMatchOp::DashMatch, "[a|=\"v\"]"),
3228 (AttributeMatchOp::Prefix, "[a^=\"v\"]"),
3229 (AttributeMatchOp::Suffix, "[a$=\"v\"]"),
3230 (AttributeMatchOp::Substring, "[a*=\"v\"]"),
3231 ];
3232 for (op, expected) in ops {
3233 let sel = CssAttributeSelector {
3234 name: "a".to_string().into(),
3235 op,
3236 value: OptionString::Some("v".to_string().into()),
3237 };
3238 assert_eq!(format!("{sel}"), expected);
3239 }
3240 }
3241
3242 #[test]
3243 fn css_attribute_selector_exists_ignores_any_value() {
3244 let sel = CssAttributeSelector {
3246 name: "a".to_string().into(),
3247 op: AttributeMatchOp::Exists,
3248 value: OptionString::Some("ignored".to_string().into()),
3249 };
3250 assert_eq!(format!("{sel}"), "[a]");
3251 assert_eq!(format!("{}", CssAttributeSelector::default()), "[]");
3252 }
3253
3254 #[test]
3255 fn css_attribute_selector_missing_value_renders_an_empty_string_literal() {
3256 let sel = CssAttributeSelector {
3257 name: "a".to_string().into(),
3258 op: AttributeMatchOp::Eq,
3259 value: OptionString::None,
3260 };
3261 assert_eq!(format!("{sel}"), "[a=\"\"]");
3262 }
3263
3264 #[test]
3265 fn css_attribute_selector_display_with_hostile_names_and_values_does_not_panic() {
3266 for (name, value) in [
3267 ("", ""),
3268 ("😀", "😀"),
3269 ("a\u{0301}", "e\u{0301}"),
3270 ("a", "has \" quote"), ("a", "]"),
3272 ("a", "\u{0}"),
3273 ("a", "\n"),
3274 ] {
3275 let sel = CssAttributeSelector {
3276 name: name.to_string().into(),
3277 op: AttributeMatchOp::Eq,
3278 value: OptionString::Some(value.to_string().into()),
3279 };
3280 let out = format!("{sel}");
3281 assert!(out.starts_with('[') && out.ends_with(']'));
3282 }
3283 }
3284
3285 #[test]
3286 fn css_nth_child_selector_display_at_numeric_boundaries() {
3287 assert_eq!(format!("{}", CssNthChildSelector::Number(0)), "0");
3288 assert_eq!(
3289 format!("{}", CssNthChildSelector::Number(u32::MAX)),
3290 u32::MAX.to_string()
3291 );
3292 assert_eq!(format!("{}", CssNthChildSelector::Even), "even");
3293 assert_eq!(format!("{}", CssNthChildSelector::Odd), "odd");
3294 assert_eq!(
3295 format!(
3296 "{}",
3297 CssNthChildSelector::Pattern(CssNthChildPattern {
3298 pattern_repeat: 2,
3299 offset: 1,
3300 })
3301 ),
3302 "2n + 1"
3303 );
3304 assert_eq!(
3306 format!(
3307 "{}",
3308 CssNthChildSelector::Pattern(CssNthChildPattern {
3309 pattern_repeat: 0,
3310 offset: 0,
3311 })
3312 ),
3313 "0n + 0"
3314 );
3315 let maxed = CssNthChildSelector::Pattern(CssNthChildPattern {
3316 pattern_repeat: u32::MAX,
3317 offset: u32::MAX,
3318 });
3319 assert_eq!(
3320 format!("{maxed}"),
3321 format!("{}n + {}", u32::MAX, u32::MAX)
3322 );
3323 }
3324
3325 #[test]
3326 fn css_path_pseudo_selector_display_covers_every_variant() {
3327 let cases: Vec<(CssPathPseudoSelector, String)> = vec![
3328 (CssPathPseudoSelector::First, "first".to_string()),
3329 (CssPathPseudoSelector::Last, "last".to_string()),
3330 (
3331 CssPathPseudoSelector::NthChild(CssNthChildSelector::Even),
3332 "nth-child(even)".to_string(),
3333 ),
3334 (CssPathPseudoSelector::Hover, "hover".to_string()),
3335 (CssPathPseudoSelector::Active, "active".to_string()),
3336 (CssPathPseudoSelector::Focus, "focus".to_string()),
3337 (
3338 CssPathPseudoSelector::Lang("de-DE".to_string().into()),
3339 "lang(de-DE)".to_string(),
3340 ),
3341 (CssPathPseudoSelector::Backdrop, "backdrop".to_string()),
3342 (CssPathPseudoSelector::Dragging, "dragging".to_string()),
3343 (CssPathPseudoSelector::DragOver, "drag-over".to_string()),
3344 ];
3345 for (p, expected) in cases {
3346 assert_eq!(format!("{p}"), expected);
3347 }
3348 }
3349
3350 #[test]
3351 fn css_path_pseudo_selector_lang_with_hostile_payloads_does_not_panic() {
3352 let long = "l".repeat(100_000);
3353 for lang in ["", " ", "😀", "e\u{0301}", ")", "\u{0}", long.as_str()] {
3354 let p = CssPathPseudoSelector::Lang(lang.to_string().into());
3355 let out = format!("{p}");
3356 assert!(out.starts_with("lang(") && out.ends_with(')'));
3357 }
3358 }
3359
3360 #[test]
3365 fn get_specificity_of_an_empty_path_is_all_zero() {
3366 assert_eq!(get_specificity(&CssPath::default()), (0, 0, 0, 0));
3367 assert_eq!(get_specificity(&CssPath::new(Vec::new())), (0, 0, 0, 0));
3368 }
3369
3370 #[test]
3371 fn get_specificity_counts_ids_classes_and_types_separately() {
3372 let path = CssPath::new(vec![
3373 CssPathSelector::Id("a".to_string().into()),
3374 CssPathSelector::Id("b".to_string().into()),
3375 CssPathSelector::Class("c".to_string().into()),
3376 CssPathSelector::PseudoSelector(CssPathPseudoSelector::Hover),
3377 CssPathSelector::Attribute(CssAttributeSelector::default()),
3378 CssPathSelector::Type(NodeTypeTag::Div),
3379 ]);
3380 assert_eq!(get_specificity(&path), (2, 3, 1, 6));
3382 }
3383
3384 #[test]
3385 fn get_specificity_ignores_combinators_and_root_except_in_the_total() {
3386 let path = CssPath::new(vec![
3387 CssPathSelector::Root(CssScopeRange { start: 0, end: 9 }),
3388 CssPathSelector::Global,
3389 CssPathSelector::Children,
3390 CssPathSelector::DirectChildren,
3391 CssPathSelector::AdjacentSibling,
3392 CssPathSelector::GeneralSibling,
3393 ]);
3394 let (ids, classes, types, total) = get_specificity(&path);
3395 assert_eq!((ids, classes, types), (0, 0, 0));
3396 assert_eq!(total, 6, "the 4th field is the raw selector count");
3397 }
3398
3399 #[test]
3400 fn get_specificity_orders_ids_above_classes_above_types() {
3401 let id = get_specificity(&CssPath::new(vec![CssPathSelector::Id("x".to_string().into())]));
3402 let class = get_specificity(&CssPath::new(vec![CssPathSelector::Class(
3403 "x".to_string().into(),
3404 )]));
3405 let ty = get_specificity(&CssPath::new(vec![CssPathSelector::Type(NodeTypeTag::Div)]));
3406 let star = get_specificity(&CssPath::new(vec![CssPathSelector::Global]));
3407 assert!(star < ty, "* must be the weakest");
3408 assert!(ty < class);
3409 assert!(class < id);
3410 }
3411
3412 #[test]
3413 fn get_specificity_on_a_huge_path_does_not_overflow_or_hang() {
3414 let sels: Vec<CssPathSelector> = (0..50_000)
3415 .map(|i| CssPathSelector::Id(format!("i{i}").into()))
3416 .collect();
3417 let path = CssPath::new(sels);
3418 assert_eq!(get_specificity(&path), (50_000, 0, 0, 50_000));
3419 }
3420
3421 #[cfg(feature = "parser")]
3426 fn parse(s: &str) -> Css {
3427 Css::from_string(s.to_string().into())
3428 }
3429
3430 #[cfg(feature = "parser")]
3431 #[test]
3432 fn from_string_on_empty_and_whitespace_only_input_yields_no_rules() {
3433 for input in ["", " ", " ", "\t\n\r\n ", "\u{FEFF}", "\u{00A0}"] {
3434 let css = parse(input);
3435 assert!(
3436 css.is_empty(),
3437 "{input:?} must produce zero rules, got {}",
3438 css.rules.as_ref().len()
3439 );
3440 }
3441 }
3442
3443 #[cfg(feature = "parser")]
3444 #[test]
3445 fn from_string_on_garbage_never_panics_and_is_deterministic() {
3446 let garbage = [
3447 "}}}}",
3448 "{{{{",
3449 "@@@@",
3450 ";;;;",
3451 "\u{0}\u{1}\u{2}",
3452 "div {",
3453 "div }",
3454 "} div {",
3455 "div { color",
3456 "div { color: }",
3457 "div { : red; }",
3458 "* * * * *",
3459 ":::::",
3460 "[[[[",
3461 "/* unterminated comment",
3462 "@media {",
3463 "url(",
3464 "\"unterminated",
3465 "div{color:red;}}}}",
3466 ];
3467 for input in garbage {
3468 let a = parse(input);
3469 let b = parse(input);
3470 assert_eq!(a, b, "parsing {input:?} must be deterministic");
3471 }
3472 }
3473
3474 #[cfg(feature = "parser")]
3475 #[test]
3476 fn from_string_valid_minimal_produces_one_author_rule() {
3477 let css = parse("div { width: 200px; }");
3478 assert_eq!(css.rules.as_ref().len(), 1);
3479 let rule = &css.rules.as_ref()[0];
3480 assert_eq!(
3481 rule.priority,
3482 rule_priority::AUTHOR,
3483 "parser output belongs to the author layer"
3484 );
3485 assert_eq!(
3486 rule.path.selectors.as_ref(),
3487 &[CssPathSelector::Type(NodeTypeTag::Div)][..]
3488 );
3489 let props: Vec<CssPropertyType> = css
3490 .iter_inline_properties()
3491 .map(|(p, _)| p.get_type())
3492 .collect();
3493 assert_eq!(props, vec![CssPropertyType::Width]);
3494 }
3495
3496 #[cfg(feature = "parser")]
3497 #[test]
3498 fn from_string_handles_leading_and_trailing_junk_deterministically() {
3499 assert_eq!(parse(" div { width: 1px; } "), parse("div { width: 1px; }"));
3501 let with_junk = parse("div { width: 1px; } @@@ garbage");
3503 assert!(
3504 with_junk.rules().any(|r| r.path.selectors.as_ref()
3505 == &[CssPathSelector::Type(NodeTypeTag::Div)][..]),
3506 "the leading valid rule must survive trailing junk"
3507 );
3508 }
3509
3510 #[cfg(feature = "parser")]
3511 #[test]
3512 fn from_string_on_boundary_numbers_does_not_panic() {
3513 let inputs = [
3514 "div { width: 0px; }",
3515 "div { width: -0px; }",
3516 "div { width: -1px; }",
3517 "div { width: 9223372036854775807px; }", "div { width: 340282350000000000000000000000000000000px; }", "div { width: 1e400px; }", "div { width: 1e-400px; }", "div { width: NaN; }",
3522 "div { width: inf; }",
3523 "div { width: -inf; }",
3524 "div { width: 99999999999999999999999999px; }",
3525 "div { opacity: 1e309; }",
3526 "div { z-index: -9223372036854775808; }", "div { width: .....; }",
3528 "div { width: --5px; }",
3529 ];
3530 for input in inputs {
3531 let css = parse(input);
3532 assert!(
3533 css.rules.as_ref().len() <= 1,
3534 "{input:?} must not explode into multiple rules"
3535 );
3536 }
3537 }
3538
3539 #[cfg(feature = "parser")]
3540 #[test]
3541 fn from_string_on_unicode_input_does_not_panic() {
3542 let inputs = [
3543 "div { content: \"😀\"; }",
3544 ".😀 { width: 1px; }",
3545 "#e\u{0301} { width: 1px; }",
3546 "div { font-family: \"日本語\"; }",
3547 "div\u{0301} { width: 1px; }",
3548 "div { width: 1px; } /* 🎉 */",
3549 "\u{202E}div { width: 1px; }", ];
3551 for input in inputs {
3552 let css = parse(input);
3553 assert_eq!(css, parse(input));
3555 }
3556 }
3557
3558 #[cfg(feature = "parser")]
3559 #[test]
3560 fn from_string_on_a_one_megabyte_input_terminates() {
3561 let body = "width:1px;".repeat(100_000);
3563 assert!(body.len() >= 1_000_000);
3564 let css = parse(&format!("div{{{body}}}"));
3565 assert_eq!(css.rules.as_ref().len(), 1);
3566 assert!(!css.rules.as_ref()[0].declarations.as_ref().is_empty());
3567
3568 let junk = "a".repeat(100_000);
3570 let _ = parse(&junk);
3571 }
3572
3573 #[cfg(feature = "parser")]
3574 #[test]
3575 fn from_string_on_deeply_nested_blocks_does_not_stack_overflow() {
3576 let depth = 10_000;
3579 let mut s = String::with_capacity(depth * 8);
3580 for _ in 0..depth {
3581 s.push_str("div{");
3582 }
3583 s.push_str("width:1px;");
3584 for _ in 0..depth {
3585 s.push('}');
3586 }
3587
3588 let handle = std::thread::Builder::new()
3589 .stack_size(64 * 1024 * 1024)
3590 .spawn(move || Css::from_string(s.into()).rules.as_ref().len())
3591 .expect("spawning the parser thread must succeed");
3592
3593 let rule_count = handle
3594 .join()
3595 .expect("10_000 nested blocks must not panic or overflow the stack");
3596 assert!(rule_count <= depth + 1, "rule count must stay bounded by the nesting depth");
3597 }
3598
3599 #[cfg(feature = "parser")]
3600 #[test]
3601 fn from_string_with_warnings_agrees_with_from_string() {
3602 for input in [
3603 "",
3604 " ",
3605 "div { width: 1px; }",
3606 "div { not-a-property: 1; }",
3607 "}}} garbage {{{",
3608 "div { width: NaN; }",
3609 ] {
3610 let (css, _warnings) = Css::from_string_with_warnings(input.to_string().into());
3611 assert_eq!(
3612 css,
3613 parse(input),
3614 "from_string_with_warnings must parse {input:?} identically to from_string"
3615 );
3616 }
3617 }
3618
3619 #[cfg(feature = "parser")]
3620 #[test]
3621 fn from_string_with_warnings_reports_an_unknown_property() {
3622 let (css, warnings) =
3623 Css::from_string_with_warnings("div { definitely-not-a-property: 1px; }".to_string().into());
3624 assert!(
3625 !warnings.is_empty(),
3626 "an unknown property must surface as a warning rather than being dropped silently"
3627 );
3628 assert!(css.rules.as_ref().len() <= 1);
3631 }
3632
3633 #[cfg(feature = "parser")]
3634 #[test]
3635 fn from_string_with_warnings_on_empty_input_has_no_rules() {
3636 let (css, warnings) = Css::from_string_with_warnings(String::new().into());
3637 assert!(css.is_empty());
3638 assert!(warnings.is_empty());
3639 }
3640
3641 #[cfg(feature = "parser")]
3642 #[test]
3643 fn parse_inline_marks_every_rule_as_the_inline_layer() {
3644 let css = Css::parse_inline("width: 200px; color: red;");
3645 assert!(!css.is_empty());
3646 for r in css.rules() {
3647 assert_eq!(
3648 r.priority,
3649 rule_priority::INLINE,
3650 "parse_inline must stamp every rule with the INLINE layer"
3651 );
3652 }
3653 let props: Vec<CssPropertyType> = css
3654 .iter_inline_properties()
3655 .map(|(p, _)| p.get_type())
3656 .collect();
3657 assert!(props.contains(&CssPropertyType::Width));
3658 assert!(props.contains(&CssPropertyType::TextColor));
3659 }
3660
3661 #[cfg(feature = "parser")]
3662 #[test]
3663 fn parse_inline_wraps_bare_declarations_in_a_star_rule() {
3664 let css = Css::parse_inline("width: 200px;");
3665 assert_eq!(css.rules.as_ref().len(), 1);
3666 assert_eq!(
3667 css.rules.as_ref()[0].path.selectors.as_ref(),
3668 &[CssPathSelector::Global][..],
3669 "the wrapper path must be exactly `*` — push_front_scope keys node-only \
3670 inline semantics off that shape"
3671 );
3672 }
3673
3674 #[cfg(feature = "parser")]
3675 #[test]
3676 fn parse_inline_on_empty_and_whitespace_input_does_not_panic() {
3677 for input in ["", " ", "\t\n", " \r\n "] {
3678 let css = Css::parse_inline(input);
3679 for r in css.rules() {
3680 assert_eq!(r.priority, rule_priority::INLINE);
3681 assert!(
3682 r.declarations.as_ref().is_empty(),
3683 "an empty inline style must not produce declarations"
3684 );
3685 }
3686 }
3687 }
3688
3689 #[cfg(feature = "parser")]
3690 #[test]
3691 fn parse_inline_on_garbage_never_panics_and_is_deterministic() {
3692 for input in [
3693 "}}}}",
3694 "{{{{",
3695 ";;;;",
3696 ":::",
3697 "color",
3698 "color:",
3699 ": red",
3700 "\u{0}\u{1}",
3701 "/* unterminated",
3702 "@@@",
3703 "width: 1px", ] {
3705 assert_eq!(
3706 Css::parse_inline(input),
3707 Css::parse_inline(input),
3708 "parse_inline({input:?}) must be deterministic"
3709 );
3710 }
3711 }
3712
3713 #[cfg(feature = "parser")]
3714 #[test]
3715 fn parse_inline_on_unicode_and_boundary_numbers_does_not_panic() {
3716 for input in [
3717 "content: \"😀\"",
3718 "font-family: \"日本語\"",
3719 "width: 0px",
3720 "width: -0px",
3721 "width: 9223372036854775807px",
3722 "width: 1e400px",
3723 "width: NaN",
3724 "width: inf",
3725 "opacity: 1e309",
3726 ] {
3727 let css = Css::parse_inline(input);
3728 for r in css.rules() {
3729 assert_eq!(r.priority, rule_priority::INLINE);
3730 }
3731 }
3732 }
3733
3734 #[cfg(feature = "parser")]
3735 #[test]
3736 fn parse_inline_on_a_one_megabyte_style_terminates() {
3737 let style = "width:1px;".repeat(100_000);
3738 assert!(style.len() >= 1_000_000);
3739 let css = Css::parse_inline(&style);
3740 assert!(!css.is_empty());
3741 for r in css.rules() {
3742 assert_eq!(r.priority, rule_priority::INLINE);
3743 }
3744 }
3745
3746 #[cfg(feature = "parser")]
3747 #[test]
3748 fn parse_inline_supports_nested_pseudo_blocks() {
3749 let css = Css::parse_inline(":hover { color: red; }");
3751 assert!(!css.is_empty(), "a nested pseudo block must produce a rule");
3752 for r in css.rules() {
3753 assert_eq!(r.priority, rule_priority::INLINE);
3754 }
3755 }
3756
3757 #[cfg(feature = "parser")]
3758 #[test]
3759 fn parse_inline_must_not_let_a_brace_escape_the_star_wrapper() {
3760 let css = Css::parse_inline("color: red; } div { background: green;");
3766 for r in css.rules() {
3767 let first = r.path.selectors.as_ref().first();
3768 assert!(
3769 matches!(first, None | Some(CssPathSelector::Global)),
3770 "a `}}` in the inline style escaped the `*` wrapper and produced the \
3771 free-standing rule `{}` (selector injection)",
3772 r.path
3773 );
3774 }
3775 }
3776}