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