diff options
Diffstat (limited to 'components/style')
-rw-r--r-- | components/style/attr.rs | 2 | ||||
-rw-r--r-- | components/style/dom.rs | 5 | ||||
-rw-r--r-- | components/style/restyle_hints.rs | 327 | ||||
-rw-r--r-- | components/style/selector_impl.rs | 12 | ||||
-rw-r--r-- | components/style/selector_matching.rs | 12 | ||||
-rw-r--r-- | components/style/servo_selector_impl.rs | 84 |
6 files changed, 275 insertions, 167 deletions
diff --git a/components/style/attr.rs b/components/style/attr.rs index 6ca278d7d2c..af7cfa015f6 100644 --- a/components/style/attr.rs +++ b/components/style/attr.rs @@ -29,7 +29,7 @@ pub enum LengthOrPercentageOrAuto { Length(Au), } -#[derive(PartialEq, Clone)] +#[derive(PartialEq, Clone, Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub enum AttrValue { String(String), diff --git a/components/style/dom.rs b/components/style/dom.rs index ee40e8a8c3c..5c5f3905d8d 100644 --- a/components/style/dom.rs +++ b/components/style/dom.rs @@ -11,7 +11,7 @@ use data::PrivateStyleData; use element_state::ElementState; use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock}; use refcell::{Ref, RefMut}; -use restyle_hints::{ElementSnapshot, RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; +use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_LATER_SIBLINGS, RESTYLE_SELF, RestyleHint}; use selector_impl::{ElementExt, SelectorImplExt}; use selectors::Element; use selectors::matching::DeclarationBlock; @@ -192,7 +192,8 @@ pub trait TDocument : Sized + Copy + Clone { fn root_node(&self) -> Option<Self::ConcreteNode>; - fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement, ElementSnapshot)>; + fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement, + <Self::ConcreteElement as ElementExt>::Snapshot)>; } pub trait PresentationalHintsSynthetizer { diff --git a/components/style/restyle_hints.rs b/components/style/restyle_hints.rs index 3af92457d6b..5dcaa147453 100644 --- a/components/style/restyle_hints.rs +++ b/components/style/restyle_hints.rs @@ -4,24 +4,21 @@ //! Restyle hints: an optimization to avoid unnecessarily matching selectors. -use attr::{AttrIdentifier, AttrValue}; use element_state::*; -use selector_impl::{SelectorImplExt, TheSelectorImpl, AttrString}; +use selector_impl::{ElementExt, SelectorImplExt, TheSelectorImpl, AttrString}; use selectors::matching::matches_compound_selector; use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SelectorImpl, SimpleSelector}; -use selectors::{Element, MatchAttrGeneric}; -#[cfg(feature = "gecko")] use selectors::MatchAttr; +use selectors::{Element, MatchAttr}; use std::clone::Clone; use std::sync::Arc; -use string_cache::{Atom, BorrowedAtom, BorrowedNamespace, Namespace}; +use string_cache::{Atom, BorrowedAtom, BorrowedNamespace}; /// When the ElementState of an element (like IN_HOVER_STATE) changes, certain /// pseudo-classes (like :hover) may require us to restyle that element, its /// siblings, and/or its descendants. Similarly, when various attributes of an -/// element change, we may also need to restyle things with id, class, and attribute -/// selectors. Doing this conservatively is expensive, and so we use RestyleHints to -/// short-circuit work we know is unnecessary. - +/// element change, we may also need to restyle things with id, class, and +/// attribute selectors. Doing this conservatively is expensive, and so we use +/// RestyleHints to short-circuit work we know is unnecessary. bitflags! { pub flags RestyleHint: u8 { #[doc = "Rerun selector matching on the element."] @@ -35,143 +32,153 @@ bitflags! { } } -/// In order to compute restyle hints, we perform a selector match against a list of partial -/// selectors whose rightmost simple selector may be sensitive to the thing being changed. We -/// do this matching twice, once for the element as it exists now and once for the element as it -/// existed at the time of the last restyle. If the results of the selector match differ, that means -/// that the given partial selector is sensitive to the change, and we compute a restyle hint -/// based on its combinator. +/// In order to compute restyle hints, we perform a selector match against a +/// list of partial selectors whose rightmost simple selector may be sensitive +/// to the thing being changed. We do this matching twice, once for the element +/// as it exists now and once for the element as it existed at the time of the +/// last restyle. If the results of the selector match differ, that means that +/// the given partial selector is sensitive to the change, and we compute a +/// restyle hint based on its combinator. /// -/// In order to run selector matching against the old element state, we generate a wrapper for -/// the element which claims to have the old state. This is the ElementWrapper logic below. +/// In order to run selector matching against the old element state, we generate +/// a wrapper for the element which claims to have the old state. This is the +/// ElementWrapper logic below. /// -/// Gecko does this differently for element states, and passes a mask called mStateMask, which -/// indicates the states that need to be ignored during selector matching. This saves an ElementWrapper -/// allocation and an additional selector match call at the expense of additional complexity inside -/// the selector matching logic. This only works for boolean states though, so we still need to -/// take the ElementWrapper approach for attribute-dependent style. So we do it the same both ways for -/// now to reduce complexity, but it's worth measuring the performance impact (if any) of the -/// mStateMask approach. - -#[derive(Clone)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct ElementSnapshot { - pub state: Option<ElementState>, - pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>, +/// Gecko does this differently for element states, and passes a mask called +/// mStateMask, which indicates the states that need to be ignored during +/// selector matching. This saves an ElementWrapper allocation and an additional +/// selector match call at the expense of additional complexity inside the +/// selector matching logic. This only works for boolean states though, so we +/// still need to take the ElementWrapper approach for attribute-dependent +/// style. So we do it the same both ways for now to reduce complexity, but it's +/// worth measuring the performance impact (if any) of the mStateMask approach. +pub trait ElementSnapshot : Sized + MatchAttr { + /// The state of the snapshot, if any. + fn state(&self) -> Option<ElementState>; + + /// If this snapshot contains attribute information. + fn has_attrs(&self) -> bool; + + /// The ID attribute per this snapshot. Should only be called if + /// `has_attrs()` returns true. + fn id_attr(&self) -> Option<Atom>; + + /// Whether this snapshot contains the class `name`. Should only be called + /// if `has_attrs()` returns true. + fn has_class(&self, name: &Atom) -> bool; + + /// A callback that should be called for each class of the snapshot. Should + /// only be called if `has_attrs()` returns true. + fn each_class<F>(&self, F) + where F: FnMut(&Atom); } -impl ElementSnapshot { - pub fn new() -> ElementSnapshot { - EMPTY_SNAPSHOT.clone() - } - - // Gets an attribute matching |namespace| and |name|, if any. Panics if |attrs| is None. - pub fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> { - self.attrs.as_ref().unwrap().iter() - .find(|&&(ref ident, _)| ident.local_name == *name && ident.namespace == *namespace) - .map(|&(_, ref v)| v) - } - - // Gets an attribute matching |name| if any, ignoring namespace. Panics if |attrs| is None. - pub fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> { - self.attrs.as_ref().unwrap().iter() - .find(|&&(ref ident, _)| ident.local_name == *name) - .map(|&(_, ref v)| v) - } -} - -static EMPTY_SNAPSHOT: ElementSnapshot = ElementSnapshot { state: None, attrs: None }; - -// FIXME(bholley): This implementation isn't going to work for geckolib, because -// it's fundamentally based on get_attr/match_attr, which we don't want to support -// that configuration due to the overhead of converting between UTF-16 and UTF-8. -// We'll need to figure something out when we start using restyle hints with -// geckolib, but in the mean time we can just use the trait parameters to -// specialize it to the Servo configuration. struct ElementWrapper<'a, E> - where E: Element<AttrString=AttrString>, - E::Impl: SelectorImplExt { + where E: ElementExt +{ element: E, - snapshot: &'a ElementSnapshot, + snapshot: Option<&'a E::Snapshot>, } impl<'a, E> ElementWrapper<'a, E> - where E: Element<AttrString=AttrString>, - E::Impl: SelectorImplExt { + where E: ElementExt +{ pub fn new(el: E) -> ElementWrapper<'a, E> { - ElementWrapper { element: el, snapshot: &EMPTY_SNAPSHOT } + ElementWrapper { element: el, snapshot: None } } - pub fn new_with_snapshot(el: E, snapshot: &'a ElementSnapshot) -> ElementWrapper<'a, E> { - ElementWrapper { element: el, snapshot: snapshot } + pub fn new_with_snapshot(el: E, snapshot: &'a E::Snapshot) -> ElementWrapper<'a, E> { + ElementWrapper { element: el, snapshot: Some(snapshot) } } } -#[cfg(not(feature = "gecko"))] -impl<'a, E> MatchAttrGeneric for ElementWrapper<'a, E> - where E: Element<AttrString=AttrString>, - E: MatchAttrGeneric, - E::Impl: SelectorImplExt { - fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool - where F: Fn(&str) -> bool { - use selectors::parser::NamespaceConstraint; - match self.snapshot.attrs { - Some(_) => { - let html = self.is_html_element_in_html_document(); - let local_name = if html { &attr.lower_name } else { &attr.name }; - match attr.namespace { - NamespaceConstraint::Specific(ref ns) => self.snapshot.get_attr(ns, local_name), - NamespaceConstraint::Any => self.snapshot.get_attr_ignore_ns(local_name), - }.map_or(false, |v| test(v)) - }, - None => self.element.match_attr(attr, test) - } - } -} - -#[cfg(feature = "gecko")] impl<'a, E> MatchAttr for ElementWrapper<'a, E> - where E: Element<AttrString=AttrString>, - E::Impl: SelectorImplExt { - type AttrString = AttrString; - - fn match_attr_has(&self, _attr: &AttrSelector) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + where E: ElementExt<AttrString=AttrString>, +{ + type AttrString = E::AttrString; + + fn match_attr_has(&self, attr: &AttrSelector) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_has(attr), + _ => self.element.match_attr_has(attr) + } } - fn match_attr_equals(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_equals(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_equals(attr, value), + _ => self.element.match_attr_equals(attr, value) + } } - fn match_attr_equals_ignore_ascii_case(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_equals_ignore_ascii_case(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_equals_ignore_ascii_case(attr, value), + _ => self.element.match_attr_equals_ignore_ascii_case(attr, value) + } } - fn match_attr_includes(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_includes(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_includes(attr, value), + _ => self.element.match_attr_includes(attr, value) + } } - fn match_attr_dash(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_dash(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_dash(attr, value), + _ => self.element.match_attr_dash(attr, value) + } } - fn match_attr_prefix(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_prefix(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_prefix(attr, value), + _ => self.element.match_attr_prefix(attr, value) + } } - fn match_attr_substring(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_substring(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_substring(attr, value), + _ => self.element.match_attr_substring(attr, value) + } } - fn match_attr_suffix(&self, _attr: &AttrSelector, _value: &Self::AttrString) -> bool { - panic!("Not implemented for Gecko - this system will need to be redesigned"); + fn match_attr_suffix(&self, + attr: &AttrSelector, + value: &Self::AttrString) -> bool { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_suffix(attr, value), + _ => self.element.match_attr_suffix(attr, value) + } } } impl<'a, E> Element for ElementWrapper<'a, E> - where E: Element<AttrString=AttrString>, - E: MatchAttrGeneric, - E::Impl: SelectorImplExt { + where E: ElementExt<AttrString=AttrString>, + E::Impl: SelectorImplExt<AttrString=AttrString> { type Impl = E::Impl; fn match_non_ts_pseudo_class(&self, @@ -180,9 +187,9 @@ impl<'a, E> Element for ElementWrapper<'a, E> if flag == ElementState::empty() { self.element.match_non_ts_pseudo_class(pseudo_class) } else { - match self.snapshot.state { - Some(s) => s.contains(flag), - None => self.element.match_non_ts_pseudo_class(pseudo_class) + match self.snapshot.and_then(|s| s.state()) { + Some(snapshot_state) => snapshot_state.contains(flag), + _ => self.element.match_non_ts_pseudo_class(pseudo_class) } } } @@ -190,54 +197,65 @@ impl<'a, E> Element for ElementWrapper<'a, E> fn parent_element(&self) -> Option<Self> { self.element.parent_element().map(ElementWrapper::new) } + fn first_child_element(&self) -> Option<Self> { self.element.first_child_element().map(ElementWrapper::new) } + fn last_child_element(&self) -> Option<Self> { self.element.last_child_element().map(ElementWrapper::new) } + fn prev_sibling_element(&self) -> Option<Self> { self.element.prev_sibling_element().map(ElementWrapper::new) } + fn next_sibling_element(&self) -> Option<Self> { self.element.next_sibling_element().map(ElementWrapper::new) } + fn is_html_element_in_html_document(&self) -> bool { self.element.is_html_element_in_html_document() } + fn get_local_name(&self) -> BorrowedAtom { self.element.get_local_name() } + fn get_namespace(&self) -> BorrowedNamespace { self.element.get_namespace() } + fn get_id(&self) -> Option<Atom> { - match self.snapshot.attrs { - Some(_) => self.snapshot.get_attr(&ns!(), &atom!("id")).map(|value| value.as_atom().clone()), - None => self.element.get_id(), + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.id_attr(), + _ => self.element.get_id() } } + fn has_class(&self, name: &Atom) -> bool { - match self.snapshot.attrs { - Some(_) => self.snapshot.get_attr(&ns!(), &atom!("class")) - .map_or(false, |v| { v.as_tokens().iter().any(|atom| atom == name) }), - None => self.element.has_class(name), + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.has_class(name), + _ => self.element.has_class(name) } } + fn is_empty(&self) -> bool { self.element.is_empty() } + fn is_root(&self) -> bool { self.element.is_root() } - fn each_class<F>(&self, mut callback: F) where F: FnMut(&Atom) { - match self.snapshot.attrs { - Some(_) => { - if let Some(v) = self.snapshot.get_attr(&ns!(), &atom!("class")) { - for c in v.as_tokens() { callback(c) } - } - } - None => self.element.each_class(callback), + + fn each_class<F>(&self, callback: F) + where F: FnMut(&Atom) { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.each_class(callback), + _ => self.element.each_class(callback) } } } @@ -296,24 +314,24 @@ impl Sensitivities { } } -// Mapping between (partial) CompoundSelectors (and the combinator to their right) -// and the states and attributes they depend on. -// -// In general, for all selectors in all applicable stylesheets of the form: -// -// |a _ b _ c _ d _ e| -// -// Where: -// * |b| and |d| are simple selectors that depend on state (like :hover) or -// attributes (like [attr...], .foo, or #foo). -// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on -// state or attributes. -// -// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, even -// though those selectors may not appear on their own in any stylesheet. This allows -// us to quickly scan through the dependency sites of all style rules and determine the -// maximum effect that a given state or attribute change may have on the style of -// elements in the document. +/// Mapping between (partial) CompoundSelectors (and the combinator to their +/// right) and the states and attributes they depend on. +/// +/// In general, for all selectors in all applicable stylesheets of the form: +/// +/// |a _ b _ c _ d _ e| +/// +/// Where: +/// * |b| and |d| are simple selectors that depend on state (like :hover) or +/// attributes (like [attr...], .foo, or #foo). +/// * |a|, |c|, and |e| are arbitrary simple selectors that do not depend on +/// state or attributes. +/// +/// We generate a Dependency for both |a _ b:X _| and |a _ b:X _ c _ d:Y _|, +/// even though those selectors may not appear on their own in any stylesheet. +/// This allows us to quickly scan through the dependency sites of all style +/// rules and determine the maximum effect that a given state or attribute +/// change may have on the style of elements in the document. #[derive(Debug)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] struct Dependency { @@ -368,11 +386,14 @@ impl DependencySet { } impl DependencySet { - pub fn compute_hint<E>(&self, el: &E, snapshot: &ElementSnapshot, current_state: ElementState) - -> RestyleHint - where E: Element<Impl=TheSelectorImpl, AttrString=AttrString> + Clone + MatchAttrGeneric { - let state_changes = snapshot.state.map_or(ElementState::empty(), |old_state| current_state ^ old_state); - let attrs_changed = snapshot.attrs.is_some(); + pub fn compute_hint<E>(&self, el: &E, + snapshot: &E::Snapshot, + current_state: ElementState) + -> RestyleHint + where E: ElementExt + Clone + { + let state_changes = snapshot.state().map_or_else(ElementState::empty, |old_state| current_state ^ old_state); + let attrs_changed = snapshot.has_attrs(); let mut hint = RestyleHint::empty(); for dep in &self.deps { if state_changes.intersects(dep.sensitivities.states) || (attrs_changed && dep.sensitivities.attrs) { diff --git a/components/style/selector_impl.rs b/components/style/selector_impl.rs index a95499092cf..64b5725ebd0 100644 --- a/components/style/selector_impl.rs +++ b/components/style/selector_impl.rs @@ -5,6 +5,7 @@ //! The pseudo-classes and pseudo-elements supported by the style system. use element_state::ElementState; +use restyle_hints; use selectors::Element; use selectors::parser::SelectorImpl; use std::fmt::Debug; @@ -13,13 +14,16 @@ use stylesheets::Stylesheet; pub type AttrString = <TheSelectorImpl as SelectorImpl>::AttrString; #[cfg(feature = "servo")] -pub use servo_selector_impl::ServoSelectorImpl; +pub use servo_selector_impl::*; #[cfg(feature = "servo")] -pub use servo_selector_impl::{ServoSelectorImpl as TheSelectorImpl, PseudoElement, NonTSPseudoClass}; +pub use servo_selector_impl::{ServoSelectorImpl as TheSelectorImpl, ServoElementSnapshot as ElementSnapshot}; #[cfg(feature = "gecko")] -pub use gecko_selector_impl::{GeckoSelectorImpl as TheSelectorImpl, PseudoElement, NonTSPseudoClass}; +pub use gecko_selector_impl::*; + +#[cfg(feature = "gecko")] +pub use gecko_selector_impl::{GeckoSelectorImpl as TheSelectorImpl, GeckoElementSnapshot as ElementSnapshot}; /// This function determines if a pseudo-element is eagerly cascaded or not. /// @@ -69,6 +73,8 @@ impl PseudoElementCascadeType { } pub trait ElementExt: Element<Impl=TheSelectorImpl, AttrString=<TheSelectorImpl as SelectorImpl>::AttrString> { + type Snapshot: restyle_hints::ElementSnapshot<AttrString = Self::AttrString> + 'static; + fn is_link(&self) -> bool; } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index 1e87ade3f5f..5451bae28bf 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -10,12 +10,12 @@ use error_reporting::StdoutErrorReporter; use keyframes::KeyframesAnimation; use media_queries::{Device, MediaType}; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock, ComputedValues}; -use restyle_hints::{ElementSnapshot, RestyleHint, DependencySet}; -use selector_impl::{SelectorImplExt, TheSelectorImpl, PseudoElement, AttrString}; +use restyle_hints::{RestyleHint, DependencySet}; +use selector_impl::{ElementExt, SelectorImplExt, TheSelectorImpl, PseudoElement, AttrString}; +use selectors::Element; use selectors::bloom::BloomFilter; use selectors::matching::DeclarationBlock as GenericDeclarationBlock; use selectors::matching::{Rule, SelectorMap}; -use selectors::{Element, MatchAttrGeneric}; use sink::Push; use smallvec::VecLike; use std::collections::HashMap; @@ -402,15 +402,15 @@ impl Stylist { } pub fn compute_restyle_hint<E>(&self, element: &E, - snapshot: &ElementSnapshot, + snapshot: &E::Snapshot, // NB: We need to pass current_state as an argument because // selectors::Element doesn't provide access to ElementState // directly, and computing it from the ElementState would be // more expensive than getting it directly from the caller. current_state: ElementState) -> RestyleHint - where E: Element<Impl=TheSelectorImpl, AttrString=AttrString> - + Clone + MatchAttrGeneric { + where E: ElementExt + Clone + { self.state_deps.compute_hint(element, snapshot, current_state) } } diff --git a/components/style/servo_selector_impl.rs b/components/style/servo_selector_impl.rs index d1fdaf0336c..cee046e62f3 100644 --- a/components/style/servo_selector_impl.rs +++ b/components/style/servo_selector_impl.rs @@ -2,13 +2,16 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use attr::{AttrIdentifier, AttrValue}; use element_state::ElementState; use error_reporting::StdoutErrorReporter; use parser::ParserContextExtraData; +use restyle_hints::ElementSnapshot; use selector_impl::{SelectorImplExt, ElementExt, PseudoElementCascadeType, TheSelectorImpl}; -use selectors::Element; -use selectors::parser::{ParserContext, SelectorImpl}; +use selectors::parser::{AttrSelector, ParserContext, SelectorImpl}; +use selectors::{Element, MatchAttrGeneric}; use std::process; +use string_cache::{Atom, Namespace}; use stylesheets::{Stylesheet, Origin}; use url::Url; use util::opts; @@ -189,7 +192,84 @@ impl SelectorImplExt for ServoSelectorImpl { } } +/// Servo's version of an element snapshot. +#[derive(Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +pub struct ServoElementSnapshot { + pub state: Option<ElementState>, + pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>, + pub is_html_element_in_html_document: bool, +} + +impl ServoElementSnapshot { + pub fn new(is_html_element_in_html_document: bool) -> Self { + ServoElementSnapshot { + state: None, + attrs: None, + is_html_element_in_html_document: is_html_element_in_html_document, + } + } + + fn get_attr(&self, namespace: &Namespace, name: &Atom) -> Option<&AttrValue> { + self.attrs.as_ref().unwrap().iter() + .find(|&&(ref ident, _)| ident.local_name == *name && + ident.namespace == *namespace) + .map(|&(_, ref v)| v) + } + + fn get_attr_ignore_ns(&self, name: &Atom) -> Option<&AttrValue> { + self.attrs.as_ref().unwrap().iter() + .find(|&&(ref ident, _)| ident.local_name == *name) + .map(|&(_, ref v)| 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!(), &atom!("id")).map(|v| v.as_atom().clone()) + } + + fn has_class(&self, name: &Atom) -> bool { + self.get_attr(&ns!(), &atom!("class")) + .map_or(false, |v| v.as_tokens().iter().any(|atom| atom == name)) + } + + fn each_class<F>(&self, mut callback: F) + where F: FnMut(&Atom) + { + if let Some(v) = self.get_attr(&ns!(), &atom!("class")) { + for class in v.as_tokens() { + callback(class); + } + } + } +} + +impl MatchAttrGeneric for ServoElementSnapshot { + fn match_attr<F>(&self, attr: &AttrSelector, test: F) -> bool + where F: Fn(&str) -> bool + { + use selectors::parser::NamespaceConstraint; + let html = self.is_html_element_in_html_document; + let local_name = if html { &attr.lower_name } else { &attr.name }; + match attr.namespace { + NamespaceConstraint::Specific(ref ns) => self.get_attr(ns, local_name), + NamespaceConstraint::Any => self.get_attr_ignore_ns(local_name), + }.map_or(false, |v| test(v)) + } +} + impl<E: Element<Impl=TheSelectorImpl, AttrString=String>> ElementExt for E { + type Snapshot = ServoElementSnapshot; + fn is_link(&self) -> bool { self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink) } |