Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

style: Split style resolution and dynamic change computation. #17688

Merged
merged 11 commits into from Jul 12, 2017

style: Rewrite get*Style using StyleResolverForElement.

Removing the ugly.

MozReview-Commit-ID: BvahbMKS7QU
  • Loading branch information
emilio committed Jul 12, 2017
commit 0ad2d39c30ac2827e4956224de578f2dea78916d
@@ -37,7 +37,7 @@ use style::selector_parser::PseudoElement;
use style_traits::ToCss;
use style_traits::cursor::Cursor;
use webrender_traits::ClipId;
use wrapper::{LayoutNodeHelpers, LayoutNodeLayoutData};
use wrapper::LayoutNodeLayoutData;

/// Mutable data belonging to the LayoutThread.
///
@@ -680,7 +680,9 @@ pub fn process_resolved_style_request<'a, N>(context: &LayoutContext,
layout_root: &mut Flow) -> String
where N: LayoutNode,
{
use style::stylist::RuleInclusion;
use style::traversal::resolve_style;

let element = node.as_element().unwrap();

// We call process_resolved_style_request after performing a whole-document
@@ -689,30 +691,43 @@ pub fn process_resolved_style_request<'a, N>(context: &LayoutContext,
return process_resolved_style_request_internal(node, pseudo, property, layout_root);
}

// 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).
// In a display: none subtree. No pseudo-element exists.
if pseudo.is_some() {
return String::new();
}

let mut tlc = ThreadLocalStyleContext::new(&context.style_context);
let mut context = StyleContext {
shared: &context.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(&mut context, element, &ensure, &clear, |_: &_| {
let s = process_resolved_style_request_internal(node, pseudo, property, layout_root);
result = Some(s);
});
result.unwrap()

let styles = resolve_style(&mut context, element, RuleInclusion::All);
let style = styles.primary();
let longhand_id = match *property {
PropertyId::Longhand(id) => id,
// Firefox returns blank strings for the computed value of shorthands,
// so this should be web-compatible.
PropertyId::Shorthand(_) => return String::new(),
PropertyId::Custom(ref name) => {
return style.computed_value_to_string(PropertyDeclarationId::Custom(name))
}
};

// No need to care about used values here, since we're on a display: none
// subtree, use the resolved value.
style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id))
}

/// 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,
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 = match *pseudo {
@@ -721,6 +736,8 @@ fn process_resolved_style_request_internal<'a, N>(requested_node: N,
Some(PseudoElement::DetailsSummary) |
Some(PseudoElement::DetailsContent) |
Some(PseudoElement::Selection) => None,
// FIXME(emilio): What about the other pseudos? Probably they shouldn't
// just return the element's style!
_ => Some(layout_el)
};

@@ -735,7 +752,6 @@ fn process_resolved_style_request_internal<'a, N>(requested_node: N,
};

let style = &*layout_el.resolved_style();

