attr.rs (6686B)
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 #[cfg(feature = "to_shmem")] 10 use 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))] 15 pub 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 25 impl<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))] 36 pub enum NamespaceConstraint<NamespaceUrl> { 37 Any, 38 39 /// Empty string for no namespace 40 Specific(NamespaceUrl), 41 } 42 43 #[derive(Clone, Eq, PartialEq)] 44 #[cfg_attr(feature = "to_shmem", derive(ToShmem))] 45 pub 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)] 55 pub enum AttrSelectorOperation<AttrValue> { 56 Exists, 57 WithValue { 58 operator: AttrSelectorOperator, 59 case_sensitivity: CaseSensitivity, 60 value: AttrValue, 61 }, 62 } 63 64 impl<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))] 82 pub enum AttrSelectorOperator { 83 Equal, 84 Includes, 85 DashMatch, 86 Prefix, 87 Substring, 88 Suffix, 89 } 90 91 impl ToCss for AttrSelectorOperator { 92 fn to_css<W>(&self, dest: &mut W) -> fmt::Result 93 where 94 W: fmt::Write, 95 { 96 // https://drafts.csswg.org/cssom/#serializing-selectors 97 // See "attribute selector". 98 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 109 impl 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 143 /// The definition of whitespace per CSS Selectors Level 3 ยง 4. 144 pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C']; 145 146 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 147 #[cfg_attr(feature = "to_shmem", derive(ToShmem))] 148 pub enum ParsedCaseSensitivity { 149 /// 's' was specified. 150 ExplicitCaseSensitive, 151 /// 'i' was specified. 152 AsciiCaseInsensitive, 153 /// No flags were specified and HTML says this is a case-sensitive attribute. 154 CaseSensitive, 155 /// No flags were specified and HTML says this is a case-insensitive attribute. 156 AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, 157 } 158 159 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 160 pub enum CaseSensitivity { 161 CaseSensitive, 162 AsciiCaseInsensitive, 163 } 164 165 impl 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 // any_str.contains("") == true, 190 // though these cases should be handled with *NeverMatches and never go here. 191 true 192 } 193 }, 194 } 195 } 196 }