1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4 
5 use crate::parser::SelectorImpl;
6 use cssparser::ToCss;
7 use std::fmt;
8 
9 #[derive(Clone, Eq, PartialEq, ToShmem)]
10 #[shmem(no_bounds)]
11 pub struct AttrSelectorWithOptionalNamespace<Impl: SelectorImpl> {
12     #[shmem(field_bound)]
13     pub namespace: Option<NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>>,
14     #[shmem(field_bound)]
15     pub local_name: Impl::LocalName,
16     pub local_name_lower: Impl::LocalName,
17     #[shmem(field_bound)]
18     pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
19     pub never_matches: bool,
20 }
21 
22 impl<Impl: SelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>>23     pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> {
24         self.namespace.as_ref().map(|ns| match ns {
25             NamespaceConstraint::Any => NamespaceConstraint::Any,
26             NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url),
27         })
28     }
29 }
30 
31 #[derive(Clone, Eq, PartialEq, ToShmem)]
32 pub enum NamespaceConstraint<NamespaceUrl> {
33     Any,
34 
35     /// Empty string for no namespace
36     Specific(NamespaceUrl),
37 }
38 
39 #[derive(Clone, Eq, PartialEq, ToShmem)]
40 pub enum ParsedAttrSelectorOperation<AttrValue> {
41     Exists,
42     WithValue {
43         operator: AttrSelectorOperator,
44         case_sensitivity: ParsedCaseSensitivity,
45         expected_value: AttrValue,
46     },
47 }
48 
49 #[derive(Clone, Eq, PartialEq)]
50 pub enum AttrSelectorOperation<AttrValue> {
51     Exists,
52     WithValue {
53         operator: AttrSelectorOperator,
54         case_sensitivity: CaseSensitivity,
55         expected_value: AttrValue,
56     },
57 }
58 
59 impl<AttrValue> AttrSelectorOperation<AttrValue> {
eval_str(&self, element_attr_value: &str) -> bool where AttrValue: AsRef<str>,60     pub fn eval_str(&self, element_attr_value: &str) -> bool
61     where
62         AttrValue: AsRef<str>,
63     {
64         match *self {
65             AttrSelectorOperation::Exists => true,
66             AttrSelectorOperation::WithValue {
67                 operator,
68                 case_sensitivity,
69                 ref expected_value,
70             } => operator.eval_str(
71                 element_attr_value,
72                 expected_value.as_ref(),
73                 case_sensitivity,
74             ),
75         }
76     }
77 }
78 
79 #[derive(Clone, Copy, Eq, PartialEq, ToShmem)]
80 pub enum AttrSelectorOperator {
81     Equal,
82     Includes,
83     DashMatch,
84     Prefix,
85     Substring,
86     Suffix,
87 }
88 
89 impl ToCss for AttrSelectorOperator {
to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write,90     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
91     where
92         W: fmt::Write,
93     {
94         // https://drafts.csswg.org/cssom/#serializing-selectors
95         // See "attribute selector".
96         dest.write_str(match *self {
97             AttrSelectorOperator::Equal => "=",
98             AttrSelectorOperator::Includes => "~=",
99             AttrSelectorOperator::DashMatch => "|=",
100             AttrSelectorOperator::Prefix => "^=",
101             AttrSelectorOperator::Substring => "*=",
102             AttrSelectorOperator::Suffix => "$=",
103         })
104     }
105 }
106 
107 impl AttrSelectorOperator {
eval_str( self, element_attr_value: &str, attr_selector_value: &str, case_sensitivity: CaseSensitivity, ) -> bool108     pub fn eval_str(
109         self,
110         element_attr_value: &str,
111         attr_selector_value: &str,
112         case_sensitivity: CaseSensitivity,
113     ) -> bool {
114         let e = element_attr_value.as_bytes();
115         let s = attr_selector_value.as_bytes();
116         let case = case_sensitivity;
117         match self {
118             AttrSelectorOperator::Equal => case.eq(e, s),
119             AttrSelectorOperator::Prefix => e.len() >= s.len() && case.eq(&e[..s.len()], s),
120             AttrSelectorOperator::Suffix => {
121                 e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s)
122             },
123             AttrSelectorOperator::Substring => {
124                 case.contains(element_attr_value, attr_selector_value)
125             },
126             AttrSelectorOperator::Includes => element_attr_value
127                 .split(SELECTOR_WHITESPACE)
128                 .any(|part| case.eq(part.as_bytes(), s)),
129             AttrSelectorOperator::DashMatch => {
130                 case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s))
131             },
132         }
133     }
134 }
135 
136 /// The definition of whitespace per CSS Selectors Level 3 § 4.
137 pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
138 
139 #[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)]
140 pub enum ParsedCaseSensitivity {
141     // 's' was specified.
142     ExplicitCaseSensitive,
143     // 'i' was specified.
144     AsciiCaseInsensitive,
145     // No flags were specified and HTML says this is a case-sensitive attribute.
146     CaseSensitive,
147     // No flags were specified and HTML says this is a case-insensitive attribute.
148     AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument,
149 }
150 
151 impl ParsedCaseSensitivity {
to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity152     pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity {
153         match self {
154             ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
155                 if is_html_element_in_html_document =>
156             {
157                 CaseSensitivity::AsciiCaseInsensitive
158             },
159             ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {
160                 CaseSensitivity::CaseSensitive
161             },
162             ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => {
163                 CaseSensitivity::CaseSensitive
164             },
165             ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive,
166         }
167     }
168 }
169 
170 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
171 pub enum CaseSensitivity {
172     CaseSensitive,
173     AsciiCaseInsensitive,
174 }
175 
176 impl CaseSensitivity {
eq(self, a: &[u8], b: &[u8]) -> bool177     pub fn eq(self, a: &[u8], b: &[u8]) -> bool {
178         match self {
179             CaseSensitivity::CaseSensitive => a == b,
180             CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b),
181         }
182     }
183 
contains(self, haystack: &str, needle: &str) -> bool184     pub fn contains(self, haystack: &str, needle: &str) -> bool {
185         match self {
186             CaseSensitivity::CaseSensitive => haystack.contains(needle),
187             CaseSensitivity::AsciiCaseInsensitive => {
188                 if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() {
189                     haystack.bytes().enumerate().any(|(i, byte)| {
190                         if !byte.eq_ignore_ascii_case(&n_first_byte) {
191                             return false;
192                         }
193                         let after_this_byte = &haystack.as_bytes()[i + 1..];
194                         match after_this_byte.get(..n_rest.len()) {
195                             None => false,
196                             Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest),
197                         }
198                     })
199                 } else {
200                     // any_str.contains("") == true,
201                     // though these cases should be handled with *NeverMatches and never go here.
202                     true
203                 }
204             },
205         }
206     }
207 }
208