Auto merge of #12668 - emilio:style-cache, r=SimonSapin,pcwalton

Rewrite the style sharing candidate cache

<!-- Please describe your changes on the following line: -->

See the first commit's description for a bit of background. I'm making the PR in this state just to verify against try I've handled all cases of possibly incorrect sharing and, if not, fix it.

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #12534

<!-- Either: -->
- [x] There are tests for these changes OR

<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/12668)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-08-17 16:34:30 -05:00 committed by GitHub
commit ec7efff14b
25 changed files with 575 additions and 279 deletions

View file

@ -33,7 +33,7 @@ range = {path = "../range"}
rustc-serialize = "0.3"
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
selectors = {version = "0.8", features = ["heap_size"]}
selectors = {version = "0.9", features = ["heap_size"]}
serde_macros = "0.8"
smallvec = "0.1"
string_cache = {version = "0.2.23", features = ["heap_size"]}

View file

@ -13,7 +13,7 @@ use gfx::display_list::OpaqueNode;
use script_layout_interface::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, RestyleDamage};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use std::mem;
use style::context::SharedStyleContext;
use style::context::{LocalStyleContext, SharedStyleContext, StyleContext};
use style::dom::TNode;
use style::selector_impl::ServoSelectorImpl;
use style::traversal::RestyleResult;
@ -81,6 +81,10 @@ impl<'lc, N> DomTraversalContext<N> for RecalcStyleAndConstructFlows<'lc>
fn process_postorder(&self, node: N) {
construct_flows_at(&self.context, self.root, node);
}
fn local_context(&self) -> &LocalStyleContext {
self.context.local_context()
}
}
/// A bottom-up, parallelizable traversal.

View file

@ -65,7 +65,7 @@ regex = "0.1.43"
rustc-serialize = "0.3"
script_layout_interface = {path = "../script_layout_interface"}
script_traits = {path = "../script_traits"}
selectors = {version = "0.8", features = ["heap_size"]}
selectors = {version = "0.9", features = ["heap_size"]}
serde = "0.8"
smallvec = "0.1"
string_cache = {version = "0.2.23", features = ["heap_size", "unstable"]}

View file

