From 3f71c80e2f9905ea643d8eb4c8c7fe64064ea437 Mon Sep 17 00:00:00 2001 From: Cameron McCormack Date: Tue, 4 Apr 2017 19:32:47 +0800 Subject: [PATCH] style: Handle TraversalRestyleBehavior::ForReconstruct in the Servo restyle. --- components/style/build_gecko.rs | 2 + components/style/data.rs | 7 +++ components/style/matching.rs | 11 ++++- components/style/traversal.rs | 75 ++++++++++++++++++++++++++------- ports/geckolib/glue.rs | 17 +++++--- 5 files changed, 89 insertions(+), 23 deletions(-) diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index ff9f9820f91..671314ee894 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -321,6 +321,7 @@ mod bindings { "mozilla::css::SheetParsingMode", "mozilla::HalfCorner", "mozilla::PropertyStyleAnimationValuePair", + "mozilla::TraversalRestyleBehavior", "mozilla::TraversalRootBehavior", "mozilla::StyleShapeRadius", "mozilla::StyleGrid.*", @@ -616,6 +617,7 @@ mod bindings { "RawGeckoURLExtraData", "RefPtr", "CSSPseudoClassType", + "TraversalRestyleBehavior", "TraversalRootBehavior", "ComputedTimingFunction_BeforeFlag", "FontFamilyList", diff --git a/components/style/data.rs b/components/style/data.rs index 46c1c621825..a34ce78c2e8 100644 --- a/components/style/data.rs +++ b/components/style/data.rs @@ -223,6 +223,13 @@ impl StoredRestyleHint { StoredRestyleHint(RESTYLE_SELF | RESTYLE_DESCENDANTS) } + /// Creates a restyle hint that forces the element and all its later + /// siblings to have their whole subtrees restyled, including the elements + /// themselves. + pub fn subtree_and_later_siblings() -> Self { + StoredRestyleHint(RESTYLE_SELF | RESTYLE_DESCENDANTS | RESTYLE_LATER_SIBLINGS) + } + /// Returns true if the hint indicates that our style may be invalidated. pub fn has_self_invalidations(&self) -> bool { self.0.intersects(RestyleHint::for_self()) diff --git a/components/style/matching.rs b/components/style/matching.rs index 7391befcea9..3f2aa3c1e24 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -557,7 +557,7 @@ trait PrivateMatchMethods: TElement { // Accumulate restyle damage. if let Some(old) = old_values { - self.accumulate_damage(restyle.unwrap(), &old, &new_values, pseudo); + self.accumulate_damage(&context.shared, restyle.unwrap(), &old, &new_values, pseudo); } // Set the new computed values. @@ -677,10 +677,16 @@ trait PrivateMatchMethods: TElement { /// Computes and applies non-redundant damage. #[cfg(feature = "gecko")] fn accumulate_damage(&self, + shared_context: &SharedStyleContext, restyle: &mut RestyleData, old_values: &Arc, new_values: &Arc, pseudo: Option<&PseudoElement>) { + // Don't accumulate damage if we're in a restyle for reconstruction. + if shared_context.traversal_flags.for_reconstruct() { + return; + } + // If an ancestor is already getting reconstructed by Gecko's top-down // frame constructor, no need to apply damage. if restyle.damage_handled.contains(RestyleDamage::reconstruct()) { @@ -705,6 +711,7 @@ trait PrivateMatchMethods: TElement { /// Computes and applies restyle damage unless we've already maxed it out. #[cfg(feature = "servo")] fn accumulate_damage(&self, + _shared_context: &SharedStyleContext, restyle: &mut RestyleData, old_values: &Arc, new_values: &Arc, @@ -1109,7 +1116,7 @@ pub trait MatchMethods : TElement { let old_values = data.get_styles_mut() .and_then(|s| s.primary.values.take()); if let Some(old) = old_values { - self.accumulate_damage(data.restyle_mut(), &old, + self.accumulate_damage(shared_context, data.restyle_mut(), &old, shared_style.values(), None); } diff --git a/components/style/traversal.rs b/components/style/traversal.rs index ec915321ba7..ded0f3e7b3a 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -31,12 +31,14 @@ pub struct PerLevelTraversalData { } bitflags! { - /// Represents that target elements of the traversal. + /// Flags that control the traversal process. pub flags TraversalFlags: u8 { /// Traverse only unstyled children. const UNSTYLED_CHILDREN_ONLY = 0x01, - /// Traverse only elements for animation restyles + /// Traverse only elements for animation restyles. const ANIMATION_ONLY = 0x02, + /// Traverse without generating any change hints. + const FOR_RECONSTRUCT = 0x04, } } @@ -50,6 +52,11 @@ impl TraversalFlags { pub fn for_unstyled_children_only(&self) -> bool { self.contains(UNSTYLED_CHILDREN_ONLY) } + + /// Returns true if the traversal is for a frame reconstruction. + pub fn for_reconstruct(&self) -> bool { + self.contains(FOR_RECONSTRUCT) + } } /// This structure exists to enforce that callers invoke pre_traverse, and also @@ -148,6 +155,10 @@ pub trait DomTraversal : Sync { fn pre_traverse(root: E, stylist: &Stylist, traversal_flags: TraversalFlags) -> PreTraverseToken { + debug_assert!(!(traversal_flags.for_reconstruct() && + traversal_flags.for_unstyled_children_only()), + "must not specify FOR_RECONSTRUCT in combination with UNSTYLED_CHILDREN_ONLY"); + if traversal_flags.for_unstyled_children_only() { if root.borrow_data().map_or(true, |d| d.has_styles() && d.styles().is_display_none()) { return PreTraverseToken { @@ -165,12 +176,18 @@ pub trait DomTraversal : Sync { // we need a special case for the root. // // Expanding snapshots here may create a LATER_SIBLINGS restyle hint, which - // we will drop on the floor. To prevent missed restyles, we assert against - // restyling a root with later siblings. + // we propagate to the next sibling element. if let Some(mut data) = root.mutate_data() { if let Some(r) = data.get_restyle_mut() { - debug_assert!(root.next_sibling_element().is_none()); - let _later_siblings = r.compute_final_hint(root, stylist); + let later_siblings = r.compute_final_hint(root, stylist); + if later_siblings { + if let Some(next) = root.next_sibling_element() { + if let Some(mut next_data) = next.mutate_data() { + let hint = StoredRestyleHint::subtree_and_later_siblings(); + next_data.ensure_restyle().hint.insert(&hint); + } + } + } } } @@ -195,6 +212,10 @@ pub trait DomTraversal : Sync { return true; } + if traversal_flags.for_reconstruct() { + return true; + } + match node.as_element() { None => Self::text_node_needs_traversal(node), Some(el) => { @@ -264,7 +285,11 @@ pub trait DomTraversal : Sync { // Servo uses the post-order traversal for flow construction, so // we need to traverse any element with damage so that we can perform // fixup / reconstruction on our way back up the tree. - if cfg!(feature = "servo") && + // + // We also need to traverse nodes with explicit damage and no other + // restyle data, so that this damage can be cleared. + if (cfg!(feature = "servo") || + traversal_flags.for_reconstruct()) && data.get_restyle().map_or(false, |r| r.damage != RestyleDamage::empty()) { return true; @@ -342,11 +367,15 @@ pub trait DomTraversal : Sync { for kid in parent.as_node().children() { if Self::node_needs_traversal(kid, self.shared_context().traversal_flags) { - let el = kid.as_element(); - if el.as_ref().and_then(|el| el.borrow_data()) - .map_or(false, |d| d.has_styles()) - { - unsafe { parent.set_dirty_descendants(); } + // 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()) { + unsafe { parent.set_dirty_descendants(); } + } } f(thread_local, kid); } @@ -557,20 +586,34 @@ pub fn recalc_style_at(traversal: &D, inherited_style_changed); } + // If we are in a restyle for reconstruction, drop the existing restyle + // data here, since we won't need to perform a post-traversal to pick up + // any change hints. + if context.shared.traversal_flags.for_reconstruct() { + data.clear_restyle(); + } + if context.shared.traversal_flags.for_animation_only() { unsafe { element.unset_animation_only_dirty_descendants(); } } - // Make sure the dirty descendants bit is not set for the root of a - // display:none subtree, even if the style didn't change (since, if - // the style did change, we'd have already cleared it above). + // There are two cases when we want to clear the dity descendants bit + // here after styling this element. + // + // The first case is when this element is the root of a display:none + // subtree, even if the style didn't change (since, if the style did + // change, we'd have already cleared it above). // // This keeps the tree in a valid state without requiring the DOM to // check display:none on the parent when inserting new children (which // can be moderately expensive). Instead, DOM implementations can // unconditionally set the dirty descendants bit on any styled parent, // and let the traversal sort it out. - if data.styles().is_display_none() { + // + // The second case is when we are in a restyle for reconstruction, + // where we won't need to perform a post-traversal to pick up any + // change hints. + if data.styles().is_display_none() || context.shared.traversal_flags.for_reconstruct() { unsafe { element.unset_dirty_descendants(); } } } diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index 33beba27edc..15d9a7b1c3f 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -85,7 +85,7 @@ use style::stylesheets::StylesheetLoader as StyleStylesheetLoader; use style::supports::parse_condition_or_declaration; use style::thread_state; use style::timer::Timer; -use style::traversal::{ANIMATION_ONLY, UNSTYLED_CHILDREN_ONLY}; +use style::traversal::{ANIMATION_ONLY, FOR_RECONSTRUCT, UNSTYLED_CHILDREN_ONLY}; use style::traversal::{resolve_style, DomTraversal, TraversalDriver, TraversalFlags}; use style_traits::ToCss; use super::stylesheet_loader::StylesheetLoader; @@ -207,13 +207,20 @@ fn traverse_subtree(element: GeckoElement, raw_data: RawServoStyleSetBorrowed, #[no_mangle] pub extern "C" fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed, raw_data: RawServoStyleSetBorrowed, - behavior: structs::TraversalRootBehavior) -> bool { + root_behavior: structs::TraversalRootBehavior, + restyle_behavior: structs::TraversalRestyleBehavior) + -> bool { + use self::structs::TraversalRestyleBehavior as Restyle; + use self::structs::TraversalRootBehavior as Root; + let element = GeckoElement(root); debug!("Servo_TraverseSubtree: {:?}", element); - let traversal_flags = match behavior { - structs::TraversalRootBehavior::UnstyledChildrenOnly => UNSTYLED_CHILDREN_ONLY, - _ => TraversalFlags::empty(), + let traversal_flags = match (root_behavior, restyle_behavior) { + (Root::Normal, Restyle::Normal) => TraversalFlags::empty(), + (Root::UnstyledChildrenOnly, Restyle::Normal) => UNSTYLED_CHILDREN_ONLY, + (Root::Normal, Restyle::ForReconstruct) => FOR_RECONSTRUCT, + _ => panic!("invalid combination of TraversalRootBehavior and TraversalRestyleBehavior"), }; if element.has_animation_only_dirty_descendants() ||