From 611e611215a2826677b2fa255a166ce65fc221e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emilio=20Cobos=20=C3=81lvarez?= Date: Fri, 15 Jul 2016 16:31:20 -0700 Subject: [PATCH] style: Rewrite the restyle hints code to allow different kinds of element snapshots, and use it for Gecko. This is a rewrite for how style interfaces with its consumers in order to allow different representations for an element snapshot. This also changes the requirements of an element snapshot, requiring them to only implement MatchAttr, instead of MatchAttrGeneric. This is important for stylo since implementing MatchAttrGeneric is way more difficult for us given the atom limitations. This also allows for more performant implementations in the Gecko side of things. --- components/script/dom/bindings/trace.rs | 3 +- components/script/dom/document.rs | 12 +- components/script/layout_wrapper.rs | 3 +- components/style/attr.rs | 2 +- components/style/dom.rs | 5 +- components/style/restyle_hints.rs | 353 ++++++++++--------- components/style/selector_impl.rs | 12 +- components/style/selector_matching.rs | 12 +- components/style/servo_selector_impl.rs | 84 ++++- ports/geckolib/gecko_bindings/tools/regen.py | 5 +- ports/geckolib/glue.rs | 22 ++ ports/geckolib/lib.rs | 2 + ports/geckolib/snapshot.rs | 149 ++++++++ ports/geckolib/snapshot_helpers.rs | 53 +++ ports/geckolib/wrapper.rs | 94 ++--- 15 files changed, 565 insertions(+), 246 deletions(-) create mode 100644 ports/geckolib/snapshot.rs create mode 100644 ports/geckolib/snapshot_helpers.rs diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 53912f63508..3c13572bf37 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -89,8 +89,7 @@ use string_cache::{Atom, Namespace, QualName}; use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto}; use style::element_state::*; use style::properties::PropertyDeclarationBlock; -use style::restyle_hints::ElementSnapshot; -use style::selector_impl::PseudoElement; +use style::selector_impl::{PseudoElement, ElementSnapshot}; use style::values::specified::Length; use url::Origin as UrlOrigin; use url::Url; diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 37037d8b35a..28edce553ad 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -122,7 +122,7 @@ use std::sync::Arc; use string_cache::{Atom, QualName}; use style::attr::AttrValue; use style::context::ReflowGoal; -use style::restyle_hints::ElementSnapshot; +use style::selector_impl::ElementSnapshot; use style::str::{split_html_space_chars, str_join}; use style::stylesheets::Stylesheet; use time; @@ -1851,7 +1851,10 @@ impl Document { pub fn element_state_will_change(&self, el: &Element) { let mut map = self.modified_elements.borrow_mut(); - let snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new()); + let snapshot = map.entry(JS::from_ref(el)) + .or_insert_with(|| { + ElementSnapshot::new(el.html_element_in_html_document()) + }); if snapshot.state.is_none() { snapshot.state = Some(el.state()); } @@ -1859,7 +1862,10 @@ impl Document { pub fn element_attr_will_change(&self, el: &Element) { let mut map = self.modified_elements.borrow_mut(); - let mut snapshot = map.entry(JS::from_ref(el)).or_insert(ElementSnapshot::new()); + let mut snapshot = map.entry(JS::from_ref(el)) + .or_insert_with(|| { + ElementSnapshot::new(el.html_element_in_html_document()) + }); if snapshot.attrs.is_none() { let attrs = el.attrs() .iter() diff --git a/components/script/layout_wrapper.rs b/components/script/layout_wrapper.rs index 8f22c369e4c..df44774dd5a 100644 --- a/components/script/layout_wrapper.rs +++ b/components/script/layout_wrapper.rs @@ -60,8 +60,7 @@ use style::dom::{PresentationalHintsSynthetizer, OpaqueNode, TDocument, TElement use style::element_state::*; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::refcell::{Ref, RefCell, RefMut}; -use style::restyle_hints::ElementSnapshot; -use style::selector_impl::{NonTSPseudoClass, ServoSelectorImpl}; +use style::selector_impl::{ElementSnapshot, NonTSPseudoClass, ServoSelectorImpl}; use style::sink::Push; use style::str::is_whitespace; use url::Url; 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; - fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement, ElementSnapshot)>; + fn drain_modified_elements(&self) -> Vec<(Self::ConcreteElement, + ::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. +/// 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; -#[derive(Clone)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -pub struct ElementSnapshot { - pub state: Option, - pub attrs: Option>, + /// 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; + + /// 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(&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, - 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, - 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, - E: MatchAttrGeneric, - E::Impl: SelectorImplExt { - fn match_attr(&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) +impl<'a, E> MatchAttr for ElementWrapper<'a, E> + where E: ElementExt, +{ + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + match self.snapshot { + Some(snapshot) if snapshot.has_attrs() + => snapshot.match_attr_suffix(attr, value), + _ => self.element.match_attr_suffix(attr, value) } } } -#[cfg(feature = "gecko")] -impl<'a, E> MatchAttr for ElementWrapper<'a, E> - where E: Element, - 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"); - } - - 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_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_includes(&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 { - panic!("Not implemented for Gecko - this system will need to be redesigned"); - } - - 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_substring(&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 { - panic!("Not implemented for Gecko - this system will need to be redesigned"); - } -} - impl<'a, E> Element for ElementWrapper<'a, E> - where E: Element, - E: MatchAttrGeneric, - E::Impl: SelectorImplExt { + where E: ElementExt, + E::Impl: SelectorImplExt { 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.element.parent_element().map(ElementWrapper::new) } + fn first_child_element(&self) -> Option { self.element.first_child_element().map(ElementWrapper::new) } + fn last_child_element(&self) -> Option { self.element.last_child_element().map(ElementWrapper::new) } + fn prev_sibling_element(&self) -> Option { self.element.prev_sibling_element().map(ElementWrapper::new) } + fn next_sibling_element(&self) -> Option { 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 { - 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(&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(&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(&self, el: &E, snapshot: &ElementSnapshot, current_state: ElementState) - -> RestyleHint - where E: Element + 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(&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 = ::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::AttrString> { + type Snapshot: restyle_hints::ElementSnapshot + '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(&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 - + 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, + pub attrs: Option>, + 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 { + self.state.clone() + } + + fn has_attrs(&self) -> bool { + self.attrs.is_some() + } + + fn id_attr(&self) -> Option { + 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(&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(&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> ElementExt for E { + type Snapshot = ServoElementSnapshot; + fn is_link(&self) -> bool { self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink) } diff --git a/ports/geckolib/gecko_bindings/tools/regen.py b/ports/geckolib/gecko_bindings/tools/regen.py index 4c3d4e5ac9c..8542a0ec8d1 100755 --- a/ports/geckolib/gecko_bindings/tools/regen.py +++ b/ports/geckolib/gecko_bindings/tools/regen.py @@ -45,6 +45,7 @@ COMPILATION_TARGETS = { "includes": [ "{}/dist/include/nsThemeConstants.h", "{}/dist/include/mozilla/dom/AnimationEffectReadOnlyBinding.h", + "{}/dist/include/mozilla/ServoElementSnapshot.h", ], "files": [ "{}/dist/include/nsStyleStruct.h", @@ -74,6 +75,8 @@ COMPILATION_TARGETS = { "nsDataHashtable.h", "nsCSSScanner.h", "nsTArray", "pair", "SheetParsingMode.h", "StaticPtr.h", "nsProxyRelease.h", "mozilla/dom/AnimationEffectReadOnlyBinding.h", + "nsChangeHint.h", "ServoElementSnapshot.h", + "EventStates.h", "nsAttrValue.h", "nsAttrName.h", "/Types.h", # <- Disallow UnionTypes.h "/utility", # <- Disallow xutility "nsINode.h", # <- For `NodeFlags`. @@ -122,7 +125,7 @@ COMPILATION_TARGETS = { "nsStyleCoord", "nsStyleGradientStop", "nsStyleImageLayers", "nsStyleImageLayers::Layer", "nsStyleImageLayers::LayerType", "nsStyleUnit", "nsStyleUnion", "nsStyleCoord::CalcValue", - "nsStyleCoord::Calc", + "nsStyleCoord::Calc", "nsRestyleHint", "ServoElementSnapshot", "SheetParsingMode", "nsMainThreadPtrHandle", "nsMainThreadPtrHolder", "nscolor", "nsFont", "FontFamilyList", diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 9d8e8ec5129..f38cd8374d4 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -13,7 +13,10 @@ use gecko_bindings::bindings::{RawServoStyleSet, RawServoStyleSheet, ServoComput use gecko_bindings::bindings::{ServoDeclarationBlock, ServoNodeData, ThreadSafePrincipalHolder}; use gecko_bindings::bindings::{ThreadSafeURIHolder, nsHTMLCSSStyleSheet}; use gecko_bindings::ptr::{GeckoArcPrincipal, GeckoArcURI}; +use gecko_bindings::structs::ServoElementSnapshot; +use gecko_bindings::structs::nsRestyleHint; use gecko_bindings::structs::{SheetParsingMode, nsIAtom}; +use snapshot::GeckoElementSnapshot; use std::mem::transmute; use std::ptr; use std::slice; @@ -437,3 +440,22 @@ pub extern "C" fn Servo_CSSSupports(property: *const u8, property_length: u32, Err(()) => false, } } + +#[no_mangle] +pub extern "C" fn Servo_ComputeRestyleHint(element: *mut RawGeckoElement, + snapshot: *mut ServoElementSnapshot, + raw_data: *mut RawServoStyleSet) -> nsRestyleHint { + let per_doc_data = unsafe { &mut *(raw_data as *mut PerDocumentStyleData) }; + let snapshot = unsafe { GeckoElementSnapshot::from_raw(snapshot) }; + let element = unsafe { GeckoElement::from_raw(element) }; + + // NB: This involves an FFI call, we can get rid of it easily if needed. + let current_state = element.get_state(); + + let hint = per_doc_data.stylist + .compute_restyle_hint(&element, &snapshot, + current_state); + + // NB: Binary representations match. + unsafe { transmute(hint.bits() as u32) } +} diff --git a/ports/geckolib/lib.rs b/ports/geckolib/lib.rs index 7f9813597c4..81923a9188c 100644 --- a/ports/geckolib/lib.rs +++ b/ports/geckolib/lib.rs @@ -22,6 +22,8 @@ extern crate util; mod context; mod data; +mod snapshot; +mod snapshot_helpers; #[allow(non_snake_case)] pub mod glue; mod traversal; diff --git a/ports/geckolib/snapshot.rs b/ports/geckolib/snapshot.rs new file mode 100644 index 00000000000..60aba7037e9 --- /dev/null +++ b/ports/geckolib/snapshot.rs @@ -0,0 +1,149 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ +use gecko_bindings::bindings; +use gecko_bindings::structs::ServoElementSnapshot; +use gecko_bindings::structs::ServoElementSnapshotFlags as Flags; +use selectors::parser::AttrSelector; +use snapshot_helpers; +use string_cache::Atom; +use style::element_state::ElementState; +use style::restyle_hints::ElementSnapshot; +use wrapper::AttrSelectorHelpers; + +// NB: This is sound, in some sense, because during computation of restyle hints +// the snapshot is kept alive by the modified elements table. +#[derive(Debug)] +pub struct GeckoElementSnapshot(*mut ServoElementSnapshot); + +impl GeckoElementSnapshot { + #[inline] + pub unsafe fn from_raw(raw: *mut ServoElementSnapshot) -> Self { + GeckoElementSnapshot(raw) + } + + #[inline] + fn is_html_element_in_html_document(&self) -> bool { + unsafe { (*self.0).mIsHTMLElementInHTMLDocument } + } + + #[inline] + fn has_any(&self, flags: Flags) -> bool { + unsafe { ((*self.0).mContains as u8 & flags as u8) != 0 } + } +} + +impl ::selectors::MatchAttr for GeckoElementSnapshot { + type AttrString = Atom; + + fn match_attr_has(&self, attr: &AttrSelector) -> bool { + unsafe { + bindings::Gecko_SnapshotHasAttr(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document())) + } + } + + fn match_attr_equals(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrEquals(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr(), + /* ignoreCase = */ false) + } + } + + fn match_attr_equals_ignore_ascii_case(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrEquals(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr(), + /* ignoreCase = */ true) + } + } + fn match_attr_includes(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrIncludes(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr()) + } + } + fn match_attr_dash(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrDashEquals(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr()) + } + } + fn match_attr_prefix(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrHasPrefix(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr()) + } + } + fn match_attr_substring(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrHasSubstring(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr()) + } + } + fn match_attr_suffix(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { + unsafe { + bindings::Gecko_SnapshotAttrHasSuffix(self.0, + attr.ns_or_null(), + attr.select_name(self.is_html_element_in_html_document()), + value.as_ptr()) + } + } +} + +impl ElementSnapshot for GeckoElementSnapshot { + fn state(&self) -> Option { + if self.has_any(Flags::State) { + Some(ElementState::from_bits_truncate(unsafe { (*self.0).mState as u16 })) + } else { + None + } + } + + #[inline] + fn has_attrs(&self) -> bool { + self.has_any(Flags::Attributes) + } + + fn id_attr(&self) -> Option { + let ptr = unsafe { + bindings::Gecko_SnapshotAtomAttrValue(self.0, + atom!("id").as_ptr()) + }; + + if ptr.is_null() { + None + } else { + Some(Atom::from(ptr)) + } + } + + // TODO: share logic with Element::{has_class, each_class}? + fn has_class(&self, name: &Atom) -> bool { + snapshot_helpers::has_class(self.0, + name, + bindings::Gecko_SnapshotClassOrClassList) + } + + fn each_class(&self, callback: F) + where F: FnMut(&Atom) + { + snapshot_helpers::each_class(self.0, + callback, + bindings::Gecko_SnapshotClassOrClassList) + } +} diff --git a/ports/geckolib/snapshot_helpers.rs b/ports/geckolib/snapshot_helpers.rs new file mode 100644 index 00000000000..ee7fdd14421 --- /dev/null +++ b/ports/geckolib/snapshot_helpers.rs @@ -0,0 +1,53 @@ +/* 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 http://mozilla.org/MPL/2.0/. */ + +//! Element an snapshot common logic. + +use gecko_bindings::structs::nsIAtom; +use std::{ptr, slice}; +use string_cache::Atom; + +pub type ClassOrClassList = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut *mut nsIAtom) -> u32; + +pub fn has_class(item: T, + name: &Atom, + getter: ClassOrClassList) -> bool +{ + unsafe { + let mut class: *mut nsIAtom = ptr::null_mut(); + let mut list: *mut *mut nsIAtom = ptr::null_mut(); + let length = getter(item, &mut class, &mut list); + match length { + 0 => false, + 1 => name.as_ptr() == class, + n => { + let classes = slice::from_raw_parts(list, n as usize); + classes.iter().any(|ptr| name.as_ptr() == *ptr) + } + } + } +} + + +pub fn each_class(item: T, + mut callback: F, + getter: ClassOrClassList) + where F: FnMut(&Atom) +{ + unsafe { + let mut class: *mut nsIAtom = ptr::null_mut(); + let mut list: *mut *mut nsIAtom = ptr::null_mut(); + let length = getter(item, &mut class, &mut list); + match length { + 0 => {} + 1 => Atom::with(class, &mut callback), + n => { + let classes = slice::from_raw_parts(list, n as usize); + for c in classes { + Atom::with(*c, &mut callback) + } + } + } + } +} diff --git a/ports/geckolib/wrapper.rs b/ports/geckolib/wrapper.rs index 73bfa8adcc3..d71666975a0 100644 --- a/ports/geckolib/wrapper.rs +++ b/ports/geckolib/wrapper.rs @@ -7,7 +7,6 @@ use gecko_bindings::bindings; use gecko_bindings::bindings::Gecko_ChildrenCount; use gecko_bindings::bindings::Gecko_ClassOrClassList; -use gecko_bindings::bindings::Gecko_GetElementId; use gecko_bindings::bindings::Gecko_GetNodeData; use gecko_bindings::bindings::ServoNodeData; use gecko_bindings::bindings::{Gecko_ElementState, Gecko_GetDocumentElement}; @@ -20,7 +19,6 @@ use gecko_bindings::bindings::{Gecko_GetPrevSibling, Gecko_GetPrevSiblingElement use gecko_bindings::bindings::{Gecko_GetServoDeclarationBlock, Gecko_IsHTMLElementInHTMLDocument}; use gecko_bindings::bindings::{Gecko_IsLink, Gecko_IsRootElement, Gecko_IsTextNode}; use gecko_bindings::bindings::{Gecko_IsUnvisitedLink, Gecko_IsVisitedLink}; -#[allow(unused_imports)] // Used in commented-out code. use gecko_bindings::bindings::{Gecko_LocalName, Gecko_Namespace, Gecko_NodeIsElement, Gecko_SetNodeData}; use gecko_bindings::bindings::{RawGeckoDocument, RawGeckoElement, RawGeckoNode}; use gecko_bindings::structs::nsIAtom; @@ -30,29 +28,25 @@ use libc::uintptr_t; use selectors::Element; use selectors::matching::DeclarationBlock; use selectors::parser::{AttrSelector, NamespaceConstraint}; +use snapshot::GeckoElementSnapshot; +use snapshot_helpers; use std::marker::PhantomData; use std::ops::BitOr; use std::ptr; -use std::slice; use std::sync::Arc; use string_cache::{Atom, BorrowedAtom, BorrowedNamespace, Namespace}; use style::data::PrivateStyleData; use style::dom::{OpaqueNode, PresentationalHintsSynthetizer}; use style::dom::{TDocument, TElement, TNode, TRestyleDamage, UnsafeNode}; use style::element_state::ElementState; -#[allow(unused_imports)] // Used in commented-out code. use style::error_reporting::StdoutErrorReporter; use style::gecko_selector_impl::{GeckoSelectorImpl, NonTSPseudoClass}; -#[allow(unused_imports)] // Used in commented-out code. use style::parser::ParserContextExtraData; -#[allow(unused_imports)] // Used in commented-out code. use style::properties::{ComputedValues, parse_style_attribute}; use style::properties::{PropertyDeclaration, PropertyDeclarationBlock}; use style::refcell::{Ref, RefCell, RefMut}; -use style::restyle_hints::ElementSnapshot; use style::selector_impl::ElementExt; use style::sink::Push; -#[allow(unused_imports)] // Used in commented-out code. use url::Url; pub type NonOpaqueStyleData = *mut RefCell; @@ -324,7 +318,7 @@ impl<'ld> TDocument for GeckoDocument<'ld> { } } - fn drain_modified_elements(&self) -> Vec<(GeckoElement<'ld>, ElementSnapshot)> { + fn drain_modified_elements(&self) -> Vec<(GeckoElement<'ld>, GeckoElementSnapshot)> { unimplemented!() /* let elements = unsafe { self.document.drain_modified_elements() }; @@ -497,48 +491,30 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { } fn get_id(&self) -> Option { - unsafe { - let ptr = Gecko_GetElementId(self.element); - if ptr.is_null() { - None - } else { - Some(Atom::from(ptr)) - } + let ptr = unsafe { + bindings::Gecko_AtomAttrValue(self.element, + atom!("id").as_ptr()) + }; + + if ptr.is_null() { + None + } else { + Some(Atom::from(ptr)) } } fn has_class(&self, name: &Atom) -> bool { - unsafe { - let mut class: *mut nsIAtom = ptr::null_mut(); - let mut list: *mut *mut nsIAtom = ptr::null_mut(); - let length = Gecko_ClassOrClassList(self.element, &mut class, &mut list); - match length { - 0 => false, - 1 => name.as_ptr() == class, - n => { - let classes = slice::from_raw_parts(list, n as usize); - classes.iter().any(|ptr| name.as_ptr() == *ptr) - } - } - } + snapshot_helpers::has_class(self.element, + name, + Gecko_ClassOrClassList) } - fn each_class(&self, mut callback: F) where F: FnMut(&Atom) { - unsafe { - let mut class: *mut nsIAtom = ptr::null_mut(); - let mut list: *mut *mut nsIAtom = ptr::null_mut(); - let length = Gecko_ClassOrClassList(self.element, &mut class, &mut list); - match length { - 0 => {} - 1 => Atom::with(class, &mut callback), - n => { - let classes = slice::from_raw_parts(list, n as usize); - for c in classes { - Atom::with(*c, &mut callback) - } - } - } - } + fn each_class(&self, callback: F) + where F: FnMut(&Atom) + { + snapshot_helpers::each_class(self.element, + callback, + Gecko_ClassOrClassList) } fn is_html_element_in_html_document(&self) -> bool { @@ -548,9 +524,9 @@ impl<'le> ::selectors::Element for GeckoElement<'le> { } } -trait AttrSelectorHelpers { +pub trait AttrSelectorHelpers { fn ns_or_null(&self) -> *mut nsIAtom; - fn select_name<'le>(&self, el: &GeckoElement<'le>) -> *mut nsIAtom; + fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom; } impl AttrSelectorHelpers for AttrSelector { @@ -561,13 +537,12 @@ impl AttrSelectorHelpers for AttrSelector { } } - fn select_name<'le>(&self, el: &GeckoElement<'le>) -> *mut nsIAtom { - if el.is_html_element_in_html_document() { + fn select_name(&self, is_html_element_in_html_document: bool) -> *mut nsIAtom { + if is_html_element_in_html_document { self.lower_name.as_ptr() } else { self.name.as_ptr() } - } } @@ -577,14 +552,14 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_HasAttr(self.element, attr.ns_or_null(), - attr.select_name(self)) + attr.select_name(self.is_html_element_in_html_document())) } } fn match_attr_equals(&self, attr: &AttrSelector, value: &Self::AttrString) -> bool { unsafe { bindings::Gecko_AttrEquals(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr(), /* ignoreCase = */ false) } @@ -593,7 +568,7 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_AttrEquals(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr(), /* ignoreCase = */ false) } @@ -602,7 +577,7 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_AttrIncludes(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } @@ -610,7 +585,7 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_AttrDashEquals(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } @@ -618,7 +593,7 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_AttrHasPrefix(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } @@ -626,7 +601,7 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_AttrHasSubstring(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } @@ -634,13 +609,16 @@ impl<'le> ::selectors::MatchAttr for GeckoElement<'le> { unsafe { bindings::Gecko_AttrHasSuffix(self.element, attr.ns_or_null(), - attr.select_name(self), + attr.select_name(self.is_html_element_in_html_document()), value.as_ptr()) } } } impl<'le> ElementExt for GeckoElement<'le> { + type Snapshot = GeckoElementSnapshot; + + #[inline] fn is_link(&self) -> bool { self.match_non_ts_pseudo_class(NonTSPseudoClass::AnyLink) }