@ -444,6 +444,11 @@ impl<'le> TElement for ServoLayoutElement<'le> {
}
}
impl<'le> PartialEq for ServoLayoutElement<'le> {
fn eq(&self, other: &Self) -> bool {
self.as_node() == other.as_node()
}
}
impl<'le> ServoLayoutElement<'le> {
fn from_layout_js(el: LayoutJS<Element>) -> ServoLayoutElement<'le> {

View file

@ -27,7 +27,7 @@ plugins = {path = "../plugins"}
profile_traits = {path = "../profile_traits"}
range = {path = "../range"}
script_traits = {path = "../script_traits"}
selectors = {version = "0.8", features = ["heap_size"]}
selectors = {version = "0.9", features = ["heap_size"]}
string_cache = {version = "0.2.23", features = ["heap_size"]}
style = {path = "../style"}
url = {version = "1.2", features = ["heap_size"]}

View file

@ -1162,7 +1162,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1931,7 +1931,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1968,7 +1968,7 @@ dependencies = [
"profile_traits 0.0.1",
"range 0.0.1",
"script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2017,7 +2017,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.8.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2239,7 +2239,7 @@ dependencies = [
"plugins 0.0.1",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2259,7 +2259,7 @@ dependencies = [
"cssparser 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
"euclid 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1",
"style_traits 0.0.1",

View file

@ -38,7 +38,7 @@ num-traits = "0.1.32"
ordered-float = "0.2.2"
rand = "0.3"
rustc-serialize = "0.3"
selectors = "0.8.2"
selectors = "0.9"
serde = {version = "0.8", optional = true}
serde_macros = {version = "0.8", optional = true}
smallvec = "0.1"

View file

@ -7,7 +7,7 @@
use rand;
use rand::Rng;
use std::hash::{Hash, Hasher, SipHasher};
use std::slice::Iter;
use std::slice::{Iter, IterMut};
pub struct LRUCache<K, V> {
entries: Vec<(K, V)>,
@ -17,7 +17,7 @@ pub struct LRUCache<K, V> {
impl<K: PartialEq, V: Clone> LRUCache<K, V> {
pub fn new(size: usize) -> LRUCache<K, V> {
LRUCache {
entries: vec!(),
entries: vec![],
cache_size: size,
}
}
@ -36,6 +36,10 @@ impl<K: PartialEq, V: Clone> LRUCache<K, V> {
self.entries.iter()
}
pub fn iter_mut(&mut self) -> IterMut<(K, V)> {
self.entries.iter_mut()
}
pub fn insert(&mut self, key: K, val: V) {
if self.entries.len() == self.cache_size {
self.entries.remove(0);

View file

@ -201,7 +201,7 @@ pub trait PresentationalHintsSynthetizer {
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>;
}
pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
pub trait TElement : PartialEq + Sized + Copy + Clone + ElementExt + PresentationalHintsSynthetizer {
type ConcreteNode: TNode<ConcreteElement = Self, ConcreteDocument = Self::ConcreteDocument>;
type ConcreteDocument: TDocument<ConcreteNode = Self::ConcreteNode, ConcreteElement = Self>;
@ -236,6 +236,7 @@ pub trait TElement : Sized + Copy + Clone + ElementExt + PresentationalHintsSynt
unsafe { node.set_dirty(true); }
// XXX(emilio): For now, dirty implies dirty descendants if found.
} else if hint.contains(RESTYLE_DESCENDANTS) {
unsafe { node.set_dirty_descendants(true); }
let mut current = node.first_child();
while let Some(node) = current {
unsafe { node.set_dirty(true); }

View file

@ -12,21 +12,21 @@ use cache::{LRUCache, SimpleHashCache};
use cascade_info::CascadeInfo;
use context::{StyleContext, SharedStyleContext};
use data::PrivateStyleData;
use dom::{TElement, TNode, TRestyleDamage};
use dom::{TElement, TNode, TRestyleDamage, UnsafeNode};
use properties::longhands::display::computed_value as display;
use properties::{ComputedValues, PropertyDeclaration, cascade};
use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement};
use selector_impl::{TheSelectorImpl, PseudoElement};
use selector_matching::{DeclarationBlock, Stylist};
use selectors::bloom::BloomFilter;
use selectors::matching::{StyleRelations, AFFECTED_BY_PSEUDO_ELEMENTS};
use selectors::{Element, MatchAttr};
use sink::ForgetfulSink;
use smallvec::SmallVec;
use std::borrow::Borrow;
use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::slice::Iter;
use std::slice::IterMut;
use std::sync::Arc;
use string_cache::{Atom, Namespace};
use string_cache::Atom;
use traversal::RestyleResult;
use util::opts;
@ -178,155 +178,147 @@ impl ApplicableDeclarationsCache {
}
}
/// An LRU cache of the last few nodes seen, so that we can aggressively try to reuse their styles.
pub struct StyleSharingCandidateCache {
cache: LRUCache<StyleSharingCandidate, ()>,
/// Information regarding a candidate.
///
/// TODO: We can stick a lot more info here.
#[derive(Debug)]
struct StyleSharingCandidate {
/// The node, guaranteed to be an element.
node: UnsafeNode,
/// The cached computed style, here for convenience.
style: Arc<ComputedValues>,
/// The cached common style affecting attribute info.
common_style_affecting_attributes: Option<CommonStyleAffectingAttributes>,
}
#[derive(Clone)]
pub struct StyleSharingCandidate {
pub style: Arc<ComputedValues>,
pub parent_style: Arc<ComputedValues>,
pub local_name: Atom,
pub classes: Vec<Atom>,
pub namespace: Namespace,
pub common_style_affecting_attributes: CommonStyleAffectingAttributes,
pub link: bool,
}
impl PartialEq for StyleSharingCandidate {
impl PartialEq<StyleSharingCandidate> for StyleSharingCandidate {
fn eq(&self, other: &Self) -> bool {
self.node == other.node &&
arc_ptr_eq(&self.style, &other.style) &&
arc_ptr_eq(&self.parent_style, &other.parent_style) &&
self.local_name == other.local_name &&
self.classes == other.classes &&
self.link == other.link &&
self.namespace == other.namespace &&
self.common_style_affecting_attributes == other.common_style_affecting_attributes
}
}
impl StyleSharingCandidate {
/// Attempts to create a style sharing candidate from this node. Returns
/// the style sharing candidate or `None` if this node is ineligible for
/// style sharing.
#[allow(unsafe_code)]
fn new<N: TNode>(element: &N::ConcreteElement) -> Option<Self> {
let parent_element = match element.parent_element() {
None => return None,
Some(parent_element) => parent_element,
};
/// An LRU cache of the last few nodes seen, so that we can aggressively try to
/// reuse their styles.
///
/// Note that this cache is flushed every time we steal work from the queue, so
/// storing nodes here temporarily is safe.
///
/// NB: We store UnsafeNode's, but this is not unsafe. It's a shame being
/// generic over elements is unfeasible (you can make compile style without much
/// difficulty, but good luck with layout and all the types with assoc.
/// lifetimes).
pub struct StyleSharingCandidateCache {
cache: LRUCache<StyleSharingCandidate, ()>,
}
let style = unsafe {
match element.as_node().borrow_data_unchecked() {
None => return None,
Some(data_ref) => {
match (*data_ref).style {
None => return None,
Some(ref data) => (*data).clone(),
#[derive(Clone, Debug)]
pub enum CacheMiss {
Parent,
LocalName,
Namespace,
Link,
State,
IdAttr,
StyleAttr,
Class,
CommonStyleAffectingAttributes,
PresHints,
SiblingRules,
NonCommonAttrRules,
}
fn element_matches_candidate<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate,
candidate_element: &E,
shared_context: &SharedStyleContext)
-> Result<Arc<ComputedValues>, CacheMiss> {
macro_rules! miss {
($miss: ident) => {
return Err(CacheMiss::$miss);
}
}
if element.parent_element() != candidate_element.parent_element() {
miss!(Parent)
}
};
let parent_style = unsafe {
match parent_element.as_node().borrow_data_unchecked() {
None => return None,
Some(parent_data_ref) => {
match (*parent_data_ref).style {
None => return None,
Some(ref data) => (*data).clone(),
if *element.get_local_name() != *candidate_element.get_local_name() {
miss!(LocalName)
}
if *element.get_namespace() != *candidate_element.get_namespace() {
miss!(Namespace)
}
if element.is_link() != candidate_element.is_link() {
miss!(Link)
}
if element.get_state() != candidate_element.get_state() {
miss!(State)
}
if element.get_id().is_some() {
miss!(IdAttr)
}
};
if element.style_attribute().is_some() {
return None
miss!(StyleAttr)
}
let mut classes = Vec::new();
element.each_class(|c| classes.push(c.clone()));
Some(StyleSharingCandidate {
style: style,
parent_style: parent_style,
local_name: element.get_local_name().clone(),
classes: classes,
link: element.is_link(),
namespace: (*element.get_namespace()).clone(),
common_style_affecting_attributes:
create_common_style_affecting_attributes_from_element::<N::ConcreteElement>(&element)
})
if !have_same_class(element, candidate_element) {
miss!(Class)
}
pub fn can_share_style_with<E: TElement>(&self, element: &E) -> bool {
if element.get_local_name() != self.local_name.borrow() {
return false
if !have_same_common_style_affecting_attributes(element,
candidate,
candidate_element) {
miss!(CommonStyleAffectingAttributes)
}
let mut num_classes = 0;
let mut classes_match = true;
element.each_class(|c| {
num_classes += 1;
// Note that we could do this check more cheaply if we decided to
// only consider class lists as equal if the orders match, since
// we could then index by num_classes instead of using .contains().
if classes_match && !self.classes.contains(c) {
classes_match = false;
}
});
if !classes_match || num_classes != self.classes.len() {
return false;
if !have_same_presentational_hints(element, candidate_element) {
miss!(PresHints)
}
if element.get_namespace() != self.namespace.borrow() {
return false
if !match_same_sibling_affecting_rules(element,
candidate_element,
shared_context) {
miss!(SiblingRules)
}
let mut matching_rules = ForgetfulSink::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut matching_rules);
if !matching_rules.is_empty() {
return false;
if !match_same_not_common_style_affecting_attributes_rules(element,
candidate_element,
shared_context) {
miss!(NonCommonAttrRules)
}
// FIXME(pcwalton): It's probably faster to iterate over all the element's attributes and
// use the {common, rare}-style-affecting-attributes tables as lookup tables.
Ok(candidate.style.clone())
}
for attribute_info in &common_style_affecting_attributes() {
match attribute_info.mode {
CommonStyleAffectingAttributeMode::IsPresent(flag) => {
if self.common_style_affecting_attributes.contains(flag) !=
element.has_attr(&ns!(), &attribute_info.atom) {
return false
}
}
CommonStyleAffectingAttributeMode::IsEqual(ref target_value, flag) => {
let contains = self.common_style_affecting_attributes.contains(flag);
if element.has_attr(&ns!(), &attribute_info.atom) {
if !contains || !element.attr_equals(&ns!(), &attribute_info.atom, target_value) {
return false
}
} else if contains {
return false
}
}
fn have_same_common_style_affecting_attributes<E: TElement>(element: &E,
candidate: &mut StyleSharingCandidate,
candidate_element: &E) -> bool {
if candidate.common_style_affecting_attributes.is_none() {
candidate.common_style_affecting_attributes =
Some(create_common_style_affecting_attributes_from_element(candidate_element))
}
create_common_style_affecting_attributes_from_element(element) ==
candidate.common_style_affecting_attributes.unwrap()
}
fn have_same_presentational_hints<E: TElement>(element: &E, candidate: &E) -> bool {
let mut first = ForgetfulSink::new();
element.synthesize_presentational_hints_for_legacy_attributes(&mut first);
if cfg!(debug_assertions) {
let mut second = vec![];
candidate.synthesize_presentational_hints_for_legacy_attributes(&mut second);
debug_assert!(second.is_empty(),
"Should never have inserted an element with preshints in the cache!");
}
for attribute_name in &rare_style_affecting_attributes() {
if element.has_attr(&ns!(), attribute_name) {
return false
}
}
if element.is_link() != self.link {
return false
}
// TODO(pcwalton): We don't support visited links yet, but when we do there will need to
// be some logic here.
true
}
first.is_empty()
}
bitflags! {
@ -384,7 +376,33 @@ pub fn rare_style_affecting_attributes() -> [Atom; 3] {
[ atom!("bgcolor"), atom!("border"), atom!("colspan") ]
}
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 40;
fn have_same_class<E: TElement>(element: &E, candidate: &E) -> bool {
// XXX Efficiency here, I'm only validating ideas.
let mut first = vec![];
let mut second = vec![];
element.each_class(|c| first.push(c.clone()));
candidate.each_class(|c| second.push(c.clone()));
first == second
}
// TODO: These re-match the candidate every time, which is suboptimal.
#[inline]
fn match_same_not_common_style_affecting_attributes_rules<E: TElement>(element: &E,
candidate: &E,
ctx: &SharedStyleContext) -> bool {
ctx.stylist.match_same_not_common_style_affecting_attributes_rules(element, candidate)
}
#[inline]
fn match_same_sibling_affecting_rules<E: TElement>(element: &E,
candidate: &E,
ctx: &SharedStyleContext) -> bool {
ctx.stylist.match_same_sibling_affecting_rules(element, candidate)
}
static STYLE_SHARING_CANDIDATE_CACHE_SIZE: usize = 8;
impl StyleSharingCandidateCache {
pub fn new() -> Self {
@ -393,20 +411,62 @@ impl StyleSharingCandidateCache {
}
}
pub fn iter(&self) -> Iter<(StyleSharingCandidate, ())> {
self.cache.iter()
fn iter_mut(&mut self) -> IterMut<(StyleSharingCandidate, ())> {
self.cache.iter_mut()
}
pub fn insert_if_possible<N: TNode>(&mut self, element: &N::ConcreteElement) {
match StyleSharingCandidate::new::<N>(element) {
None => {}
Some(candidate) => self.cache.insert(candidate, ())
pub fn insert_if_possible<E: TElement>(&mut self,
element: &E,
relations: StyleRelations) {
use traversal::relations_are_shareable;
let parent = match element.parent_element() {
Some(element) => element,
None => {
debug!("Failing to insert to the cache: no parent element");
return;
}
};
// These are things we don't check in the candidate match because they
// are either uncommon or expensive.
if !relations_are_shareable(&relations) {
debug!("Failing to insert to the cache: {:?}", relations);
return;
}
let node = element.as_node();
let data = node.borrow_data().unwrap();
let style = data.style.as_ref().unwrap();
let box_style = style.get_box();
if box_style.transition_property_count() > 0 {
debug!("Failing to insert to the cache: transitions");
return;
}
if box_style.animation_name_count() > 0 {
debug!("Failing to insert to the cache: animations");
return;
}
debug!("Inserting into cache: {:?} with parent {:?}",
element.as_node().to_unsafe(), parent.as_node().to_unsafe());
self.cache.insert(StyleSharingCandidate {
node: node.to_unsafe(),
style: style.clone(),
common_style_affecting_attributes: None,
}, ());
}
pub fn touch(&mut self, index: usize) {
self.cache.touch(index);
}
pub fn clear(&mut self) {
self.cache.evict_all()
}
}
/// The results of attempting to share a style.
@ -562,31 +622,18 @@ impl<N: TNode> PrivateMatchMethods for N {}
trait PrivateElementMatchMethods: TElement {
fn share_style_with_candidate_if_possible(&self,
parent_node: Option<Self::ConcreteNode>,
candidate: &StyleSharingCandidate)
-> Option<Arc<ComputedValues>> {
let parent_node = match parent_node {
Some(ref parent_node) if parent_node.as_element().is_some() => parent_node,
Some(_) | None => return None,
parent_node: Self::ConcreteNode,
shared_context: &SharedStyleContext,
candidate: &mut StyleSharingCandidate)
-> Result<Arc<ComputedValues>, CacheMiss> {
debug_assert!(parent_node.is_element());
let candidate_element = unsafe {
Self::ConcreteNode::from_unsafe(&candidate.node).as_element().unwrap()
};
let parent_data: Option<&PrivateStyleData> = unsafe {
parent_node.borrow_data_unchecked().map(|d| &*d)
};
if let Some(parent_data_ref) = parent_data {
// Check parent style.
let parent_style = (*parent_data_ref).style.as_ref().unwrap();
if !arc_ptr_eq(parent_style, &candidate.parent_style) {
return None
}
// Check tag names, classes, etc.
if !candidate.can_share_style_with(self) {
return None
}
return Some(candidate.style.clone())
}
None
element_matches_candidate(self, candidate, &candidate_element,
shared_context)
}
}
@ -597,15 +644,19 @@ pub trait ElementMatchMethods : TElement {
stylist: &Stylist,
parent_bf: Option<&BloomFilter>,
applicable_declarations: &mut ApplicableDeclarations)
-> bool {
-> StyleRelations {
use traversal::relations_are_shareable;
let style_attribute = self.style_attribute().as_ref();
applicable_declarations.normal_shareable =
let mut relations =
stylist.push_applicable_declarations(self,
parent_bf,
style_attribute,
None,
&mut applicable_declarations.normal);
applicable_declarations.normal_shareable = relations_are_shareable(&relations);
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
stylist.push_applicable_declarations(self,
parent_bf,
@ -614,8 +665,14 @@ pub trait ElementMatchMethods : TElement {
applicable_declarations.per_pseudo.entry(pseudo).or_insert(vec![]));
});
applicable_declarations.normal_shareable &&
applicable_declarations.per_pseudo.values().all(|v| v.is_empty())
let has_pseudos =
applicable_declarations.per_pseudo.values().any(|v| !v.is_empty());
if has_pseudos {
relations |= AFFECTED_BY_PSEUDO_ELEMENTS;
}
relations
}
/// Attempts to share a style with another node. This method is unsafe because it depends on
@ -624,6 +681,7 @@ pub trait ElementMatchMethods : TElement {
unsafe fn share_style_if_possible(&self,
style_sharing_candidate_cache:
&mut StyleSharingCandidateCache,
shared_context: &SharedStyleContext,
parent: Option<Self::ConcreteNode>)
-> StyleSharingResult<<Self::ConcreteNode as TNode>::ConcreteRestyleDamage> {
if opts::get().disable_share_style_cache {
@ -633,17 +691,30 @@ pub trait ElementMatchMethods : TElement {
if self.style_attribute().is_some() {
return StyleSharingResult::CannotShare
}
if self.has_attr(&ns!(), &atom!("id")) {
return StyleSharingResult::CannotShare
}
for (i, &(ref candidate, ())) in style_sharing_candidate_cache.iter().enumerate() {
if let Some(shared_style) = self.share_style_with_candidate_if_possible(parent.clone(), candidate) {
let parent = match parent {
Some(parent) if parent.is_element() => parent,
_ => return StyleSharingResult::CannotShare,
};
for (i, &mut (ref mut candidate, ())) in style_sharing_candidate_cache.iter_mut().enumerate() {
let sharing_result = self.share_style_with_candidate_if_possible(parent,
shared_context,
candidate);
match sharing_result {
Ok(shared_style) => {
// Yay, cache hit. Share the style.
let node = self.as_node();
let style = &mut node.mutate_data().unwrap().style;
// TODO: add the display: none optimisation here too! Even
// better, factor it out/make it a bit more generic so Gecko
// can decide more easily if it knows that it's a child of
// replaced content, or similar stuff!
let damage =
match node.existing_style_for_restyle_damage((*style).as_ref(), None) {
Some(ref source) => {
@ -666,6 +737,22 @@ pub trait ElementMatchMethods : TElement {
return StyleSharingResult::StyleWasShared(i, damage, restyle_result)
}
Err(miss) => {
debug!("Cache miss: {:?}", miss);
// Cache miss, let's see what kind of failure to decide
// whether we keep trying or not.
match miss {
// Too expensive failure, give up, we don't want another
// one of these.
CacheMiss::CommonStyleAffectingAttributes |
CacheMiss::PresHints |
CacheMiss::SiblingRules |
CacheMiss::NonCommonAttrRules => break,
_ => {}
}
}
}
}
StyleSharingResult::CannotShare

View file

@ -12,6 +12,8 @@ use dom::{OpaqueNode, TNode, UnsafeNode};
use std::mem;
use std::sync::atomic::Ordering;
use traversal::{RestyleResult, DomTraversalContext};
use traversal::{STYLE_SHARING_CACHE_HITS, STYLE_SHARING_CACHE_MISSES};
use util::opts;
use workqueue::{WorkQueue, WorkUnit, WorkerProxy};
#[allow(dead_code)]
@ -42,13 +44,28 @@ pub fn run_queue_with_custom_work_data_type<To, F, SharedContext: Sync>(
pub fn traverse_dom<N, C>(root: N,
queue_data: &C::SharedContext,
queue: &mut WorkQueue<C::SharedContext, WorkQueueData>)
where N: TNode, C: DomTraversalContext<N> {
where N: TNode,
C: DomTraversalContext<N>
{
if opts::get().style_sharing_stats {
STYLE_SHARING_CACHE_HITS.store(0, Ordering::SeqCst);
STYLE_SHARING_CACHE_MISSES.store(0, Ordering::SeqCst);
}
run_queue_with_custom_work_data_type(queue, |queue| {
queue.push(WorkUnit {
fun: top_down_dom::<N, C>,
data: (Box::new(vec![root.to_unsafe()]), root.opaque()),
});
}, queue_data);
if opts::get().style_sharing_stats {
let hits = STYLE_SHARING_CACHE_HITS.load(Ordering::SeqCst);
let misses = STYLE_SHARING_CACHE_MISSES.load(Ordering::SeqCst);
println!("Style sharing stats:");
println!(" * Hits: {}", hits);
println!(" * Misses: {}", misses);
}
}
/// A parallel top-down DOM traversal.
@ -102,6 +119,10 @@ fn top_down_dom<N, C>(unsafe_nodes: UnsafeNodeList,
}
}
// NB: In parallel traversal mode we have to purge the LRU cache in order to
// be able to access it without races.
context.local_context().style_sharing_candidate_cache.borrow_mut().clear();
for chunk in discovered_child_nodes.chunks(CHUNK_SIZE) {
proxy.push(WorkUnit {
fun: top_down_dom::<N, C>,

View file

@ -6,6 +6,7 @@
use element_state::*;
use selector_impl::{ElementExt, TheSelectorImpl, NonTSPseudoClass, AttrValue};
use selectors::matching::StyleRelations;
use selectors::matching::matches_compound_selector;
use selectors::parser::{AttrSelector, Combinator, CompoundSelector, SimpleSelector, SelectorImpl};
use selectors::{Element, MatchAttr};
@ -348,7 +349,11 @@ impl DependencySet {
DependencySet { deps: Vec::new() }
}
pub fn note_selector(&mut self, selector: Arc<CompoundSelector<TheSelectorImpl>>) {
pub fn len(&self) -> usize {
self.deps.len()
}
pub fn note_selector(&mut self, selector: &Arc<CompoundSelector<TheSelectorImpl>>) {
let mut cur = selector;
let mut combinator: Option<Combinator> = None;
loop {
@ -370,7 +375,7 @@ impl DependencySet {
cur = match cur.next {
Some((ref sel, comb)) => {
combinator = Some(comb);
sel.clone()
sel
}
None => break,
}
@ -389,14 +394,19 @@ impl DependencySet {
-> RestyleHint
where E: ElementExt + Clone
{
debug!("About to calculate restyle hint for element. Deps: {}",
self.deps.len());
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) {
let old_el: ElementWrapper<E> = ElementWrapper::new_with_snapshot(el.clone(), snapshot);
let matched_then = matches_compound_selector(&*dep.selector, &old_el, None, &mut false);
let matches_now = matches_compound_selector(&*dep.selector, el, None, &mut false);
let matched_then =
matches_compound_selector(&*dep.selector, &old_el, None, &mut StyleRelations::empty());
let matches_now =
matches_compound_selector(&*dep.selector, el, None, &mut StyleRelations::empty());
if matched_then != matches_now {
hint.insert(combinator_to_restyle_hint(dep.combinator));
if hint.is_all() {

View file

@ -15,7 +15,9 @@ use selector_impl::{ElementExt, TheSelectorImpl, PseudoElement};
use selectors::Element;
use selectors::bloom::BloomFilter;
use selectors::matching::DeclarationBlock as GenericDeclarationBlock;
use selectors::matching::{Rule, SelectorMap};
use selectors::matching::{AFFECTED_BY_STYLE_ATTRIBUTE, AFFECTED_BY_PRESENTATIONAL_HINTS};
use selectors::matching::{Rule, SelectorMap, StyleRelations};
use selectors::parser::Selector;
use sink::Push;
use smallvec::VecLike;
use std::collections::HashMap;
@ -26,7 +28,6 @@ use style_traits::viewport::ViewportConstraints;
use stylesheets::{CSSRule, CSSRuleIteratorExt, Origin, Stylesheet};
use viewport::{MaybeNew, ViewportRuleCascade};
pub type DeclarationBlock = GenericDeclarationBlock<Vec<PropertyDeclaration>>;
/// This structure holds all the selectors and device characteristics
@ -76,6 +77,13 @@ pub struct Stylist {
/// Selector dependencies used to compute restyle hints.
state_deps: DependencySet,
/// Selectors in the page affecting siblings
sibling_affecting_selectors: Vec<Selector<TheSelectorImpl>>,
/// Selectors in the page matching elements with non-common style-affecting
/// attributes.
non_common_style_affecting_attributes_selectors: Vec<Selector<TheSelectorImpl>>,
}
impl Stylist {
@ -93,6 +101,10 @@ impl Stylist {
precomputed_pseudo_element_decls: HashMap::with_hasher(Default::default()),
rules_source_order: 0,
state_deps: DependencySet::new(),
// XXX remember resetting them!
sibling_affecting_selectors: vec![],
non_common_style_affecting_attributes_selectors: vec![]
};
TheSelectorImpl::each_eagerly_cascaded_pseudo_element(|pseudo| {
@ -120,6 +132,9 @@ impl Stylist {
self.rules_source_order = 0;
self.state_deps.clear();
self.sibling_affecting_selectors.clear();
self.non_common_style_affecting_attributes_selectors.clear();
for ref stylesheet in TheSelectorImpl::get_user_or_user_agent_stylesheets().iter() {
self.add_stylesheet(&stylesheet);
}
@ -178,8 +193,16 @@ impl Stylist {
append!(style_rule, normal);
append!(style_rule, important);
rules_source_order += 1;
for selector in &style_rule.selectors {
self.state_deps.note_selector(selector.compound_selectors.clone());
self.state_deps.note_selector(&selector.compound_selectors);
if selector.affects_siblings() {
self.sibling_affecting_selectors.push(selector.clone());
}
if selector.matches_non_common_style_affecting_attribute() {
self.non_common_style_affecting_attributes_selectors.push(selector.clone());
}
}
self.rules_source_order = rules_source_order;
@ -202,6 +225,14 @@ impl Stylist {
}
}
debug!("Stylist stats:");
debug!(" - Got {} sibling-affecting selectors",
self.sibling_affecting_selectors.len());
debug!(" - Got {} non-common-style-attribute-affecting selectors",
self.non_common_style_affecting_attributes_selectors.len());
debug!(" - Got {} deps for style-hint calculation",
self.state_deps.len());
TheSelectorImpl::each_precomputed_pseudo_element(|pseudo| {
// TODO: Consider not doing this and just getting the rules on the
// fly. It should be a bit slower, but we'd take rid of the
@ -310,11 +341,11 @@ impl Stylist {
parent_bf: Option<&BloomFilter>,
style_attribute: Option<&PropertyDeclarationBlock>,
pseudo_element: Option<&PseudoElement>,
applicable_declarations: &mut V)
-> bool
applicable_declarations: &mut V) -> StyleRelations
where E: Element<Impl=TheSelectorImpl> +
PresentationalHintsSynthetizer,
V: Push<DeclarationBlock> + VecLike<DeclarationBlock> {
V: Push<DeclarationBlock> + VecLike<DeclarationBlock>
{
assert!(!self.is_device_dirty);
assert!(style_attribute.is_none() || pseudo_element.is_none(),
"Style attributes do not apply to pseudo-elements");
@ -327,65 +358,82 @@ impl Stylist {
None => &self.element_map,
};
let mut shareable = true;
let mut relations = StyleRelations::empty();
debug!("Determining if style is shareable: pseudo: {}", pseudo_element.is_some());
// Step 1: Normal user-agent rules.
map.user_agent.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
&mut relations);
debug!("UA normal: {:?}", relations);
// Step 2: Presentational hints.
let length = applicable_declarations.len();
element.synthesize_presentational_hints_for_legacy_attributes(applicable_declarations);
if applicable_declarations.len() != length {
// Never share style for elements with preshints
shareable = false;
relations |= AFFECTED_BY_PRESENTATIONAL_HINTS;
}
debug!("preshints: {:?}", relations);
// Step 3: User and author normal rules.
map.user.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
&mut relations);
debug!("user normal: {:?}", relations);
map.author.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
&mut relations);
debug!("author normal: {:?}", relations);
// Step 4: Normal style attributes.
style_attribute.map(|sa| {
shareable = false;
if let Some(ref sa) = style_attribute {
relations |= AFFECTED_BY_STYLE_ATTRIBUTE;
Push::push(
applicable_declarations,
GenericDeclarationBlock::from_declarations(sa.normal.clone()))
});
GenericDeclarationBlock::from_declarations(sa.normal.clone()));
}
debug!("style attr: {:?}", relations);
// Step 5: Author-supplied `!important` rules.
map.author.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
&mut relations);
debug!("author important: {:?}", relations);
// Step 6: `!important` style attributes.
style_attribute.map(|sa| {
shareable = false;
if let Some(ref sa) = style_attribute {
Push::push(
applicable_declarations,
GenericDeclarationBlock::from_declarations(sa.important.clone()))
});
GenericDeclarationBlock::from_declarations(sa.important.clone()));
}
debug!("style attr important: {:?}", relations);
// Step 7: User and UA `!important` rules.
map.user.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
&mut relations);
debug!("user important: {:?}", relations);
map.user_agent.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
&mut relations);
shareable
debug!("UA important: {:?}", relations);
debug!("push_applicable_declarations: shareable: {:?}", relations);
relations
}
#[inline]
@ -398,6 +446,67 @@ impl Stylist {
&self.animations
}
pub fn match_same_not_common_style_affecting_attributes_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: ElementExt
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_compound_selector;
// XXX we can probably do better, the candidate should already know what
// rules it matches.
//
// XXX Could the bloom filter help here? Should be available.
for ref selector in self.non_common_style_affecting_attributes_selectors.iter() {
let element_matches = matches_compound_selector(&selector.compound_selectors,
element,
None,
&mut StyleRelations::empty());
let candidate_matches = matches_compound_selector(&selector.compound_selectors,
candidate,
None,
&mut StyleRelations::empty());
if element_matches != candidate_matches {
return false;
}
}
true
}
pub fn match_same_sibling_affecting_rules<E>(&self,
element: &E,
candidate: &E) -> bool
where E: ElementExt
{
use selectors::matching::StyleRelations;
use selectors::matching::matches_compound_selector;
// XXX we can probably do better, the candidate should already know what
// rules it matches.
//
// XXX The bloom filter would help here, and should be available.
for ref selector in self.sibling_affecting_selectors.iter() {
let element_matches = matches_compound_selector(&selector.compound_selectors,
element,
None,
&mut StyleRelations::empty());
let candidate_matches = matches_compound_selector(&selector.compound_selectors,
candidate,
None,
&mut StyleRelations::empty());
if element_matches != candidate_matches {
debug!("match_same_sibling_affecting_rules: Failure due to {:?}",
selector.compound_selectors);
return false;
}
}
true
}
pub fn compute_restyle_hint<E>(&self, element: &E,
snapshot: &E::Snapshot,
// NB: We need to pass current_state as an argument because

View file

@ -35,5 +35,6 @@ pub fn traverse_dom<N, C>(root: N,
if context.should_process(root) {
doit::<N, C>(&context, root);
}
// Clear the local LRU cache since we store stateful elements inside.
context.local_context().style_sharing_candidate_cache.borrow_mut().clear();
}

View file

@ -5,11 +5,13 @@
//! Traversing the DOM tree; the bloom filter.
use animation;
use context::{SharedStyleContext, StyleContext};
use context::{LocalStyleContext, SharedStyleContext, StyleContext};
use dom::{OpaqueNode, TElement, TNode, TRestyleDamage, UnsafeNode};
use matching::{ApplicableDeclarations, ElementMatchMethods, MatchMethods, StyleSharingResult};
use selectors::bloom::BloomFilter;
use selectors::matching::StyleRelations;
use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use tid::tid;
use util::opts;
use values::HasViewportPercentage;
@ -27,6 +29,10 @@ pub enum RestyleResult {
Stop,
}
/// Style sharing candidate cache stats. These are only used when
/// `-Z style-sharing-stats` is given.
pub static STYLE_SHARING_CACHE_HITS: AtomicUsize = ATOMIC_USIZE_INIT;
pub static STYLE_SHARING_CACHE_MISSES: AtomicUsize = ATOMIC_USIZE_INIT;
/// A pair of the bloom filter used for css selector matching, and the node to
/// which it applies. This is used to efficiently do `Descendant` selector
@ -191,6 +197,19 @@ pub trait DomTraversalContext<N: TNode> {
}
}
}
fn local_context(&self) -> &LocalStyleContext;
}
/// Determines the amount of relations where we're going to share style.
#[inline]
pub fn relations_are_shareable(relations: &StyleRelations) -> bool {
use selectors::matching::*;
!relations.intersects(AFFECTED_BY_ID_SELECTOR |
AFFECTED_BY_PSEUDO_ELEMENTS | AFFECTED_BY_STATE |
AFFECTED_BY_NON_COMMON_STYLE_AFFECTING_ATTRIBUTE_SELECTOR |
AFFECTED_BY_STYLE_ATTRIBUTE |
AFFECTED_BY_PRESENTATIONAL_HINTS)
}
pub fn ensure_node_styled<'a, N, C>(node: N,
@ -301,6 +320,7 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
Some(element) => {
unsafe {
element.share_style_if_possible(style_sharing_candidate_cache,
context.shared_context(),
parent_opt.clone())
}
},
@ -312,20 +332,30 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
StyleSharingResult::CannotShare => {
let mut applicable_declarations = ApplicableDeclarations::new();
let relations;
let shareable_element = match node.as_element() {
Some(element) => {
if opts::get().style_sharing_stats {
STYLE_SHARING_CACHE_MISSES.fetch_add(1, Ordering::Relaxed);
}
// Perform the CSS selector matching.
let stylist = &context.shared_context().stylist;
if element.match_element(&**stylist,
relations = element.match_element(&**stylist,
Some(&*bf),
&mut applicable_declarations) {
&mut applicable_declarations);
debug!("Result of selector matching: {:?}", relations);
if relations_are_shareable(&relations) {
Some(element)
} else {
None
}
},
None => {
relations = StyleRelations::empty();
if node.has_changed() {
node.set_restyle_damage(N::ConcreteRestyleDamage::rebuild_and_reflow())
}
@ -342,11 +372,14 @@ pub fn recalc_style_at<'a, N, C>(context: &'a C,
// Add ourselves to the LRU cache.
if let Some(element) = shareable_element {
style_sharing_candidate_cache.insert_if_possible::<'ln, N>(&element);
style_sharing_candidate_cache.insert_if_possible(&element, relations);
}
}
StyleSharingResult::StyleWasShared(index, damage, restyle_result_cascade) => {
restyle_result = restyle_result_cascade;
StyleSharingResult::StyleWasShared(index, damage, cached_restyle_result) => {
restyle_result = cached_restyle_result;
if opts::get().style_sharing_stats {
STYLE_SHARING_CACHE_HITS.fetch_add(1, Ordering::Relaxed);
}
style_sharing_candidate_cache.touch(index);
node.set_restyle_damage(damage);
}

View file

@ -172,6 +172,9 @@ pub struct Opts {
/// Whether Style Sharing Cache is used
pub disable_share_style_cache: bool,
/// Whether to show in stdout style sharing cache stats after a restyle.
pub style_sharing_stats: bool,
/// Translate mouse input into touch events.
pub convert_mouse_to_touch: bool,
@ -275,6 +278,9 @@ pub struct DebugOptions {
/// Disable the style sharing cache.
pub disable_share_style_cache: bool,
/// Whether to show in stdout style sharing cache stats after a restyle.
pub style_sharing_stats: bool,
/// Translate mouse input into touch events.
pub convert_mouse_to_touch: bool,
@ -331,6 +337,7 @@ impl DebugOptions {
"paint-flashing" => debug_options.paint_flashing = true,
"trace-layout" => debug_options.trace_layout = true,
"disable-share-style-cache" => debug_options.disable_share_style_cache = true,
"style-sharing-stats" => debug_options.style_sharing_stats = true,
"convert-mouse-to-touch" => debug_options.convert_mouse_to_touch = true,
"replace-surrogates" => debug_options.replace_surrogates = true,
"gc-profile" => debug_options.gc_profile = true,
@ -512,6 +519,7 @@ pub fn default_opts() -> Opts {
profile_script_events: false,
profile_heartbeats: false,
disable_share_style_cache: false,
style_sharing_stats: false,
convert_mouse_to_touch: false,
exit_after_load: false,
no_native_titlebar: false,
@ -817,6 +825,7 @@ pub fn from_cmdline_args(args: &[String]) -> ArgumentParsingResult {
dump_layer_tree: debug_options.dump_layer_tree,
relayout_event: debug_options.relayout_event,
disable_share_style_cache: debug_options.disable_share_style_cache,
style_sharing_stats: debug_options.style_sharing_stats,
convert_mouse_to_touch: debug_options.convert_mouse_to_touch,
exit_after_load: opt_match.opt_present("x"),
no_native_titlebar: do_not_use_native_titlebar,

10
ports/cef/Cargo.lock generated
View file

@ -1070,7 +1070,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1783,7 +1783,7 @@ dependencies = [
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"script_layout_interface 0.0.1",
"script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1820,7 +1820,7 @@ dependencies = [
"profile_traits 0.0.1",
"range 0.0.1",
"script_traits 0.0.1",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"string_cache 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1859,7 +1859,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.8.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2122,7 +2122,7 @@ dependencies = [
"plugins 0.0.1",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -11,7 +11,7 @@ dependencies = [
"libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"style 0.0.1",
"style_traits 0.0.1",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -170,7 +170,7 @@ dependencies = [
"gecko_bindings 0.0.1",
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -311,7 +311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "selectors"
version = "0.8.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -367,7 +367,7 @@ dependencies = [
"ordered-float 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
"selectors 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"style_traits 0.0.1",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",

View file

@ -19,7 +19,7 @@ lazy_static = "0.2"
libc = "0.2"
log = {version = "0.3.5", features = ["release_max_level_info"]}
num_cpus = "0.2.2"
selectors = "0.8"
selectors = "0.9"
style = {path = "../../components/style", features = ["gecko"]}
style_traits = {path = "../../components/style_traits"}
url = "1.2"

View file

@ -14,5 +14,5 @@ cfg-if = "0.1.0"
gecko_bindings = {version = "0.0.1", path = "../gecko_bindings"}
heapsize = "0.3.5"
libc = "0.2"
selectors = "0.8"
selectors = "0.9"
serde = "0.8"

View file

@ -4,7 +4,7 @@
use context::StandaloneStyleContext;
use std::mem;
use style::context::SharedStyleContext;
use style::context::{LocalStyleContext, SharedStyleContext, StyleContext};
use style::dom::OpaqueNode;
use style::traversal::RestyleResult;
use style::traversal::{DomTraversalContext, recalc_style_at};
@ -42,4 +42,8 @@ impl<'lc, 'ln> DomTraversalContext<GeckoNode<'ln>> for RecalcStyleOnly<'lc> {
/// We don't use the post-order traversal for anything.
fn needs_postorder_traversal(&self) -> bool { false }
fn local_context(&self) -> &LocalStyleContext {
self.context.local_context()
}
}

View file

@ -459,6 +459,12 @@ impl<'le> TElement for GeckoElement<'le> {
}
}
impl<'le> PartialEq for GeckoElement<'le> {
fn eq(&self, other: &Self) -> bool {
self.element == other.element
}
}
impl<'le> PresentationalHintsSynthetizer for GeckoElement<'le> {
fn synthesize_presentational_hints_for_legacy_attributes<V>(&self, _hints: &mut V)
where V: Push<DeclarationBlock<Vec<PropertyDeclaration>>>

View file

@ -14,7 +14,7 @@ app_units = "0.3"
cssparser = {version = "0.5.7", features = ["heap_size"]}
euclid = "0.9"
rustc-serialize = "0.3"
selectors = {version = "0.8", features = ["heap_size"]}
selectors = {version = "0.9", features = ["heap_size"]}
string_cache = {version = "0.2.23", features = ["heap_size"]}
style = {path = "../../../components/style"}
style_traits = {path = "../../../components/style_traits"}

View file

@ -1,3 +0,0 @@
[flexbox_columns.htm]
type: reftest
expected: FAIL

View file

@ -29,7 +29,11 @@
* we're not getting as much test coverage there as we'd like. When we
* implement attribute-based restyle hints, we can stop dirtying the subtree
* on attribute modifications, and these tests will start to be more useful.
*
* Also, note that we use window.onload in order to prevent the restyle
* hints to interact with any content appended notifications.
*/
window.onload = function() {
window.dummy = 0;
var $ = document.getElementById.bind(document);
function syncRestyle() { window.dummy += $("fs2").offsetTop; }
@ -41,5 +45,6 @@
$('cb').checked = true;
syncRestyle();
$('fs2').disabled = true;
}
</script>
</body>