summaryrefslogtreecommitdiffstats
path: root/servo/components/style/servo/selector_parser.rs
diff options
context:
space:
mode:
Diffstat (limited to 'servo/components/style/servo/selector_parser.rs')
-rw-r--r--servo/components/style/servo/selector_parser.rs806
1 files changed, 806 insertions, 0 deletions
diff --git a/servo/components/style/servo/selector_parser.rs b/servo/components/style/servo/selector_parser.rs
new file mode 100644
index 0000000000..b20f1754a0
--- /dev/null
+++ b/servo/components/style/servo/selector_parser.rs
@@ -0,0 +1,806 @@
+/* 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/. */
+
+#![deny(missing_docs)]
+
+//! Servo's selector parser.
+
+use crate::attr::{AttrIdentifier, AttrValue};
+use crate::dom::{OpaqueNode, TElement, TNode};
+use crate::invalidation::element::document_state::InvalidationMatchingData;
+use crate::invalidation::element::element_wrapper::ElementSnapshot;
+use crate::properties::longhands::display::computed_value::T as Display;
+use crate::properties::{ComputedValues, PropertyFlags};
+use crate::selector_parser::AttrValue as SelectorAttrValue;
+use crate::selector_parser::{PseudoElementCascadeType, SelectorParser};
+use crate::values::{AtomIdent, AtomString};
+use crate::{Atom, CaseSensitivityExt, LocalName, Namespace, Prefix};
+use cssparser::{serialize_identifier, CowRcStr, Parser as CssParser, SourceLocation, ToCss};
+use dom::{DocumentState, ElementState};
+use fxhash::FxHashMap;
+use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
+use selectors::parser::SelectorParseErrorKind;
+use selectors::visitor::SelectorVisitor;
+use std::fmt;
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use style_traits::{ParseError, StyleParseErrorKind};
+
+/// A pseudo-element, both public and private.
+///
+/// NB: If you add to this list, be sure to update `each_simple_pseudo_element` too.
+#[derive(
+ Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize, ToShmem,
+)]
+#[allow(missing_docs)]
+#[repr(usize)]
+pub enum PseudoElement {
+ // Eager pseudos. Keep these first so that eager_index() works.
+ After = 0,
+ Before,
+ Selection,
+ // If/when :first-letter is added, update is_first_letter accordingly.
+
+ // If/when :first-line is added, update is_first_line accordingly.
+
+ // If/when ::first-letter, ::first-line, or ::placeholder are added, adjust
+ // our property_restriction implementation to do property filtering for
+ // them. Also, make sure the UA sheet has the !important rules some of the
+ // APPLIES_TO_PLACEHOLDER properties expect!
+
+ // Non-eager pseudos.
+ DetailsSummary,
+ DetailsContent,
+ ServoText,
+ ServoInputText,
+ ServoTableWrapper,
+ ServoAnonymousTableWrapper,
+ ServoAnonymousTable,
+ ServoAnonymousTableRow,
+ ServoAnonymousTableCell,
+ ServoAnonymousBlock,
+ ServoInlineBlockWrapper,
+ ServoInlineAbsolute,
+}
+
+/// The count of all pseudo-elements.
+pub const PSEUDO_COUNT: usize = PseudoElement::ServoInlineAbsolute as usize + 1;
+
+impl ::selectors::parser::PseudoElement for PseudoElement {
+ type Impl = SelectorImpl;
+}
+
+impl ToCss for PseudoElement {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ use self::PseudoElement::*;
+ dest.write_str(match *self {
+ After => "::after",
+ Before => "::before",
+ Selection => "::selection",
+ DetailsSummary => "::-servo-details-summary",
+ DetailsContent => "::-servo-details-content",
+ ServoText => "::-servo-text",
+ ServoInputText => "::-servo-input-text",
+ ServoTableWrapper => "::-servo-table-wrapper",
+ ServoAnonymousTableWrapper => "::-servo-anonymous-table-wrapper",
+ ServoAnonymousTable => "::-servo-anonymous-table",
+ ServoAnonymousTableRow => "::-servo-anonymous-table-row",
+ ServoAnonymousTableCell => "::-servo-anonymous-table-cell",
+ ServoAnonymousBlock => "::-servo-anonymous-block",
+ ServoInlineBlockWrapper => "::-servo-inline-block-wrapper",
+ ServoInlineAbsolute => "::-servo-inline-absolute",
+ })
+ }
+}
+
+/// The number of eager pseudo-elements. Keep this in sync with cascade_type.
+pub const EAGER_PSEUDO_COUNT: usize = 3;
+
+impl PseudoElement {
+ /// Gets the canonical index of this eagerly-cascaded pseudo-element.
+ #[inline]
+ pub fn eager_index(&self) -> usize {
+ debug_assert!(self.is_eager());
+ self.clone() as usize
+ }
+
+ /// An index for this pseudo-element to be indexed in an enumerated array.
+ #[inline]
+ pub fn index(&self) -> usize {
+ self.clone() as usize
+ }
+
+ /// An array of `None`, one per pseudo-element.
+ pub fn pseudo_none_array<T>() -> [Option<T>; PSEUDO_COUNT] {
+ Default::default()
+ }
+
+ /// Creates a pseudo-element from an eager index.
+ #[inline]
+ pub fn from_eager_index(i: usize) -> Self {
+ assert!(i < EAGER_PSEUDO_COUNT);
+ let result: PseudoElement = unsafe { mem::transmute(i) };
+ debug_assert!(result.is_eager());
+ result
+ }
+
+ /// Whether the current pseudo element is ::before or ::after.
+ #[inline]
+ pub fn is_before_or_after(&self) -> bool {
+ self.is_before() || self.is_after()
+ }
+
+ /// Whether this is an unknown ::-webkit- pseudo-element.
+ #[inline]
+ pub fn is_unknown_webkit_pseudo_element(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is the ::marker pseudo.
+ #[inline]
+ pub fn is_marker(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is the ::selection pseudo.
+ #[inline]
+ pub fn is_selection(&self) -> bool {
+ *self == PseudoElement::Selection
+ }
+
+ /// Whether this pseudo-element is the ::before pseudo.
+ #[inline]
+ pub fn is_before(&self) -> bool {
+ *self == PseudoElement::Before
+ }
+
+ /// Whether this pseudo-element is the ::after pseudo.
+ #[inline]
+ pub fn is_after(&self) -> bool {
+ *self == PseudoElement::After
+ }
+
+ /// Whether the current pseudo element is :first-letter
+ #[inline]
+ pub fn is_first_letter(&self) -> bool {
+ false
+ }
+
+ /// Whether the current pseudo element is :first-line
+ #[inline]
+ pub fn is_first_line(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is the ::-moz-color-swatch pseudo.
+ #[inline]
+ pub fn is_color_swatch(&self) -> bool {
+ false
+ }
+
+ /// Whether this pseudo-element is eagerly-cascaded.
+ #[inline]
+ pub fn is_eager(&self) -> bool {
+ self.cascade_type() == PseudoElementCascadeType::Eager
+ }
+
+ /// Whether this pseudo-element is lazily-cascaded.
+ #[inline]
+ pub fn is_lazy(&self) -> bool {
+ self.cascade_type() == PseudoElementCascadeType::Lazy
+ }
+
+ /// Whether this pseudo-element is for an anonymous box.
+ pub fn is_anon_box(&self) -> bool {
+ self.is_precomputed()
+ }
+
+ /// Whether this pseudo-element skips flex/grid container display-based
+ /// fixup.
+ #[inline]
+ pub fn skip_item_display_fixup(&self) -> bool {
+ !self.is_before_or_after()
+ }
+
+ /// Whether this pseudo-element is precomputed.
+ #[inline]
+ pub fn is_precomputed(&self) -> bool {
+ self.cascade_type() == PseudoElementCascadeType::Precomputed
+ }
+
+ /// Returns which kind of cascade type has this pseudo.
+ ///
+ /// For more info on cascade types, see docs/components/style.md
+ ///
+ /// Note: Keep this in sync with EAGER_PSEUDO_COUNT.
+ #[inline]
+ pub fn cascade_type(&self) -> PseudoElementCascadeType {
+ match *self {
+ PseudoElement::After | PseudoElement::Before | PseudoElement::Selection => {
+ PseudoElementCascadeType::Eager
+ },
+ PseudoElement::DetailsSummary => PseudoElementCascadeType::Lazy,
+ PseudoElement::DetailsContent |
+ PseudoElement::ServoText |
+ PseudoElement::ServoInputText |
+ PseudoElement::ServoTableWrapper |
+ PseudoElement::ServoAnonymousTableWrapper |
+ PseudoElement::ServoAnonymousTable |
+ PseudoElement::ServoAnonymousTableRow |
+ PseudoElement::ServoAnonymousTableCell |
+ PseudoElement::ServoAnonymousBlock |
+ PseudoElement::ServoInlineBlockWrapper |
+ PseudoElement::ServoInlineAbsolute => PseudoElementCascadeType::Precomputed,
+ }
+ }
+
+ /// Covert non-canonical pseudo-element to canonical one, and keep a
+ /// canonical one as it is.
+ pub fn canonical(&self) -> PseudoElement {
+ self.clone()
+ }
+
+ /// Stub, only Gecko needs this
+ pub fn pseudo_info(&self) {
+ ()
+ }
+
+ /// Property flag that properties must have to apply to this pseudo-element.
+ #[inline]
+ pub fn property_restriction(&self) -> Option<PropertyFlags> {
+ None
+ }
+
+ /// Whether this pseudo-element should actually exist if it has
+ /// the given styles.
+ pub fn should_exist(&self, style: &ComputedValues) -> bool {
+ let display = style.get_box().clone_display();
+ if display == Display::None {
+ return false;
+ }
+ if self.is_before_or_after() && style.ineffective_content_property() {
+ return false;
+ }
+
+ true
+ }
+}
+
+/// The type used for storing `:lang` arguments.
+pub type Lang = Box<str>;
+
+/// A non tree-structural pseudo-class.
+/// See https://drafts.csswg.org/selectors-4/#structural-pseudos
+#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToShmem)]
+#[allow(missing_docs)]
+pub enum NonTSPseudoClass {
+ Active,
+ AnyLink,
+ Checked,
+ Defined,
+ Disabled,
+ Enabled,
+ Focus,
+ Fullscreen,
+ Hover,
+ Indeterminate,
+ Lang(Lang),
+ Link,
+ PlaceholderShown,
+ ReadWrite,
+ ReadOnly,
+ ServoNonZeroBorder,
+ Target,
+ Visited,
+}
+
+impl ::selectors::parser::NonTSPseudoClass for NonTSPseudoClass {
+ type Impl = SelectorImpl;
+
+ #[inline]
+ fn is_active_or_hover(&self) -> bool {
+ matches!(*self, NonTSPseudoClass::Active | NonTSPseudoClass::Hover)
+ }
+
+ #[inline]
+ fn is_user_action_state(&self) -> bool {
+ matches!(
+ *self,
+ NonTSPseudoClass::Active | NonTSPseudoClass::Hover | NonTSPseudoClass::Focus
+ )
+ }
+
+ fn visit<V>(&self, _: &mut V) -> bool
+ where
+ V: SelectorVisitor<Impl = Self::Impl>,
+ {
+ true
+ }
+}
+
+impl ToCss for NonTSPseudoClass {
+ fn to_css<W>(&self, dest: &mut W) -> fmt::Result
+ where
+ W: fmt::Write,
+ {
+ use self::NonTSPseudoClass::*;
+ if let Lang(ref lang) = *self {
+ dest.write_str(":lang(")?;
+ serialize_identifier(lang, dest)?;
+ return dest.write_char(')');
+ }
+
+ dest.write_str(match *self {
+ Active => ":active",
+ AnyLink => ":any-link",
+ Checked => ":checked",
+ Defined => ":defined",
+ Disabled => ":disabled",
+ Enabled => ":enabled",
+ Focus => ":focus",
+ Fullscreen => ":fullscreen",
+ Hover => ":hover",
+ Indeterminate => ":indeterminate",
+ Link => ":link",
+ PlaceholderShown => ":placeholder-shown",
+ ReadWrite => ":read-write",
+ ReadOnly => ":read-only",
+ ServoNonZeroBorder => ":-servo-nonzero-border",
+ Target => ":target",
+ Visited => ":visited",
+ Lang(_) => unreachable!(),
+ })
+ }
+}
+
+impl NonTSPseudoClass {
+ /// Gets a given state flag for this pseudo-class. This is used to do
+ /// selector matching, and it's set from the DOM.
+ pub fn state_flag(&self) -> ElementState {
+ use self::NonTSPseudoClass::*;
+ match *self {
+ Active => ElementState::IN_ACTIVE_STATE,
+ Focus => ElementState::IN_FOCUS_STATE,
+ Fullscreen => ElementState::IN_FULLSCREEN_STATE,
+ Hover => ElementState::IN_HOVER_STATE,
+ Defined => ElementState::IN_DEFINED_STATE,
+ Enabled => ElementState::IN_ENABLED_STATE,
+ Disabled => ElementState::IN_DISABLED_STATE,
+ Checked => ElementState::IN_CHECKED_STATE,
+ Indeterminate => ElementState::IN_INDETERMINATE_STATE,
+ ReadOnly | ReadWrite => ElementState::IN_READWRITE_STATE,
+ PlaceholderShown => ElementState::IN_PLACEHOLDER_SHOWN_STATE,
+ Target => ElementState::IN_TARGET_STATE,
+
+ AnyLink | Lang(_) | Link | Visited | ServoNonZeroBorder => ElementState::empty(),
+ }
+ }
+
+ /// Get the document state flag associated with a pseudo-class, if any.
+ pub fn document_state_flag(&self) -> DocumentState {
+ DocumentState::empty()
+ }
+
+ /// Returns true if the given pseudoclass should trigger style sharing cache revalidation.
+ pub fn needs_cache_revalidation(&self) -> bool {
+ self.state_flag().is_empty()
+ }
+}
+
+/// The abstract struct we implement the selector parser implementation on top
+/// of.
+#[derive(Clone, Debug, PartialEq)]
+#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
+pub struct SelectorImpl;
+
+impl ::selectors::SelectorImpl for SelectorImpl {
+ type PseudoElement = PseudoElement;
+ type NonTSPseudoClass = NonTSPseudoClass;
+
+ type ExtraMatchingData = InvalidationMatchingData;
+ type AttrValue = String;
+ type Identifier = Atom;
+ type ClassName = Atom;
+ type PartName = Atom;
+ type LocalName = LocalName;
+ type NamespacePrefix = Prefix;
+ type NamespaceUrl = Namespace;
+ type BorrowedLocalName = LocalName;
+ type BorrowedNamespaceUrl = Namespace;
+}
+
+impl<'a, 'i> ::selectors::Parser<'i> for SelectorParser<'a> {
+ type Impl = SelectorImpl;
+ type Error = StyleParseErrorKind<'i>;
+
+ fn parse_non_ts_pseudo_class(
+ &self,
+ location: SourceLocation,
+ name: CowRcStr<'i>,
+ ) -> Result<NonTSPseudoClass, ParseError<'i>> {
+ use self::NonTSPseudoClass::*;
+ let pseudo_class = match_ignore_ascii_case! { &name,
+ "active" => Active,
+ "any-link" => AnyLink,
+ "checked" => Checked,
+ "defined" => Defined,
+ "disabled" => Disabled,
+ "enabled" => Enabled,
+ "focus" => Focus,
+ "fullscreen" => Fullscreen,
+ "hover" => Hover,
+ "indeterminate" => Indeterminate,
+ "-moz-inert" => MozInert,
+ "link" => Link,
+ "placeholder-shown" => PlaceholderShown,
+ "read-write" => ReadWrite,
+ "read-only" => ReadOnly,
+ "target" => Target,
+ "visited" => Visited,
+ "-servo-nonzero-border" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(
+ SelectorParseErrorKind::UnexpectedIdent("-servo-nonzero-border".into())
+ ))
+ }
+ ServoNonZeroBorder
+ },
+ _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ };
+
+ Ok(pseudo_class)
+ }
+
+ fn parse_non_ts_functional_pseudo_class<'t>(
+ &self,
+ name: CowRcStr<'i>,
+ parser: &mut CssParser<'i, 't>,
+ ) -> Result<NonTSPseudoClass, ParseError<'i>> {
+ use self::NonTSPseudoClass::*;
+ let pseudo_class = match_ignore_ascii_case! { &name,
+ "lang" => {
+ Lang(parser.expect_ident_or_string()?.as_ref().into())
+ },
+ _ => return Err(parser.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone()))),
+ };
+
+ Ok(pseudo_class)
+ }
+
+ fn parse_pseudo_element(
+ &self,
+ location: SourceLocation,
+ name: CowRcStr<'i>,
+ ) -> Result<PseudoElement, ParseError<'i>> {
+ use self::PseudoElement::*;
+ let pseudo_element = match_ignore_ascii_case! { &name,
+ "before" => Before,
+ "after" => After,
+ "selection" => Selection,
+ "-servo-details-summary" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ DetailsSummary
+ },
+ "-servo-details-content" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ DetailsContent
+ },
+ "-servo-text" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoText
+ },
+ "-servo-input-text" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoInputText
+ },
+ "-servo-table-wrapper" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoTableWrapper
+ },
+ "-servo-anonymous-table-wrapper" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTableWrapper
+ },
+ "-servo-anonymous-table" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTable
+ },
+ "-servo-anonymous-table-row" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTableRow
+ },
+ "-servo-anonymous-table-cell" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousTableCell
+ },
+ "-servo-anonymous-block" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoAnonymousBlock
+ },
+ "-servo-inline-block-wrapper" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoInlineBlockWrapper
+ },
+ "-servo-inline-absolute" => {
+ if !self.in_user_agent_stylesheet() {
+ return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+ }
+ ServoInlineAbsolute
+ },
+ _ => return Err(location.new_custom_error(SelectorParseErrorKind::UnexpectedIdent(name.clone())))
+
+ };
+
+ Ok(pseudo_element)
+ }
+
+ fn default_namespace(&self) -> Option<Namespace> {
+ self.namespaces.default.as_ref().map(|ns| ns.clone())
+ }
+
+ fn namespace_for_prefix(&self, prefix: &Prefix) -> Option<Namespace> {
+ self.namespaces.prefixes.get(prefix).cloned()
+ }
+}
+
+impl SelectorImpl {
+ /// A helper to traverse each eagerly cascaded pseudo-element, executing
+ /// `fun` on it.
+ #[inline]
+ pub fn each_eagerly_cascaded_pseudo_element<F>(mut fun: F)
+ where
+ F: FnMut(PseudoElement),
+ {
+ for i in 0..EAGER_PSEUDO_COUNT {
+ fun(PseudoElement::from_eager_index(i));
+ }
+ }
+}
+
+/// A map from elements to snapshots for the Servo style back-end.
+#[derive(Debug)]
+pub struct SnapshotMap(FxHashMap<OpaqueNode, ServoElementSnapshot>);
+
+impl SnapshotMap {
+ /// Create a new empty `SnapshotMap`.
+ pub fn new() -> Self {
+ SnapshotMap(FxHashMap::default())
+ }
+
+ /// Get a snapshot given an element.
+ pub fn get<T: TElement>(&self, el: &T) -> Option<&ServoElementSnapshot> {
+ self.0.get(&el.as_node().opaque())
+ }
+}
+
+impl Deref for SnapshotMap {
+ type Target = FxHashMap<OpaqueNode, ServoElementSnapshot>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for SnapshotMap {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+/// Servo's version of an element snapshot.
+#[derive(Debug, Default, MallocSizeOf)]
+pub struct ServoElementSnapshot {
+ /// The stored state of the element.
+ pub state: Option<ElementState>,
+ /// The set of stored attributes and its values.
+ pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
+ /// The set of changed attributes and its values.
+ pub changed_attrs: Vec<LocalName>,
+ /// Whether the class attribute changed or not.
+ pub class_changed: bool,
+ /// Whether the id attribute changed or not.
+ pub id_changed: bool,
+ /// Whether other attributes other than id or class changed or not.
+ pub other_attributes_changed: bool,
+}
+
+impl ServoElementSnapshot {
+ /// Create an empty element snapshot.
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Returns whether the id attribute changed or not.
+ pub fn id_changed(&self) -> bool {
+ self.id_changed
+ }
+
+ /// Returns whether the class attribute changed or not.
+ pub fn class_changed(&self) -> bool {
+ self.class_changed
+ }
+
+ /// Returns whether other attributes other than id or class changed or not.
+ pub fn other_attr_changed(&self) -> bool {
+ self.other_attributes_changed
+ }
+
+ fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> {
+ self.attrs
+ .as_ref()
+ .unwrap()
+ .iter()
+ .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace)
+ .map(|&(_, ref v)| v)
+ }
+
+ /// Executes the callback once for each attribute that changed.
+ #[inline]
+ pub fn each_attr_changed<F>(&self, mut callback: F)
+ where
+ F: FnMut(&LocalName),
+ {
+ for name in &self.changed_attrs {
+ callback(name)
+ }
+ }
+
+ fn any_attr_ignore_ns<F>(&self, name: &LocalName, mut f: F) -> bool
+ where
+ F: FnMut(&AttrValue) -> bool,
+ {
+ self.attrs
+ .as_ref()
+ .unwrap()
+ .iter()
+ .any(|&(ref ident, ref v)| ident.local_name == *name && f(v))
+ }
+}
+
+impl ElementSnapshot for ServoElementSnapshot {
+ fn state(&self) -> Option<ElementState> {
+ self.state.clone()
+ }
+
+ fn has_attrs(&self) -> bool {
+ self.attrs.is_some()
+ }
+
+ fn id_attr(&self) -> Option<&Atom> {
+ self.get_attr(&ns!(), &local_name!("id"))
+ .map(|v| v.as_atom())
+ }
+
+ fn is_part(&self, _name: &AtomIdent) -> bool {
+ false
+ }
+
+ fn imported_part(&self, _: &AtomIdent) -> Option<AtomIdent> {
+ None
+ }
+
+ fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool {
+ self.get_attr(&ns!(), &local_name!("class"))
+ .map_or(false, |v| {
+ v.as_tokens()
+ .iter()
+ .any(|atom| case_sensitivity.eq_atom(atom, name))
+ })
+ }
+
+ fn each_class<F>(&self, mut callback: F)
+ where
+ F: FnMut(&AtomIdent),
+ {
+ if let Some(v) = self.get_attr(&ns!(), &local_name!("class")) {
+ for class in v.as_tokens() {
+ callback(AtomIdent::cast(class));
+ }
+ }
+ }
+
+ fn lang_attr(&self) -> Option<SelectorAttrValue> {
+ self.get_attr(&ns!(xml), &local_name!("lang"))
+ .or_else(|| self.get_attr(&ns!(), &local_name!("lang")))
+ .map(|v| SelectorAttrValue::from(v as &str))
+ }
+}
+
+impl ServoElementSnapshot {
+ /// selectors::Element::attr_matches
+ pub fn attr_matches(
+ &self,
+ ns: &NamespaceConstraint<&Namespace>,
+ local_name: &LocalName,
+ operation: &AttrSelectorOperation<&AtomString>,
+ ) -> bool {
+ match *ns {
+ NamespaceConstraint::Specific(ref ns) => self
+ .get_attr(ns, local_name)
+ .map_or(false, |value| value.eval_selector(operation)),
+ NamespaceConstraint::Any => {
+ self.any_attr_ignore_ns(local_name, |value| value.eval_selector(operation))
+ },
+ }
+ }
+}
+
+/// Returns whether the language is matched, as defined by
+/// [RFC 4647](https://tools.ietf.org/html/rfc4647#section-3.3.2).
+pub fn extended_filtering(tag: &str, range: &str) -> bool {
+ range.split(',').any(|lang_range| {
+ // step 1
+ let mut range_subtags = lang_range.split('\x2d');
+ let mut tag_subtags = tag.split('\x2d');
+
+ // step 2
+ // Note: [Level-4 spec](https://drafts.csswg.org/selectors/#lang-pseudo) check for wild card
+ if let (Some(range_subtag), Some(tag_subtag)) = (range_subtags.next(), tag_subtags.next()) {
+ if !(range_subtag.eq_ignore_ascii_case(tag_subtag) ||
+ range_subtag.eq_ignore_ascii_case("*"))
+ {
+ return false;
+ }
+ }
+
+ let mut current_tag_subtag = tag_subtags.next();
+
+ // step 3
+ for range_subtag in range_subtags {
+ // step 3a
+ if range_subtag == "*" {
+ continue;
+ }
+ match current_tag_subtag.clone() {
+ Some(tag_subtag) => {
+ // step 3c
+ if range_subtag.eq_ignore_ascii_case(tag_subtag) {
+ current_tag_subtag = tag_subtags.next();
+ continue;
+ }
+ // step 3d
+ if tag_subtag.len() == 1 {
+ return false;
+ }
+ // else step 3e - continue with loop
+ current_tag_subtag = tag_subtags.next();
+ if current_tag_subtag.is_none() {
+ return false;
+ }
+ },
+ // step 3b
+ None => {
+ return false;
+ },
+ }
+ }
+ // step 4
+ true
+ })
+}