style: Add a special, explicit path for lazy style resolution and use it for GetComputedStyle.

MozReview-Commit-ID: KAM9mVoxCHE
This commit is contained in:
Bobby Holley 2016-12-28 11:21:40 +08:00 committed by Cameron McCormack
parent 7e3c9e2197
commit ab71b29959
7 changed files with 176 additions and 119 deletions

View file

@ -6,7 +6,7 @@
use app_units::Au; use app_units::Au;
use construct::ConstructionResult; use construct::ConstructionResult;
use context::{ScopedThreadLocalLayoutContext, SharedLayoutContext}; use context::SharedLayoutContext;
use euclid::point::Point2D; use euclid::point::Point2D;
use euclid::rect::Rect; use euclid::rect::Rect;
use euclid::size::Size2D; use euclid::size::Size2D;
@ -29,7 +29,7 @@ use std::cmp::{min, max};
use std::ops::Deref; use std::ops::Deref;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use style::computed_values; use style::computed_values;
use style::context::StyleContext; use style::context::{StyleContext, ThreadLocalStyleContext};
use style::dom::TElement; use style::dom::TElement;
use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection}; use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId}; 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. /// Return the resolved value of property for a given (pseudo)element.
/// https://drafts.csswg.org/cssom/#resolved-value /// https://drafts.csswg.org/cssom/#resolved-value
pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext, pub fn process_resolved_style_request<'a, N>(shared: &SharedLayoutContext,
requested_node: N, node: N,
pseudo: &Option<PseudoElement>, pseudo: &Option<PseudoElement>,
property: &PropertyId, property: &PropertyId,
layout_root: &mut Flow) -> String layout_root: &mut Flow) -> String
where N: LayoutNode, where N: LayoutNode,
{ {
use style::traversal::{clear_descendant_data, style_element_in_display_none_subtree}; use style::traversal::resolve_style;
let element = requested_node.as_element().unwrap(); let element = node.as_element().unwrap();
// We call process_resolved_style_request after performing a whole-document // 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 // traversal, so in the common case, the element is styled.
// is that the requested node is in a display:none subtree. We currently if element.get_data().is_some() {
// maintain the invariant that elements in display:none subtrees always have return process_resolved_style_request_internal(node, pseudo, property, layout_root);
// 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,
};
Some(style_element_in_display_none_subtree(&context, element, // However, the element may be in a display:none subtree. The style system
&|e| e.as_node().initialize_data())) // has a mechanism to give us that within a defined scope (after which point
} else { // it's cleared to maintained style system invariants).
None 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<PseudoElement>,
property: &PropertyId,
layout_root: &mut Flow) -> String
where N: LayoutNode,
{
let layout_el = requested_node.to_threadsafe().as_element().unwrap(); let layout_el = requested_node.to_threadsafe().as_element().unwrap();
let layout_el = match *pseudo { let layout_el = match *pseudo {
Some(PseudoElement::Before) => layout_el.get_before_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 { let positioned = match style.get_box().position {
position::computed_value::T::relative | position::computed_value::T::relative |
/*position::computed_value::T::sticky |*/ /*position::computed_value::T::sticky |*/

View file

@ -224,7 +224,6 @@ mod bindings {
"RawGecko.*", "RawGecko.*",
"mozilla::ServoElementSnapshot.*", "mozilla::ServoElementSnapshot.*",
"mozilla::ConsumeStyleBehavior", "mozilla::ConsumeStyleBehavior",
"mozilla::LazyComputeBehavior",
"mozilla::css::SheetParsingMode", "mozilla::css::SheetParsingMode",
"mozilla::TraversalRootBehavior", "mozilla::TraversalRootBehavior",
"mozilla::DisplayItemClip", // Needed because bindgen generates "mozilla::DisplayItemClip", // Needed because bindgen generates
@ -251,7 +250,6 @@ mod bindings {
"GridNamedArea", "GridNamedArea",
"Image", "Image",
"ImageURL", "ImageURL",
"LazyComputeBehavior",
"nsAttrName", "nsAttrName",
"nsAttrValue", "nsAttrValue",
"nsBorderColors", "nsBorderColors",
@ -451,7 +449,6 @@ mod bindings {
"ThreadSafeURIHolder", "ThreadSafeURIHolder",
"ThreadSafePrincipalHolder", "ThreadSafePrincipalHolder",
"ConsumeStyleBehavior", "ConsumeStyleBehavior",
"LazyComputeBehavior",
"TraversalRootBehavior", "TraversalRootBehavior",
"FontFamilyList", "FontFamilyList",
"FontFamilyType", "FontFamilyType",

View file

@ -260,14 +260,6 @@ impl<'le> GeckoElement<'le> {
} }
} }
pub fn get_pseudo_style(&self, pseudo: &PseudoElement) -> Option<Arc<ComputedValues>> {
// 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. // Only safe to call with exclusive access to the element.
pub unsafe fn ensure_data(&self) -> &AtomicRefCell<ElementData> { pub unsafe fn ensure_data(&self) -> &AtomicRefCell<ElementData> {
match self.get_data() { match self.get_data() {

View file

@ -9,7 +9,6 @@ use gecko_bindings::structs::RawGeckoNode;
use gecko_bindings::structs::ThreadSafeURIHolder; use gecko_bindings::structs::ThreadSafeURIHolder;
use gecko_bindings::structs::ThreadSafePrincipalHolder; use gecko_bindings::structs::ThreadSafePrincipalHolder;
use gecko_bindings::structs::ConsumeStyleBehavior; use gecko_bindings::structs::ConsumeStyleBehavior;
use gecko_bindings::structs::LazyComputeBehavior;
use gecko_bindings::structs::TraversalRootBehavior; use gecko_bindings::structs::TraversalRootBehavior;
use gecko_bindings::structs::FontFamilyList; use gecko_bindings::structs::FontFamilyList;
use gecko_bindings::structs::FontFamilyType; use gecko_bindings::structs::FontFamilyType;
@ -1209,9 +1208,7 @@ extern "C" {
} }
extern "C" { extern "C" {
pub fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, pub fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
set: RawServoStyleSetBorrowed, consume: ConsumeStyleBehavior)
consume: ConsumeStyleBehavior,
compute: LazyComputeBehavior)
-> ServoComputedValuesStrong; -> ServoComputedValuesStrong;
} }
extern "C" { extern "C" {
@ -1220,6 +1217,13 @@ extern "C" {
set: RawServoStyleSetBorrowed) set: RawServoStyleSetBorrowed)
-> ServoComputedValuesStrong; -> ServoComputedValuesStrong;
} }
extern "C" {
pub fn Servo_ResolveStyleLazily(element: RawGeckoElementBorrowed,
pseudo_tag: *mut nsIAtom,
consume: ConsumeStyleBehavior,
set: RawServoStyleSetBorrowed)
-> ServoComputedValuesStrong;
}
extern "C" { extern "C" {
pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed, pub fn Servo_TraverseSubtree(root: RawGeckoElementBorrowed,
set: RawServoStyleSetBorrowed, set: RawServoStyleSetBorrowed,

View file

@ -709,13 +709,13 @@ pub trait MatchMethods : TElement {
} }
} }
unsafe fn cascade_node(&self, fn cascade_node(&self,
context: &StyleContext<Self>, context: &StyleContext<Self>,
mut data: &mut AtomicRefMut<ElementData>, mut data: &mut AtomicRefMut<ElementData>,
parent: Option<Self>, parent: Option<Self>,
primary_rule_node: StrongRuleNode, primary_rule_node: StrongRuleNode,
pseudo_rule_nodes: PseudoRuleNodes, pseudo_rule_nodes: PseudoRuleNodes,
primary_is_shareable: bool) primary_is_shareable: bool)
{ {
// Get our parent's style. // Get our parent's style.
let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap()); let parent_data = parent.as_ref().map(|x| x.borrow_data().unwrap());
@ -770,10 +770,12 @@ pub trait MatchMethods : TElement {
pseudo_rule_nodes, pseudo_rule_nodes,
&mut possibly_expired_animations); &mut possibly_expired_animations);
self.as_node().set_can_be_fragmented(parent.map_or(false, |p| { unsafe {
p.as_node().can_be_fragmented() || self.as_node().set_can_be_fragmented(parent.map_or(false, |p| {
parent_style.unwrap().is_multicol() p.as_node().can_be_fragmented() ||
})); parent_style.unwrap().is_multicol()
}));
}
damage damage
}; };

View file

@ -6,7 +6,7 @@
use atomic_refcell::{AtomicRefCell, AtomicRefMut}; use atomic_refcell::{AtomicRefCell, AtomicRefMut};
use context::{SharedStyleContext, StyleContext}; use context::{SharedStyleContext, StyleContext};
use data::{ElementData, StoredRestyleHint}; use data::{ElementData, ElementStyles, StoredRestyleHint};
use dom::{TElement, TNode}; use dom::{TElement, TNode};
use matching::{MatchMethods, StyleSharingResult}; use matching::{MatchMethods, StyleSharingResult};
use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF}; use restyle_hints::{RESTYLE_DESCENDANTS, RESTYLE_SELF};
@ -265,40 +265,77 @@ pub fn relations_are_shareable(relations: &StyleRelations) -> bool {
AFFECTED_BY_PRESENTATIONAL_HINTS) AFFECTED_BY_PRESENTATIONAL_HINTS)
} }
/// Handles lazy resolution of style in display:none subtrees. See the comment /// Helper for the function below.
/// at the callsite in query.rs. fn resolve_style_internal<E, F>(context: &StyleContext<E>, element: E, ensure_data: &F)
pub fn style_element_in_display_none_subtree<E, F>(context: &StyleContext<E>, -> Option<E>
element: E, init_data: &F) -> E
where E: TElement, where E: TElement,
F: Fn(E), F: Fn(E),
{ {
// Check the base case. ensure_data(element);
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.
let mut data = element.mutate_data().unwrap(); let mut data = element.mutate_data().unwrap();
let match_results = element.match_element(context, None); let mut display_none_root = None;
unsafe {
// 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(); 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.primary,
match_results.per_pseudo, match_results.per_pseudo,
shareable); 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<E, F, G, H>(context: &StyleContext<E>, 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. /// Calculates the style for a single node.

View file

@ -58,7 +58,7 @@ use style::string_cache::Atom;
use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule}; use style::stylesheets::{CssRule, CssRules, Origin, Stylesheet, StyleRule};
use style::thread_state; use style::thread_state;
use style::timer::Timer; use style::timer::Timer;
use style::traversal::{recalc_style_at, DomTraversal, PerLevelTraversalData}; use style::traversal::{resolve_style, DomTraversal};
use style_traits::ToCss; use style_traits::ToCss;
use stylesheet_loader::StylesheetLoader; use stylesheet_loader::StylesheetLoader;
@ -859,51 +859,14 @@ pub extern "C" fn Servo_CheckChangeHint(element: RawGeckoElementBorrowed) -> nsC
#[no_mangle] #[no_mangle]
pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed, pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
raw_data: RawServoStyleSetBorrowed, consume: structs::ConsumeStyleBehavior)
consume: structs::ConsumeStyleBehavior, -> ServoComputedValuesStrong
compute: structs::LazyComputeBehavior) -> ServoComputedValuesStrong
{ {
let element = GeckoElement(element); 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(); 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() { if !data.has_current_styles() {
error!("Resolving style on unstyled element with lazy computation forbidden."); error!("Resolving style on unstyled element with lazy computation forbidden.");
return Arc::new(ComputedValues::initial_values().clone()).into_strong(); return Arc::new(ComputedValues::initial_values().clone()).into_strong();
@ -920,6 +883,63 @@ pub extern "C" fn Servo_ResolveStyle(element: RawGeckoElementBorrowed,
values.into_strong() 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<ComputedValues> {
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] #[no_mangle]
pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) { pub extern "C" fn Servo_AssertTreeIsClean(root: RawGeckoElementBorrowed) {
if !cfg!(debug_assertions) { if !cfg!(debug_assertions) {