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.
This commit is contained in:
Emilio Cobos Álvarez 2016-07-15 16:31:20 -07:00
parent ca9bc23b39
commit 611e611215
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
15 changed files with 565 additions and 246 deletions

View file

@ -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;

View file

@ -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()

View file

@ -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;

View file

@ -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),

View file

@ -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 {

View file

@ -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<ElementState>;
#[derive(Clone)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct ElementSnapshot {
pub state: Option<ElementState>,
pub attrs: Option<Vec<(AttrIdentifier, AttrValue)>>,
/// 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)
impl<'a, E> MatchAttr for ElementWrapper<'a, E>
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 {
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<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");
}
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<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) {

View file

@ -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;
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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",

View file

@ -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) }
}

View file

@ -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;

149
ports/geckolib/snapshot.rs Normal file
View file

@ -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<ElementState> {
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<Atom> {
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<F>(&self, callback: F)
where F: FnMut(&Atom)
{
snapshot_helpers::each_class(self.0,
callback,
bindings::Gecko_SnapshotClassOrClassList)
}
}

View file

@ -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<T> = unsafe extern fn (T, *mut *mut nsIAtom, *mut *mut *mut nsIAtom) -> u32;
pub fn has_class<T>(item: T,
name: &Atom,
getter: ClassOrClassList<T>) -> 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<F, T>(item: T,
mut callback: F,
getter: ClassOrClassList<T>)
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)
}
}
}
}
}

View file

@ -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<PrivateStyleData>;
@ -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<Atom> {
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<F>(&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<F>(&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)
}