aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/traversal.rs14
-rw-r--r--components/style/data.rs9
-rw-r--r--components/style/gecko/traversal.rs17
-rw-r--r--components/style/parallel.rs17
-rw-r--r--components/style/sequential.rs12
-rw-r--r--components/style/traversal.rs297
6 files changed, 171 insertions, 195 deletions
diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs
index cf75a47bbf8..f821b4a2806 100644
--- a/components/layout/traversal.rs
+++ b/components/layout/traversal.rs
@@ -12,6 +12,7 @@ use flow::{CAN_BE_FRAGMENTED, Flow, ImmutableFlowUtils, PostorderFlowTraversal};
use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutNode};
use servo_config::opts;
use style::context::{SharedStyleContext, StyleContext};
+use style::data::ElementData;
use style::dom::{NodeInfo, TElement, TNode};
use style::selector_parser::RestyleDamage;
use style::servo::restyle_damage::{BUBBLE_ISIZES, REFLOW, REFLOW_OUT_OF_FLOW, REPAINT};
@@ -53,8 +54,11 @@ impl<'a, E> DomTraversal<E> for RecalcStyleAndConstructFlows<'a>
E::ConcreteNode: LayoutNode,
E::FontMetricsProvider: Send,
{
- fn process_preorder(&self, traversal_data: &PerLevelTraversalData,
- context: &mut StyleContext<E>, node: E::ConcreteNode) {
+ fn process_preorder<F>(&self, traversal_data: &PerLevelTraversalData,
+ context: &mut StyleContext<E>, node: E::ConcreteNode,
+ note_child: F)
+ where F: FnMut(E::ConcreteNode)
+ {
// FIXME(pcwalton): Stop allocating here. Ideally this should just be
// done by the HTML parser.
unsafe { node.initialize_data() };
@@ -62,7 +66,7 @@ impl<'a, E> DomTraversal<E> for RecalcStyleAndConstructFlows<'a>
if !node.is_text_node() {
let el = node.as_element().unwrap();
let mut data = el.mutate_data().unwrap();
- recalc_style_at(self, traversal_data, context, el, &mut data);
+ recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
}
}
@@ -70,13 +74,13 @@ impl<'a, E> DomTraversal<E> for RecalcStyleAndConstructFlows<'a>
construct_flows_at(&self.context, node);
}
- fn text_node_needs_traversal(node: E::ConcreteNode) -> bool {
+ fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool {
// Text nodes never need styling. However, there are two cases they may need
// flow construction:
// (1) They child doesn't yet have layout data (preorder traversal initializes it).
// (2) The parent element has restyle damage (so the text flow also needs fixup).
node.get_raw_data().is_none() ||
- node.parent_node().unwrap().to_threadsafe().restyle_damage() != RestyleDamage::empty()
+ parent_data.restyle.damage != RestyleDamage::empty()
}
fn shared_context(&self) -> &SharedStyleContext {
diff --git a/components/style/data.rs b/components/style/data.rs
index 658c7a42a5d..2478f9761c3 100644
--- a/components/style/data.rs
+++ b/components/style/data.rs
@@ -70,11 +70,16 @@ impl RestyleData {
/// Returns whether this element or any ancestor is going to be
/// reconstructed.
pub fn reconstructed_self_or_ancestor(&self) -> bool {
- self.reconstructed_ancestor() ||
+ self.reconstructed_ancestor() || self.reconstructed_self()
+ }
+
+ /// Returns whether this element is going to be reconstructed.
+ pub fn reconstructed_self(&self) -> bool {
self.damage.contains(RestyleDamage::reconstruct())
}
- /// Returns whether any ancestor of this element was restyled.
+ /// Returns whether any ancestor of this element is going to be
+ /// reconstructed.
fn reconstructed_ancestor(&self) -> bool {
self.flags.contains(ANCESTOR_WAS_RECONSTRUCTED)
}
diff --git a/components/style/gecko/traversal.rs b/components/style/gecko/traversal.rs
index 9b0770318ce..98a587f1919 100644
--- a/components/style/gecko/traversal.rs
+++ b/components/style/gecko/traversal.rs
@@ -5,7 +5,7 @@
//! Gecko-specific bits for the styling DOM traversal.
use context::{SharedStyleContext, StyleContext};
-use dom::{NodeInfo, TNode, TElement};
+use dom::{TNode, TElement};
use gecko::wrapper::{GeckoElement, GeckoNode};
use traversal::{DomTraversal, PerLevelTraversalData, TraversalDriver, recalc_style_at};
@@ -27,15 +27,16 @@ impl<'a> RecalcStyleOnly<'a> {
}
impl<'recalc, 'le> DomTraversal<GeckoElement<'le>> for RecalcStyleOnly<'recalc> {
- fn process_preorder(&self,
- traversal_data: &PerLevelTraversalData,
- context: &mut StyleContext<GeckoElement<'le>>,
- node: GeckoNode<'le>)
+ fn process_preorder<F>(&self,
+ traversal_data: &PerLevelTraversalData,
+ context: &mut StyleContext<GeckoElement<'le>>,
+ node: GeckoNode<'le>,
+ note_child: F)
+ where F: FnMut(GeckoNode<'le>),
{
- if node.is_element() {
- let el = node.as_element().unwrap();
+ if let Some(el) = node.as_element() {
let mut data = unsafe { el.ensure_data() };
- recalc_style_at(self, traversal_data, context, el, &mut data);
+ recalc_style_at(self, traversal_data, context, el, &mut data, note_child);
}
}
diff --git a/components/style/parallel.rs b/components/style/parallel.rs
index dd7072a201e..358eeffd52c 100644
--- a/components/style/parallel.rs
+++ b/components/style/parallel.rs
@@ -29,7 +29,6 @@ use rayon;
use scoped_tls::ScopedTLS;
use smallvec::SmallVec;
use std::borrow::Borrow;
-use std::mem;
use time;
use traversal::{DomTraversal, PerLevelTraversalData, PreTraverseToken};
@@ -187,10 +186,9 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode<E::ConcreteNode>],
//
// Which are not at all uncommon.
if !discovered_child_nodes.is_empty() {
- let children = mem::replace(&mut discovered_child_nodes, Default::default());
let mut traversal_data_copy = traversal_data.clone();
traversal_data_copy.current_dom_depth += 1;
- traverse_nodes(&*children,
+ traverse_nodes(&*discovered_child_nodes,
DispatchMode::NotTailCall,
recursion_depth,
root,
@@ -199,17 +197,16 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode<E::ConcreteNode>],
pool,
traversal,
tls);
+ discovered_child_nodes.clear();
}
let node = **n;
let mut children_to_process = 0isize;
- traversal.process_preorder(&traversal_data, &mut context, node);
- if let Some(el) = node.as_element() {
- traversal.traverse_children(&mut context, el, |_context, kid| {
- children_to_process += 1;
- discovered_child_nodes.push(unsafe { SendNode::new(kid) })
- });
- }
+ traversal.process_preorder(&traversal_data, &mut context, node, |n| {
+ children_to_process += 1;
+ let send_n = unsafe { SendNode::new(n) };
+ discovered_child_nodes.push(send_n);
+ });
traversal.handle_postorder_traversal(&mut context, root, node,
children_to_process);
diff --git a/components/style/sequential.rs b/components/style/sequential.rs
index 49bb3bb0c4f..3e06db02ac0 100644
--- a/components/style/sequential.rs
+++ b/components/style/sequential.rs
@@ -52,14 +52,10 @@ pub fn traverse_dom<E, D>(traversal: &D,
while let Some(WorkItem(node, depth)) = discovered.pop_front() {
let mut children_to_process = 0isize;
let traversal_data = PerLevelTraversalData { current_dom_depth: depth };
- traversal.process_preorder(&traversal_data, &mut context, node);
-
- if let Some(el) = node.as_element() {
- traversal.traverse_children(&mut context, el, |_context, kid| {
- children_to_process += 1;
- discovered.push_back(WorkItem(kid, depth + 1))
- });
- }
+ traversal.process_preorder(&traversal_data, &mut context, node, |n| {
+ children_to_process += 1;
+ discovered.push_back(WorkItem(n, depth + 1));
+ });
traversal.handle_postorder_traversal(&mut context, root.as_node().opaque(),
node, children_to_process);
diff --git a/components/style/traversal.rs b/components/style/traversal.rs
index a5c624eb374..1038e6e2012 100644
--- a/components/style/traversal.rs
+++ b/components/style/traversal.rs
@@ -85,20 +85,6 @@ impl PreTraverseToken {
}
}
-/// Enum to prevent duplicate logging.
-pub enum LogBehavior {
- /// We should log.
- MayLog,
- /// We shouldn't log.
- DontLog,
-}
-use self::LogBehavior::*;
-impl LogBehavior {
- fn allow(&self) -> bool {
- matches!(*self, MayLog)
- }
-}
-
/// The kind of traversals we could perform.
#[derive(Debug, Copy, Clone)]
pub enum TraversalDriver {
@@ -132,10 +118,15 @@ fn is_servo_nonincremental_layout() -> bool {
/// Gecko and Servo.
pub trait DomTraversal<E: TElement> : Sync {
/// Process `node` on the way down, before its children have been processed.
- fn process_preorder(&self,
- data: &PerLevelTraversalData,
- context: &mut StyleContext<E>,
- node: E::ConcreteNode);
+ ///
+ /// The callback is invoked for each child node that should be processed by
+ /// the traversal.
+ fn process_preorder<F>(&self,
+ data: &PerLevelTraversalData,
+ context: &mut StyleContext<E>,
+ node: E::ConcreteNode,
+ note_child: F)
+ where F: FnMut(E::ConcreteNode);
/// Process `node` on the way up, after its children have been processed.
///
@@ -235,15 +226,28 @@ pub trait DomTraversal<E: TElement> : Sync {
};
}
- // Look at whether there has been any attribute or state change, and
- // invalidate our style, and the one of our siblings and descendants as
- // needed.
- if let Some(mut data) = root.mutate_data() {
+ let flags = shared_context.traversal_flags;
+ let data = root.mutate_data();
+ let should_traverse = if data.as_ref().map_or(true, |d| !d.has_styles()) {
+ !flags.for_animation_only()
+ } else {
+ let mut data = data.unwrap();
+ // Look at whether there has been any attribute or state change, and
+ // invalidate our style, and the one of our siblings and descendants as
+ // needed.
data.invalidate_style_if_needed(root, shared_context);
- }
+
+ let parent = root.traversal_parent();
+ let parent_data = match parent {
+ None => None,
+ Some(ref x) => Some(x.borrow_data().unwrap())
+ };
+ let parent_data_borrow = parent_data.as_ref().map(|x| &**x);
+ Self::element_needs_traversal(root, flags, &*data, parent_data_borrow)
+ };
PreTraverseToken {
- traverse: Self::node_needs_traversal(root.as_node(), traversal_flags),
+ traverse: should_traverse,
unstyled_children_only: false,
}
}
@@ -251,16 +255,23 @@ pub trait DomTraversal<E: TElement> : Sync {
/// Returns true if traversal should visit a text node. The style system
/// never processes text nodes, but Servo overrides this to visit them for
/// flow construction when necessary.
- fn text_node_needs_traversal(node: E::ConcreteNode) -> bool {
+ fn text_node_needs_traversal(node: E::ConcreteNode, _parent_data: &ElementData) -> bool {
debug_assert!(node.is_text_node());
false
}
- /// Returns true if traversal is needed for the given node and subtree.
- fn node_needs_traversal(
- node: E::ConcreteNode,
- traversal_flags: TraversalFlags
+ /// Returns true if traversal is needed for the given element and subtree.
+ ///
+ /// The caller passes |parent_data|, which is only null if there is no
+ /// parent.
+ fn element_needs_traversal(
+ el: E,
+ traversal_flags: TraversalFlags,
+ data: &ElementData,
+ parent_data: Option<&ElementData>,
) -> bool {
+ debug_assert!(data.has_styles(), "Caller should check this");
+
// Non-incremental layout visits every node.
if is_servo_nonincremental_layout() {
return true;
@@ -270,11 +281,6 @@ pub trait DomTraversal<E: TElement> : Sync {
return true;
}
- let el = match node.as_element() {
- None => return Self::text_node_needs_traversal(node),
- Some(el) => el,
- };
-
// If the element is native-anonymous and an ancestor frame will be
// reconstructed, the child and all its descendants will be destroyed.
// In that case, we wouldn't need to traverse the subtree...
@@ -289,8 +295,7 @@ pub trait DomTraversal<E: TElement> : Sync {
// But it may be that we no longer match, so detect that case and act
// appropriately here.
if el.is_native_anonymous() {
- if let Some(parent) = el.traversal_parent() {
- let parent_data = parent.borrow_data().unwrap();
+ if let Some(parent_data) = parent_data {
let going_to_reframe =
parent_data.restyle.reconstructed_self_or_ancestor();
@@ -322,22 +327,8 @@ pub trait DomTraversal<E: TElement> : Sync {
// if the element has animation only dirty descendants bit,
// animation-only restyle hint or recascade.
if traversal_flags.for_animation_only() {
- // Skip elements that have no style data since animation-only
- // restyle is not necessary for the elements.
- let data = match el.borrow_data() {
- Some(d) => d,
- None => return false,
- };
-
- if !data.has_styles() {
- return false;
- }
-
- if el.has_animation_only_dirty_descendants() {
- return true;
- }
-
- return data.restyle.hint.has_animation_hint() ||
+ return el.has_animation_only_dirty_descendants() ||
+ data.restyle.hint.has_animation_hint() ||
data.restyle.hint.has_recascade_self();
}
@@ -347,18 +338,6 @@ pub trait DomTraversal<E: TElement> : Sync {
return true;
}
- // Check the element data. If it doesn't exist, we need to visit the
- // element.
- let data = match el.borrow_data() {
- Some(d) => d,
- None => return true,
- };
-
- // If we don't have any style data, we need to visit the element.
- if !data.has_styles() {
- return true;
- }
-
// If we have a restyle hint or need to recascade, we need to visit the
// element.
//
@@ -385,17 +364,12 @@ pub trait DomTraversal<E: TElement> : Sync {
false
}
- /// Returns true if traversal of this element's children is allowed. We use
- /// this to cull traversal of various subtrees.
- ///
- /// This may be called multiple times when processing an element, so we pass
- /// a parameter to keep the logs tidy.
- fn should_traverse_children(
+ /// Returns true if we want to cull this subtree from the travesal.
+ fn should_cull_subtree(
&self,
context: &mut StyleContext<E>,
parent: E,
parent_data: &ElementData,
- log: LogBehavior
) -> bool {
// See the comment on `cascade_node` for why we allow this on Gecko.
debug_assert!(cfg!(feature = "gecko") ||
@@ -403,11 +377,8 @@ pub trait DomTraversal<E: TElement> : Sync {
// If the parent computed display:none, we don't style the subtree.
if parent_data.styles.is_display_none() {
- if log.allow() {
- debug!("Parent {:?} is display:none, culling traversal",
- parent);
- }
- return false;
+ debug!("Parent {:?} is display:none, culling traversal", parent);
+ return true;
}
// Gecko-only XBL handling.
@@ -431,61 +402,13 @@ pub trait DomTraversal<E: TElement> : Sync {
// recursively drops Servo ElementData when the XBL insertion parent of
// an Element is changed.
if cfg!(feature = "gecko") && context.thread_local.is_initial_style() &&
- parent_data.styles.primary().has_moz_binding() {
- if log.allow() {
- debug!("Parent {:?} has XBL binding, deferring traversal",
- parent);
- }
- return false;
- }
-
- return true;
- }
-
- /// Helper for the traversal implementations to select the children that
- /// should be enqueued for processing.
- fn traverse_children<F>(
- &self,
- context: &mut StyleContext<E>,
- parent: E,
- mut f: F
- )
- where
- F: FnMut(&mut StyleContext<E>, E::ConcreteNode)
- {
- // Check if we're allowed to traverse past this element.
- let should_traverse =
- self.should_traverse_children(
- context,
- parent,
- &parent.borrow_data().unwrap(),
- MayLog
- );
-
- context.thread_local.end_element(parent);
- if !should_traverse {
- return;
+ parent_data.styles.primary().has_moz_binding()
+ {
+ debug!("Parent {:?} has XBL binding, deferring traversal", parent);
+ return true;
}
- for kid in parent.as_node().traversal_children() {
- if Self::node_needs_traversal(kid, self.shared_context().traversal_flags) {
- // If we are in a restyle for reconstruction, there is no need to
- // perform a post-traversal, so we don't need to set the dirty
- // descendants bit on the parent.
- if !self.shared_context().traversal_flags.for_reconstruct() {
- let el = kid.as_element();
- if el.as_ref().and_then(|el| el.borrow_data())
- .map_or(false, |d| d.has_styles()) {
- if self.shared_context().traversal_flags.for_animation_only() {
- unsafe { parent.set_animation_only_dirty_descendants(); }
- } else {
- unsafe { parent.set_dirty_descendants(); }
- }
- }
- }
- f(context, kid);
- }
- }
+ return false;
}
/// Return the shared style context common to all worker threads.
@@ -586,16 +509,18 @@ where
/// Calculates the style for a single node.
#[inline]
#[allow(unsafe_code)]
-pub fn recalc_style_at<E, D>(
+pub fn recalc_style_at<E, D, F>(
traversal: &D,
traversal_data: &PerLevelTraversalData,
context: &mut StyleContext<E>,
element: E,
- data: &mut ElementData
+ data: &mut ElementData,
+ note_child: F,
)
where
E: TElement,
D: DomTraversal<E>,
+ F: FnMut(E::ConcreteNode),
{
context.thread_local.begin_element(element, data);
context.thread_local.statistics.elements_traversed += 1;
@@ -660,32 +585,52 @@ where
"Should have computed style or haven't yet valid computed \
style in case of animation-only restyle");
+ let flags = context.shared.traversal_flags;
let has_dirty_descendants_for_this_restyle =
- if context.shared.traversal_flags.for_animation_only() {
+ if flags.for_animation_only() {
element.has_animation_only_dirty_descendants()
} else {
element.has_dirty_descendants()
};
+ if flags.for_animation_only() {
+ unsafe { element.unset_animation_only_dirty_descendants(); }
+ }
- // Preprocess children, propagating restyle hints and handling sibling
- // relationships.
- let should_traverse_children = traversal.should_traverse_children(
- context,
- element,
- &data,
- DontLog
- );
- if should_traverse_children &&
- (has_dirty_descendants_for_this_restyle || !propagated_hint.is_empty()) {
- let reconstructed_ancestor =
- data.restyle.reconstructed_self_or_ancestor();
-
- preprocess_children::<E>(
+ // Before examining each child individually, try to prove that our children
+ // don't need style processing. They need processing if any of the following
+ // conditions hold:
+ // * We have the dirty descendants bit.
+ // * We're propagating a hint.
+ // * This is the initial style.
+ // * We generated a reconstruct hint on self (which could mean that we
+ // switched from display:none to something else, which means the children
+ // need initial styling).
+ // * This is a reconstruct traversal.
+ // * This is a servo non-incremental traversal.
+ //
+ // Additionally, there are a few scenarios where we avoid traversing the
+ // subtree even if descendant styles are out of date. These cases are
+ // enumerated in should_cull_subtree().
+ let mut traverse_children = has_dirty_descendants_for_this_restyle ||
+ !propagated_hint.is_empty() ||
+ context.thread_local.is_initial_style() ||
+ data.restyle.reconstructed_self() ||
+ flags.for_reconstruct() ||
+ is_servo_nonincremental_layout();
+
+ traverse_children = traverse_children &&
+ !traversal.should_cull_subtree(context, element, &data);
+
+ // Examine our children, and enqueue the appropriate ones for traversal.
+ if traverse_children {
+ note_children::<E, D, F>(
context,
element,
+ data,
propagated_hint,
- reconstructed_ancestor,
- )
+ data.restyle.reconstructed_self_or_ancestor(),
+ note_child
+ );
}
// If we are in a restyle for reconstruction, drop the existing restyle
@@ -695,10 +640,6 @@ where
data.clear_restyle_state();
}
- if context.shared.traversal_flags.for_animation_only() {
- unsafe { element.unset_animation_only_dirty_descendants(); }
- }
-
// There are two cases when we want to clear the dity descendants bit here
// after styling this element.
//
@@ -718,6 +659,8 @@ where
context.shared.traversal_flags.for_reconstruct() {
unsafe { element.unset_dirty_descendants(); }
}
+
+ context.thread_local.end_element(element);
}
fn compute_style<E>(
@@ -813,31 +756,43 @@ where
)
}
-fn preprocess_children<E>(
+fn note_children<E, D, F>(
context: &mut StyleContext<E>,
element: E,
+ data: &ElementData,
propagated_hint: RestyleHint,
reconstructed_ancestor: bool,
+ mut note_child: F,
)
where
E: TElement,
+ D: DomTraversal<E>,
+ F: FnMut(E::ConcreteNode),
{
- trace!("preprocess_children: {:?}", element);
+ trace!("note_children: {:?}", element);
+ let flags = context.shared.traversal_flags;
// Loop over all the traversal children.
- for child in element.as_node().traversal_children() {
- // FIXME(bholley): Add TElement::element_children instead of this.
- let child = match child.as_element() {
+ for child_node in element.as_node().traversal_children() {
+ let child = match child_node.as_element() {
Some(el) => el,
- None => continue,
+ None => {
+ if is_servo_nonincremental_layout() ||
+ D::text_node_needs_traversal(child_node, data) {
+ note_child(child_node);
+ }
+ continue;
+ },
};
- // If the child is unstyled, we don't need to set up any restyling.
- if child.borrow_data().map_or(true, |d| !d.has_styles()) {
+ let child_data = child.mutate_data();
+ if child_data.as_ref().map_or(true, |d| !d.has_styles()) {
+ if !flags.for_animation_only() {
+ note_child(child_node);
+ }
continue;
}
-
- let mut child_data = unsafe { child.ensure_data() };
+ let mut child_data = child_data.unwrap();
trace!(" > {:?} -> {:?} + {:?}, pseudo: {:?}",
child,
@@ -857,6 +812,24 @@ where
//
// NB: This will be a no-op if there's no snapshot.
child_data.invalidate_style_if_needed(child, &context.shared);
+
+ if D::element_needs_traversal(child, flags, &*child_data, Some(data)) {
+ note_child(child_node);
+
+ // Set the dirty descendants bit on the parent as needed, so that we
+ // can find elements during the post-traversal.
+ //
+ // If we are in a restyle for reconstruction, there is no need to
+ // perform a post-traversal, so we don't need to set the dirty
+ // descendants bit on the parent.
+ if !context.shared.traversal_flags.for_reconstruct() {
+ if context.shared.traversal_flags.for_animation_only() {
+ unsafe { element.set_animation_only_dirty_descendants(); }
+ } else {
+ unsafe { element.set_dirty_descendants(); }
+ }
+ }
+ }
}
}