1use crate::parser::SelectorImpl;
6use cssparser::ToCss;
7use std::fmt;
8
9#[cfg(feature = "to_shmem")]
10use to_shmem_derive::ToShmem;
11
12#[derive(Clone, Eq, PartialEq)]
13#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
14#[cfg_attr(feature = "to_shmem", shmem(no_bounds))]
15pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> {
16 #[cfg_attr(feature = "to_shmem", shmem(field_bound))]
17 pub namespace: Option<NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>>,
18 #[cfg_attr(feature = "to_shmem", shmem(field_bound))]
19 pub local_name: Impl::LocalName,
20 pub local_name_lower: Impl::LocalName,
21 #[cfg_attr(feature = "to_shmem", shmem(field_bound))]
22 pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
23}
24
25impl<Impl: SelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
26 pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> {
27 self.namespace.as_ref().map(|ns| match ns {
28 NamespaceConstraint::Any => NamespaceConstraint::Any,
29 NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url),
30 })
31 }
32}
33
34#[derive(Clone, Eq, PartialEq)]
35#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
36pub enum NamespaceConstraint<NamespaceUrl> {
37 Any,
38
39 Specific(NamespaceUrl),
41}
42
43#[derive(Clone, Eq, PartialEq)]
44#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
45pub enum ParsedAttrSelectorOperation<AttrValue> {
46 Exists,
47 WithValue {
48 operator: AttrSelectorOperator,
49 case_sensitivity: ParsedCaseSensitivity,
50 value: AttrValue,
51 },
52}
53
54#[derive(Clone, Eq, PartialEq)]
55pub enum AttrSelectorOperation<AttrValue> {
56 Exists,
57 WithValue {
58 operator: AttrSelectorOperator,
59 case_sensitivity: CaseSensitivity,
60 value: AttrValue,
61 },
62}
63
64impl<AttrValue> AttrSelectorOperation<AttrValue> {
65 pub fn eval_str(&self, element_attr_value: &str) -> bool
66 where
67 AttrValue: AsRef<str>,
68 {
69 match *self {
70 AttrSelectorOperation::Exists => true,
71 AttrSelectorOperation::WithValue {
72 operator,
73 case_sensitivity,
74 ref value,
75 } => operator.eval_str(element_attr_value, value.as_ref(), case_sensitivity),
76 }
77 }
78}
79
80#[derive(Clone, Copy, Eq, PartialEq)]
81#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
82pub enum AttrSelectorOperator {
83 Equal,
84 Includes,
85 DashMatch,
86 Prefix,
87 Substring,
88 Suffix,
89}
90
91impl ToCss for AttrSelectorOperator {
92 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
93 where
94 W: fmt::Write,
95 {
96 dest.write_str(match *self {
99 AttrSelectorOperator::Equal => "=",
100 AttrSelectorOperator::Includes => "~=",
101 AttrSelectorOperator::DashMatch => "|=",
102 AttrSelectorOperator::Prefix => "^=",
103 AttrSelectorOperator::Substring => "*=",
104 AttrSelectorOperator::Suffix => "$=",
105 })
106 }
107}
108
109impl AttrSelectorOperator {
110 pub fn eval_str(
111 self,
112 element_attr_value: &str,
113 attr_selector_value: &str,
114 case_sensitivity: CaseSensitivity,
115 ) -> bool {
116 let e = element_attr_value.as_bytes();
117 let s = attr_selector_value.as_bytes();
118 let case = case_sensitivity;
119 match self {
120 AttrSelectorOperator::Equal => case.eq(e, s),
121 AttrSelectorOperator::Prefix => {
122 !s.is_empty() && e.len() >= s.len() && case.eq(&e[..s.len()], s)
123 },
124 AttrSelectorOperator::Suffix => {
125 !s.is_empty() && e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s)
126 },
127 AttrSelectorOperator::Substring => {
128 !s.is_empty() && case.contains(element_attr_value, attr_selector_value)
129 },
130 AttrSelectorOperator::Includes => {
131 !s.is_empty()
132 && element_attr_value
133 .split(SELECTOR_WHITESPACE)
134 .any(|part| case.eq(part.as_bytes(), s))
135 },
136 AttrSelectorOperator::DashMatch => {
137 case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s))
138 },
139 }
140 }
141}
142
143pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
145
146#[derive(Clone, Copy, Debug, Eq, PartialEq)]
147#[cfg_attr(feature = "to_shmem", derive(ToShmem))]
148pub enum ParsedCaseSensitivity {
149 ExplicitCaseSensitive,
151 AsciiCaseInsensitive,
153 CaseSensitive,
155 AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
157}
158
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum CaseSensitivity {
161 CaseSensitive,
162 AsciiCaseInsensitive,
163}
164
165impl CaseSensitivity {
166 pub fn eq(self, a: &[u8], b: &[u8]) -> bool {
167 match self {
168 CaseSensitivity::CaseSensitive => a == b,
169 CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b),
170 }
171 }
172
173 pub fn contains(self, haystack: &str, needle: &str) -> bool {
174 match self {
175 CaseSensitivity::CaseSensitive => haystack.contains(needle),
176 CaseSensitivity::AsciiCaseInsensitive => {
177 if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() {
178 haystack.bytes().enumerate().any(|(i, byte)| {
179 if !byte.eq_ignore_ascii_case(&n_first_byte) {
180 return false;
181 }
182 let after_this_byte = &haystack.as_bytes()[i + 1..];
183 match after_this_byte.get(..n_rest.len()) {
184 None => false,
185 Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest),
186 }
187 })
188 } else {
189 true
192 }
193 },
194 }
195 }
196}