let longhand_id = match *property {
PropertyId::Longhand(id) => id,

@@ -21,14 +21,16 @@ use stylearc::Arc;
use stylist::RuleInclusion;

/// A struct that takes care of resolving the style of a given element.
pub struct StyleResolverForElement<'a, 'b, E>
pub struct StyleResolverForElement<'a, 'ctx, 'le, E>
where
'b: 'a,
E: TElement + MatchMethods + 'static,
'ctx: 'a,
'le: 'ctx,
E: TElement + MatchMethods + 'le,
{
element: E,
context: &'a mut StyleContext<'b, E>,
context: &'a mut StyleContext<'ctx, E>,
rule_inclusion: RuleInclusion,
_marker: ::std::marker::PhantomData<&'le E>,
}

struct MatchingResults {
@@ -47,18 +49,24 @@ pub struct PrimaryStyle {
pub relevant_link_found: bool,
}

impl<'a, 'b, E> StyleResolverForElement<'a, 'b, E>
impl<'a, 'ctx, 'le, E> StyleResolverForElement<'a, 'ctx, 'le, E>
where
'b: 'a,
E: TElement + MatchMethods + 'static,
'ctx: 'a,
'le: 'ctx,
E: TElement + MatchMethods + 'le,
{
/// Trivially construct a new StyleResolverForElement.
pub fn new(
element: E,
context: &'a mut StyleContext<'b, E>,
context: &'a mut StyleContext<'ctx, E>,
rule_inclusion: RuleInclusion,
) -> Self {
Self { element, context, rule_inclusion, }
Self {
element,
context,
rule_inclusion,
_marker: ::std::marker::PhantomData,
}
}

/// Resolve just the style of a given element.
@@ -7,12 +7,12 @@
use atomic_refcell::AtomicRefCell;
use context::{ElementCascadeInputs, StyleContext, SharedStyleContext};
use data::{ElementData, ElementStyles};
use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
use dom::{NodeInfo, OpaqueNode, TElement, TNode};
use invalidation::element::restyle_hints::{RECASCADE_SELF, RECASCADE_DESCENDANTS, RestyleHint};
use matching::{ChildCascadeRequirement, MatchMethods};
use sharing::{StyleSharingBehavior, StyleSharingTarget};
#[cfg(feature = "servo")] use servo_config::opts;
use smallvec::SmallVec;
use stylist::RuleInclusion;

/// A per-traversal-level chunk of data. This is sent down by the traversal, and
/// currently only holds the dom depth for the bloom filter.
@@ -126,6 +126,8 @@ impl TraversalDriver {

#[cfg(feature = "servo")]
fn is_servo_nonincremental_layout() -> bool {
use servo_config::opts;

opts::get().nonincremental_layout
}

@@ -520,151 +522,87 @@ pub trait DomTraversal<E: TElement> : Sync {
fn is_parallel(&self) -> bool;
}

/// Helper for the function below.
fn resolve_style_internal<E, F>(
context: &mut StyleContext<E>,
element: E, ensure_data: &F
) -> Option<E>
where E: TElement,
F: Fn(E),
{
ensure_data(element);
let mut data = element.mutate_data().unwrap();
let mut display_none_root = None;

// If the Element isn't styled, we need to compute its style.
if !data.has_styles() {
// Compute the parent style if necessary.
let parent = element.traversal_parent();
if let Some(p) = parent {
display_none_root = resolve_style_internal(context, p, ensure_data);
}

// Maintain the bloom filter. If it doesn't exist, we need to build it
// from scratch. Otherwise we just need to push the parent.
if context.thread_local.bloom_filter.is_empty() {
context.thread_local.bloom_filter.rebuild(element);
} else {
context.thread_local.bloom_filter.push(parent.unwrap());
context.thread_local.bloom_filter.assert_complete(element);
}

// Compute our style.
context.thread_local.begin_element(element, &data);
element.match_and_cascade(context,
&mut data,
StyleSharingBehavior::Disallow);
context.thread_local.end_element(element);

if !context.shared.traversal_flags.for_default_styles() {
// Conservatively mark us as having dirty descendants, since there
// might be other unstyled siblings we miss when walking straight up
// the parent chain.
//
// No need to do this if we're computing default styles, since
// resolve_default_style will want the tree to be left as it is.
unsafe { element.note_descendants::<DirtyDescendants>() };
}
}

// 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: &mut StyleContext<E>, element: E,
ensure_data: &F, clear_data: &G, callback: H)
where E: TElement,
F: Fn(E),
G: Fn(E),
H: FnOnce(&ElementStyles)
pub fn resolve_style<E>(
context: &mut StyleContext<E>,
element: E,
rule_inclusion: RuleInclusion,
) -> ElementStyles
where
E: TElement,
{
use style_resolver::StyleResolverForElement;

debug_assert!(rule_inclusion == RuleInclusion::DefaultOnly ||
element.borrow_data().map_or(true, |d| !d.has_styles()),
"Why are we here?");
let mut ancestors_requiring_style_resolution = SmallVec::<[E; 16]>::new();

// Clear the bloom filter, just in case the caller is reusing TLS.
context.thread_local.bloom_filter.clear();

// 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 or subtrees not in the
// document, to leave the tree in a valid state. For display:none subtrees,
// we leave the styles on the display:none root, but for subtrees not in the
// document, we clear styles all the way up to the root of the disconnected
// subtree.
let in_doc = element.as_node().is_in_doc();
if !in_doc || display_none_root.is_some() {
let mut curr = element;
loop {
unsafe {
curr.unset_dirty_descendants();
curr.unset_animation_only_dirty_descendants();
}
if in_doc && curr == display_none_root.unwrap() {
break;
let mut style = None;
let mut ancestor = element.traversal_parent();
while let Some(current) = ancestor {
if rule_inclusion == RuleInclusion::All {
if let Some(data) = element.borrow_data() {
if let Some(ancestor_style) = data.styles.get_primary() {
style = Some(ancestor_style.clone());
break;
}
}
clear_data(curr);
curr = match curr.traversal_parent() {
Some(parent) => parent,
None => break,
};
}
ancestors_requiring_style_resolution.push(current);
ancestor = current.traversal_parent();
}
}

/// Manually resolve default styles for the given Element, which are the styles
/// only taking into account user agent and user cascade levels. The resolved
/// style is made available via a callback, and will be dropped by the time this
/// function returns.
pub fn resolve_default_style<E, F, G, H>(
context: &mut StyleContext<E>,
element: E,
ensure_data: &F,
set_data: &G,
callback: H
)
where
E: TElement,
F: Fn(E),
G: Fn(E, Option<ElementData>) -> Option<ElementData>,
H: FnOnce(&ElementStyles),
{
// Save and clear out element data from the element and its ancestors.
let mut old_data: SmallVec<[(E, Option<ElementData>); 8]> = SmallVec::new();
{
let mut e = element;
loop {
old_data.push((e, set_data(e, None)));
match e.traversal_parent() {
Some(parent) => e = parent,
None => break,
}
if let Some(ancestor) = ancestor {
context.thread_local.bloom_filter.rebuild(ancestor);
context.thread_local.bloom_filter.push(ancestor);
}

let mut layout_parent_style = style.clone();
while let Some(style) = layout_parent_style.take() {
if !style.is_display_contents() {
layout_parent_style = Some(style);
break;
}

ancestor = ancestor.unwrap().traversal_parent();
layout_parent_style = ancestor.map(|a| {
a.borrow_data().unwrap().styles.primary().clone()
});
}

// Resolve styles up the tree.
resolve_style_internal(context, element, ensure_data);
for ancestor in ancestors_requiring_style_resolution.iter().rev() {
context.thread_local.bloom_filter.assert_complete(*ancestor);

// 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);
let primary_style =
StyleResolverForElement::new(*ancestor, context, rule_inclusion)
.resolve_primary_style(
style.as_ref().map(|s| &**s),
layout_parent_style.as_ref().map(|s| &**s)
);

// Swap the old element data back into the element and its ancestors.
for entry in old_data {
set_data(entry.0, entry.1);
let is_display_contents = primary_style.style.is_display_contents();

style = Some(primary_style.style);
if !is_display_contents {
layout_parent_style = style.clone();
}

context.thread_local.bloom_filter.push(*ancestor);
}

context.thread_local.bloom_filter.assert_complete(element);
StyleResolverForElement::new(element, context, rule_inclusion)
.resolve_style(
style.as_ref().map(|s| &**s),
layout_parent_style.as_ref().map(|s| &**s)
)
}

/// Calculates the style for a single node.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.