From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- servo/components/selectors/attr.rs | 183 +++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 servo/components/selectors/attr.rs (limited to 'servo/components/selectors/attr.rs') diff --git a/servo/components/selectors/attr.rs b/servo/components/selectors/attr.rs new file mode 100644 index 0000000000..fee2962237 --- /dev/null +++ b/servo/components/selectors/attr.rs @@ -0,0 +1,183 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::parser::SelectorImpl; +use cssparser::ToCss; +use std::fmt; + +#[derive(Clone, Eq, PartialEq, ToShmem)] +#[shmem(no_bounds)] +pub struct AttrSelectorWithOptionalNamespace { + #[shmem(field_bound)] + pub namespace: Option>, + #[shmem(field_bound)] + pub local_name: Impl::LocalName, + pub local_name_lower: Impl::LocalName, + #[shmem(field_bound)] + pub operation: ParsedAttrSelectorOperation, +} + +impl AttrSelectorWithOptionalNamespace { + pub fn namespace(&self) -> Option> { + self.namespace.as_ref().map(|ns| match ns { + NamespaceConstraint::Any => NamespaceConstraint::Any, + NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url), + }) + } +} + +#[derive(Clone, Eq, PartialEq, ToShmem)] +pub enum NamespaceConstraint { + Any, + + /// Empty string for no namespace + Specific(NamespaceUrl), +} + +#[derive(Clone, Eq, PartialEq, ToShmem)] +pub enum ParsedAttrSelectorOperation { + Exists, + WithValue { + operator: AttrSelectorOperator, + case_sensitivity: ParsedCaseSensitivity, + value: AttrValue, + }, +} + +#[derive(Clone, Eq, PartialEq)] +pub enum AttrSelectorOperation { + Exists, + WithValue { + operator: AttrSelectorOperator, + case_sensitivity: CaseSensitivity, + value: AttrValue, + }, +} + +impl AttrSelectorOperation { + pub fn eval_str(&self, element_attr_value: &str) -> bool + where + AttrValue: AsRef, + { + match *self { + AttrSelectorOperation::Exists => true, + AttrSelectorOperation::WithValue { + operator, + case_sensitivity, + ref value, + } => operator.eval_str(element_attr_value, value.as_ref(), case_sensitivity), + } + } +} + +#[derive(Clone, Copy, Eq, PartialEq, ToShmem)] +pub enum AttrSelectorOperator { + Equal, + Includes, + DashMatch, + Prefix, + Substring, + Suffix, +} + +impl ToCss for AttrSelectorOperator { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + // https://drafts.csswg.org/cssom/#serializing-selectors + // See "attribute selector". + dest.write_str(match *self { + AttrSelectorOperator::Equal => "=", + AttrSelectorOperator::Includes => "~=", + AttrSelectorOperator::DashMatch => "|=", + AttrSelectorOperator::Prefix => "^=", + AttrSelectorOperator::Substring => "*=", + AttrSelectorOperator::Suffix => "$=", + }) + } +} + +impl AttrSelectorOperator { + pub fn eval_str( + self, + element_attr_value: &str, + attr_selector_value: &str, + case_sensitivity: CaseSensitivity, + ) -> bool { + let e = element_attr_value.as_bytes(); + let s = attr_selector_value.as_bytes(); + let case = case_sensitivity; + match self { + AttrSelectorOperator::Equal => case.eq(e, s), + AttrSelectorOperator::Prefix => e.len() >= s.len() && case.eq(&e[..s.len()], s), + AttrSelectorOperator::Suffix => { + e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) + }, + AttrSelectorOperator::Substring => { + case.contains(element_attr_value, attr_selector_value) + }, + AttrSelectorOperator::Includes => element_attr_value + .split(SELECTOR_WHITESPACE) + .any(|part| case.eq(part.as_bytes(), s)), + AttrSelectorOperator::DashMatch => { + case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s)) + }, + } + } +} + +/// The definition of whitespace per CSS Selectors Level 3 ยง 4. +pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C']; + +#[derive(Clone, Copy, Debug, Eq, PartialEq, ToShmem)] +pub enum ParsedCaseSensitivity { + /// 's' was specified. + ExplicitCaseSensitive, + /// 'i' was specified. + AsciiCaseInsensitive, + /// No flags were specified and HTML says this is a case-sensitive attribute. + CaseSensitive, + /// No flags were specified and HTML says this is a case-insensitive attribute. + AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum CaseSensitivity { + CaseSensitive, + AsciiCaseInsensitive, +} + +impl CaseSensitivity { + pub fn eq(self, a: &[u8], b: &[u8]) -> bool { + match self { + CaseSensitivity::CaseSensitive => a == b, + CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), + } + } + + pub fn contains(self, haystack: &str, needle: &str) -> bool { + match self { + CaseSensitivity::CaseSensitive => haystack.contains(needle), + CaseSensitivity::AsciiCaseInsensitive => { + if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { + haystack.bytes().enumerate().any(|(i, byte)| { + if !byte.eq_ignore_ascii_case(&n_first_byte) { + return false; + } + let after_this_byte = &haystack.as_bytes()[i + 1..]; + match after_this_byte.get(..n_rest.len()) { + None => false, + Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest), + } + }) + } else { + // any_str.contains("") == true, + // though these cases should be handled with *NeverMatches and never go here. + true + } + }, + } + } +} -- cgit v1.2.3