mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 10:15:41 +00:00
6b5853fa8e
Depends on D17196 Differential Revision: https://phabricator.services.mozilla.com/D17197 --HG-- extra : moz-landing-system : lando
208 lines
6.9 KiB
Rust
208 lines
6.9 KiB
Rust
/* 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<Impl: SelectorImpl> {
|
|
#[shmem(field_bound)]
|
|
pub namespace: Option<NamespaceConstraint<(Impl::NamespacePrefix, Impl::NamespaceUrl)>>,
|
|
#[shmem(field_bound)]
|
|
pub local_name: Impl::LocalName,
|
|
pub local_name_lower: Impl::LocalName,
|
|
#[shmem(field_bound)]
|
|
pub operation: ParsedAttrSelectorOperation<Impl::AttrValue>,
|
|
pub never_matches: bool,
|
|
}
|
|
|
|
impl<Impl: SelectorImpl> AttrSelectorWithOptionalNamespace<Impl> {
|
|
pub fn namespace(&self) -> Option<NamespaceConstraint<&Impl::NamespaceUrl>> {
|
|
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<NamespaceUrl> {
|
|
Any,
|
|
|
|
/// Empty string for no namespace
|
|
Specific(NamespaceUrl),
|
|
}
|
|
|
|
#[derive(Clone, Eq, PartialEq, ToShmem)]
|
|
pub enum ParsedAttrSelectorOperation<AttrValue> {
|
|
Exists,
|
|
WithValue {
|
|
operator: AttrSelectorOperator,
|
|
case_sensitivity: ParsedCaseSensitivity,
|
|
expected_value: AttrValue,
|
|
},
|
|
}
|
|
|
|
#[derive(Clone, Eq, PartialEq)]
|
|
pub enum AttrSelectorOperation<AttrValue> {
|
|
Exists,
|
|
WithValue {
|
|
operator: AttrSelectorOperator,
|
|
case_sensitivity: CaseSensitivity,
|
|
expected_value: AttrValue,
|
|
},
|
|
}
|
|
|
|
impl<AttrValue> AttrSelectorOperation<AttrValue> {
|
|
pub fn eval_str(&self, element_attr_value: &str) -> bool
|
|
where
|
|
AttrValue: AsRef<str>,
|
|
{
|
|
match *self {
|
|
AttrSelectorOperation::Exists => true,
|
|
AttrSelectorOperation::WithValue {
|
|
operator,
|
|
case_sensitivity,
|
|
ref expected_value,
|
|
} => operator.eval_str(
|
|
element_attr_value,
|
|
expected_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<W>(&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: &'static [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,
|
|
}
|
|
|
|
impl ParsedCaseSensitivity {
|
|
pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity {
|
|
match self {
|
|
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument
|
|
if is_html_element_in_html_document =>
|
|
{
|
|
CaseSensitivity::AsciiCaseInsensitive
|
|
},
|
|
ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {
|
|
CaseSensitivity::CaseSensitive
|
|
},
|
|
ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => {
|
|
CaseSensitivity::CaseSensitive
|
|
},
|
|
ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|