From ab71b2995960f5f6ca12ba7677874e123aec7321 Mon Sep 17 00:00:00 2001 From: Bobby Holley Date: Wed, 28 Dec 2016 11:21:40 +0800 Subject: [PATCH] style: Add a special, explicit path for lazy style resolution and use it for GetComputedStyle. MozReview-Commit-ID: KAM9mVoxCHE --- components/layout/query.rs | 59 +++++------ components/style/build_gecko.rs | 3 - components/style/gecko/wrapper.rs | 8 -- components/style/gecko_bindings/bindings.rs | 12 ++- components/style/matching.rs | 24 ++--- components/style/traversal.rs | 87 ++++++++++++----- ports/geckolib/glue.rs | 102 ++++++++++++-------- 7 files changed, 176 insertions(+), 119 deletions(-) diff --git a/components/layout/query.rs b/components/layout/query.rs index 0a44a9b1b49..4b076942a54 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -6,7 +6,7 @@ use app_units::Au; use construct::ConstructionResult; -use context::{ScopedThreadLocalLayoutContext, SharedLayoutContext}; +use context::SharedLayoutContext; use euclid::point::Point2D; use euclid::rect::Rect; use euclid::size::Size2D; @@ -29,7 +29,7 @@ use std::cmp::{min, max}; use std::ops::Deref; use std::sync::{Arc, Mutex}; use style::computed_values; -use style::context::StyleContext; +use style::context::{StyleContext, ThreadLocalStyleContext}; use style::dom::TElement; use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection}; use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId}; @@ -626,37 +626,46 @@ pub fn process_node_scroll_area_request< N: LayoutNode>(requested_node: N, layou /// Return the resolved value of property for a given (pseudo)element. /// https://drafts.csswg.org/cssom/#resolved-value pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext, - requested_node: N, + node: N, pseudo: &Option, property: &PropertyId, layout_root: &mut Flow) -> String where N: LayoutNode, { - use style::traversal::{clear_descendant_data, style_element_in_display_none_subtree}; - let element = requested_node.as_element().unwrap(); + use style::traversal::resolve_style; + let element = node.as_element().unwrap(); // We call process_resolved_style_request after performing a whole-document - // traversal, so the only reason we wouldn't have an up-to-date style here - // is that the requested node is in a display:none subtree. We currently - // maintain the invariant that elements in display:none subtrees always have - // no ElementData, so we need to temporarily bend those invariants here, and - // then throw them the style data away again before returning to preserve them. - // We could optimize this later to keep the style data cached somehow, but - // we'd need a mechanism to prevent detect when it's stale (since we don't - // traverse display:none subtrees during restyle). - let display_none_root = if element.get_data().is_none() { - let mut tlc = ScopedThreadLocalLayoutContext::new(shared); - let context = StyleContext { - shared: &shared.style_context, - thread_local: &mut tlc.style_context, - }; + // traversal, so in the common case, the element is styled. + if element.get_data().is_some() { + return process_resolved_style_request_internal(node, pseudo, property, layout_root); + } - Some(style_element_in_display_none_subtree(&context, element, - &|e| e.as_node().initialize_data())) - } else { - None + // However, the element may be in a display:none subtree. The style system + // has a mechanism to give us that within a defined scope (after which point + // it's cleared to maintained style system invariants). + let mut tlc = ThreadLocalStyleContext::new(&shared.style_context); + let context = StyleContext { + shared: &shared.style_context, + thread_local: &mut tlc, }; + let mut result = None; + let ensure = |el: N::ConcreteElement| el.as_node().initialize_data(); + let clear = |el: N::ConcreteElement| el.as_node().clear_data(); + resolve_style(&context, element, &ensure, &clear, |_: &_| { + let s = process_resolved_style_request_internal(node, pseudo, property, layout_root); + result = Some(s); + }); + result.unwrap() +} +/// The primary resolution logic, which assumes that the element is styled. +fn process_resolved_style_request_internal<'a, N>(requested_node: N, + pseudo: &Option, + property: &PropertyId, + layout_root: &mut Flow) -> String + where N: LayoutNode, +{ let layout_el = requested_node.to_threadsafe().as_element().unwrap(); let layout_el = match *pseudo { Some(PseudoElement::Before) => layout_el.get_before_pseudo(), @@ -691,10 +700,6 @@ pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext, } }; - // Clear any temporarily-resolved data to maintain our invariants. See the comment - // at the top of this function. - display_none_root.map(|r| clear_descendant_data(r, &|e| e.as_node().clear_data())); - let positioned = match style.get_box().position { position::computed_value::T::relative | /*position::computed_value::T::sticky |*/ diff --git a/components/style/build_gecko.rs b/components/style/build_gecko.rs index 7e5a40e8096..6ebfc8418bc 100644 --- a/components/style/build_gecko.rs +++ b/components/style/build_gecko.rs @@ -224,7 +224,6 @@ mod bindings { "RawGecko.*", "mozilla::ServoElementSnapshot.*", "mozilla::ConsumeStyleBehavior", - "mozilla::LazyComputeBehavior", "mozilla::css::SheetParsingMode", "mozilla::TraversalRootBehavior", "mozilla::DisplayItemClip", // Needed because bindgen generates @@ -251,7 +250,6 @@ mod bindings { "GridNamedArea", "Image", "ImageURL", - "LazyComputeBehavior", "nsAttrName", "nsAttrValue", "nsBorderColors", @@ -451,7 +449,6 @@ mod bindings { "ThreadSafeURIHolder", "ThreadSafePrincipalHolder", "ConsumeStyleBehavior", - "LazyComputeBehavior", "TraversalRootBehavior", "FontFamilyList", "FontFamilyType", diff --git a/components/style/gecko/wrapper.rs b/components/style/gecko/wrapper.rs index 45dc1bb8cdf..dfdbf749875 100644 --- a/components/style/gecko/wrapper.rs +++ b/components/style/gecko/wrapper.rs @@ -260,14 +260,6 @@ impl<'le> GeckoElement<'le> { } } - pub fn get_pseudo_style(&self, pseudo: &PseudoElement) -> Option> { - // FIXME(bholley): Gecko sometimes resolves pseudos after an element has - // already been marked for restyle. We should consider fixing this, and - // then assert has_current_styles here. - self.borrow_data().and_then(|data| data.styles().pseudos - .get(pseudo).map(|c| c.values.clone())) - } - // Only safe to call with exclusive access to the element. pub unsafe fn ensure_data(&self) -> &AtomicRefCell { match self.get_data() { diff --git a/components/style/gecko_bindings/bindings.rs b/components/style/gecko_bindings/bindings.rs index b9dc050143b..721880679a5 100644 --- a/components/style/gecko_bindings/bindings.rs +++ b/components/style/gecko_bindings/bindings.rs @@ -9,7 +9,6 @@ use gecko_bindings::structs::RawGeckoNode; use gecko_bindings::structs::ThreadSafeURIHolder; use gecko_bindings::structs::ThreadSafePrincipalHolder; use gecko_bindings::structs::ConsumeStyleBehavior; -use gecko_bindings::structs::LazyComputeBehavior; use gecko_bindings::structs::TraversalRootBehavior; use gecko_bindings::structs::FontFamilyList; use gecko_bindings::structs::FontFamilyType; @@ -1209,9 +1208,7 @@ extern "C" { } extern "C" { pub fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, - set: RawServoStyleSetBorrowed, - consume: ConsumeStyleBehavior, - compute: LazyComputeBehavior) + consume: ConsumeStyleBehavior) -> ServoComputedValuesStrong; } extern "C" { @@ -1220,6 +1217,13 @@ extern "C" { set: RawServoStyleSetBorrowed) -> ServoComputedValuesStrong; } +extern "C" { + pub fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed, + pseudo_tag: *mut nsIAtom, + consume: ConsumeStyleBehavior, + set: RawServoStyleSetBorrowed) + -> ServoComputedValuesStrong; +} extern "C" { pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed, set: RawServoStyleSetBorrowed, diff --git a/components/style/matching.rs b/components/style/matching.rs index 8600d9bdc56..0b06c998098 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -709,13 +709,13 @@ pub trait MatchMethods : TElement { } } - unsafe fn cascade_node(&self, - context: &StyleContext, - mut data: &mut AtomicRefMut, - parent: Option, - primary_rule_node: StrongRuleNode, - pseudo_rule_nodes: PseudoRuleNodes, - primary_is_shareable: bool) + fn cascade_node(&self, + context: &StyleContext, + mut data: &mut AtomicRefMut, + parent: Option, + primary_rule_node: StrongRuleNode, + pseudo_rule_nodes: PseudoRuleNodes, + primary_is_shareable: bool) { // Get our parent's style. let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap()); @@ -770,10 +770,12 @@ pub trait MatchMethods : TElement { pseudo_rule_nodes, &mut possibly_expired_animations); - self.as_node().set_can_be_fragmented(parent.map_or(false, |p| { - p.as_node().can_be_fragmented() || - parent_style.unwrap().is_multicol() - })); + unsafe { + self.as_node().set_can_be_fragmented(parent.map_or(false, |p| { + p.as_node().can_be_fragmented() || + parent_style.unwrap().is_multicol() + })); + } damage }; diff --git a/components/style/traversal.rs b/components/style/traversal.rs index a9fd8c57a4d..8a7ae7b241a 100644 --- a/components/style/traversal.rs +++ b/components/style/traversal.rs @@ -6,7 +6,7 @@ use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use context::{SharedStyleContext, StyleContext}; -use data::{ElementData, StoredRestyleHint}; +use data::{ElementData, ElementStyles, StoredRestyleHint}; use dom::{TElement, TNode}; use matching::{MatchMethods, StyleSharingResult}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; @@ -265,40 +265,77 @@ pub fn relations_are_shareable(relations: &StyleRelations) -> bool { AFFECTED_BY_PRESENTATIONAL_HINTS) } -/// Handles lazy resolution of style in display:none subtrees. See the comment -/// at the callsite in query.rs. -pub fn style_element_in_display_none_subtree(context: &StyleContext, - element: E, init_data: &F) -> E +/// Helper for the function below. +fn resolve_style_internal(context: &StyleContext, element: E, ensure_data: &F) + -> Option where E: TElement, F: Fn(E), { - // Check the base case. - if element.get_data().is_some() { - // See the comment on `cascade_node` for why we allow this on Gecko. - debug_assert!(cfg!(feature = "gecko") || element.borrow_data().unwrap().has_current_styles()); - debug_assert!(element.borrow_data().unwrap().styles().is_display_none()); - return element; - } - - // Ensure the parent is styled. - let parent = element.parent_element().unwrap(); - let display_none_root = style_element_in_display_none_subtree(context, parent, init_data); - - // Initialize our data. - init_data(element); - - // Resolve our style. + ensure_data(element); let mut data = element.mutate_data().unwrap(); - let match_results = element.match_element(context, None); - unsafe { + let mut display_none_root = None; + + // If the Element isn't styled, we need to compute its style. + if data.get_styles().is_none() { + // Compute the parent style if necessary. + if let Some(parent) = element.parent_element() { + display_none_root = resolve_style_internal(context, parent, ensure_data); + } + + // Compute our style. + let match_results = element.match_element(context, None); let shareable = match_results.primary_is_shareable(); - element.cascade_node(context, &mut data, Some(parent), + element.cascade_node(context, &mut data, element.parent_element(), match_results.primary, match_results.per_pseudo, shareable); + + // Conservatively mark us as having dirty descendants, since there might + // be other unstyled siblings we miss when walking straight up the parent + // chain. + unsafe { element.set_dirty_descendants() }; } - display_none_root + // If we're display:none and none of our ancestors are, we're the root + // of a display:none subtree. + if display_none_root.is_none() && data.styles().is_display_none() { + display_none_root = Some(element); + } + + return display_none_root +} + +/// Manually resolve style by sequentially walking up the parent chain to the +/// first styled Element, ignoring pending restyles. The resolved style is +/// made available via a callback, and can be dropped by the time this function +/// returns in the display:none subtree case. +pub fn resolve_style(context: &StyleContext, element: E, + ensure_data: &F, clear_data: &G, callback: H) + where E: TElement, + F: Fn(E), + G: Fn(E), + H: FnOnce(&ElementStyles) +{ + // Resolve styles up the tree. + let display_none_root = resolve_style_internal(context, element, ensure_data); + + // Make them available for the scope of the callback. The callee may use the + // argument, or perform any other processing that requires the styles to exist + // on the Element. + callback(element.borrow_data().unwrap().styles()); + + // Clear any styles in display:none subtrees to leave the tree in a valid state. + if let Some(root) = display_none_root { + let mut curr = element; + loop { + unsafe { curr.unset_dirty_descendants(); } + if curr == root { + break; + } + clear_data(curr); + curr = curr.parent_element().unwrap(); + } + } } /// Calculates the style for a single node. diff --git a/ports/geckolib/glue.rs b/ports/geckolib/glue.rs index e59013a931d..c198d53c71d 100644 --- a/ports/geckolib/glue.rs +++ b/ports/geckolib/glue.rs @@ -58,7 +58,7 @@ use style::string_cache::Atom; use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule}; use style::thread_state; use style::timer::Timer; -use style::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData}; +use style::traversal::{resolve_style, DomTraversal}; use style_traits::ToCss; use stylesheet_loader::StylesheetLoader; @@ -859,51 +859,14 @@ pub extern "C" fn Servo_CheckChangeHint(element: RawGeckoElementBorrowed) -> nsC #[no_mangle] pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, - raw_data: RawServoStyleSetBorrowed, - consume: structs::ConsumeStyleBehavior, - compute: structs::LazyComputeBehavior) -> ServoComputedValuesStrong + consume: structs::ConsumeStyleBehavior) + -> ServoComputedValuesStrong { let element = GeckoElement(element); - debug!("Servo_ResolveStyle: {:?}, consume={:?}, compute={:?}", element, consume, compute); + debug!("Servo_ResolveStyle: {:?}, consume={:?}", element, consume); let mut data = unsafe { element.ensure_data() }.borrow_mut(); - if compute == structs::LazyComputeBehavior::Allow { - let should_compute = !data.has_current_styles(); - if should_compute { - debug!("Performing manual style computation"); - if let Some(parent) = element.parent_element() { - if parent.borrow_data().map_or(true, |d| !d.has_current_styles()) { - error!("Attempting manual style computation with unstyled parent"); - return Arc::new(ComputedValues::initial_values().clone()).into_strong(); - } - } - - let per_doc_data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut(); - let shared_style_context = create_shared_context(&per_doc_data); - let traversal = RecalcStyleOnly::new(shared_style_context); - - let mut traversal_data = PerLevelTraversalData { - current_dom_depth: None, - }; - - let mut tlc = ThreadLocalStyleContext::new(traversal.shared_context()); - let mut context = StyleContext { - shared: traversal.shared_context(), - thread_local: &mut tlc, - }; - - recalc_style_at(&traversal, &mut traversal_data, &mut context, element, &mut data); - - // The element was either unstyled or needed restyle. If it was unstyled, it may have - // additional unstyled children that subsequent traversals won't find now that the style - // on this element is up-to-date. Mark dirty descendants in that case. - if element.first_child_element().is_some() { - unsafe { element.set_dirty_descendants() }; - } - } - } - if !data.has_current_styles() { error!("Resolving style on unstyled element with lazy computation forbidden."); return Arc::new(ComputedValues::initial_values().clone()).into_strong(); @@ -920,6 +883,63 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, values.into_strong() } +#[no_mangle] +pub extern "C" fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed, + pseudo_tag: *mut nsIAtom, + consume: structs::ConsumeStyleBehavior, + raw_data: RawServoStyleSetBorrowed) + -> ServoComputedValuesStrong +{ + let element = GeckoElement(element); + let doc_data = PerDocumentStyleData::from_ffi(raw_data); + let finish = |styles: &ElementStyles| -> Arc { + let maybe_pseudo = if !pseudo_tag.is_null() { + get_pseudo_style(element, pseudo_tag, styles, doc_data) + } else { + None + }; + maybe_pseudo.unwrap_or_else(|| styles.primary.values.clone()) + }; + + // In the common case we already have the style. Check that before setting + // up all the computation machinery. + let mut result = element.mutate_data() + .and_then(|d| d.get_styles().map(&finish)); + if result.is_some() { + if consume == structs::ConsumeStyleBehavior::Consume { + let mut d = element.mutate_data().unwrap(); + if !d.is_persistent() { + // XXXheycam is it right to persist an ElementData::Restyle? + // Couldn't we lose restyle hints that would cause us to + // restyle descendants? + d.persist(); + } + } + return result.unwrap().into_strong(); + } + + // We don't have the style ready. Go ahead and compute it as necessary. + let shared = create_shared_context(&mut doc_data.borrow_mut()); + let mut tlc = ThreadLocalStyleContext::new(&shared); + let mut context = StyleContext { + shared: &shared, + thread_local: &mut tlc, + }; + let ensure = |el: GeckoElement| { unsafe { el.ensure_data(); } }; + let clear = |el: GeckoElement| el.clear_data(); + resolve_style(&mut context, element, &ensure, &clear, + |styles| result = Some(finish(styles))); + + // Consume the style if requested, though it may not exist anymore if the + // element is in a display:none subtree. + if consume == structs::ConsumeStyleBehavior::Consume { + element.mutate_data().map(|mut d| d.persist()); + } + + result.unwrap().into_strong() +} + + #[no_mangle] pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) { if !cfg!(debug_assertions) {