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

stylo: Basic infrastructure for RestyleHint-driven traversal #14300

Merged
merged 3 commits into from Nov 25, 2016
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -1354,8 +1354,8 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
let mut set_has_newly_constructed_flow_flag = false;
let result = {
let mut style = node.style(self.style_context());
let damage = node.restyle_damage();
let mut data = node.mutate_layout_data().unwrap();
let damage = data.base.restyle_damage;

match *node.construction_result_mut(&mut *data) {
ConstructionResult::None => true,
@@ -29,6 +29,7 @@ use std::ops::Deref;
use std::sync::{Arc, Mutex};
use style::computed_values;
use style::context::StyleContext;
use style::dom::TElement;
use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
use style::properties::longhands::{display, position};
use style::properties::style_structs;
@@ -607,20 +608,6 @@ pub fn process_node_scroll_area_request< N: LayoutNode>(requested_node: N, layou
}
}

/// Ensures that a node's data, and all its parents' is initialized. This is
/// needed to resolve style lazily.
fn ensure_node_data_initialized<N: LayoutNode>(node: &N) {
let mut cur = Some(node.clone());
while let Some(current) = cur {
if current.borrow_layout_data().is_some() {
break;
}

current.initialize_data();
cur = current.parent_node();
}
}

/// 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, C>(requested_node: N,
@@ -631,14 +618,24 @@ pub fn process_resolved_style_request<'a, N, C>(requested_node: N,
where N: LayoutNode,
C: StyleContext<'a>
{
use style::traversal::ensure_element_styled;

// This node might have display: none, or it's style might be not up to
// date, so we might need to do style recalc.
//
// FIXME(emilio): Is a bit shame we have to do this instead of in style.
ensure_node_data_initialized(&requested_node);
ensure_element_styled(requested_node.as_element().unwrap(), style_context);
use style::traversal::{clear_descendant_data, style_element_in_display_none_subtree};
let element = requested_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).

This comment has been minimized.

@emilio

emilio Nov 24, 2016

Member

For the record, what Blink does for this case is storing it in a different spot, and getting rid of it before resolving style.

This comment has been minimized.

@emilio

emilio Nov 24, 2016

Member

Actually for stylo assuming we want to do that, I guess we can use either the undisplayed content map or the dom slots.

This comment has been minimized.

@emilio

emilio Nov 24, 2016

Member

(Ideally the frame pointer itself of course)

let display_none_root = if element.get_data().is_none() {
Some(style_element_in_display_none_subtree(element, &|e| e.as_node().initialize_data(),
style_context))
} else {
None
};

let layout_el = requested_node.to_threadsafe().as_element().unwrap();
let layout_el = match *pseudo {
@@ -662,6 +659,10 @@ pub fn process_resolved_style_request<'a, N, C>(requested_node: N,

let style = &*layout_el.resolved_style();

// 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 |*/
@@ -112,12 +112,7 @@ impl<'lc, N> DomTraversalContext<N> for RecalcStyleAndConstructFlows<'lc>
construct_flows_at(&self.context, self.root, node);
}

fn should_traverse_child(parent: N::ConcreteElement, child: N) -> bool {
// If the parent is display:none, we don't need to do anything.
if parent.is_display_none() {
return false;
}

fn should_traverse_child(child: N) -> bool {
match child.as_element() {
// Elements should be traversed if they need styling or flow construction.
Some(el) => el.styling_mode() != StylingMode::Stop ||
@@ -128,7 +123,7 @@ impl<'lc, N> DomTraversalContext<N> for RecalcStyleAndConstructFlows<'lc>
// (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).
None => child.get_raw_data().is_none() ||
parent.as_node().to_threadsafe().restyle_damage() != RestyleDamage::empty(),
child.parent_node().unwrap().to_threadsafe().restyle_damage() != RestyleDamage::empty(),
}
}

@@ -156,6 +151,8 @@ pub trait PostorderNodeMutTraversal<ConcreteThreadSafeLayoutNode: ThreadSafeLayo
#[inline]
#[allow(unsafe_code)]
fn construct_flows_at<'a, N: LayoutNode>(context: &'a LayoutContext<'a>, root: OpaqueNode, node: N) {
debug!("construct_flows_at: {:?}", node);

// Construct flows for this node.
{
let tnode = node.to_threadsafe();
@@ -167,16 +164,18 @@ fn construct_flows_at<'a, N: LayoutNode>(context: &'a LayoutContext<'a>, root: O
let mut flow_constructor = FlowConstructor::new(context);
if nonincremental_layout || !flow_constructor.repair_if_possible(&tnode) {
flow_constructor.process(&tnode);
debug!("Constructed flow for {:x}: {:x}",
tnode.debug_id(),
debug!("Constructed flow for {:?}: {:x}",
tnode,
tnode.flow_debug_id());
}
}
}

tnode.clear_restyle_damage();
if let Some(el) = node.as_element() {
el.mutate_data().unwrap().persist();
unsafe { el.unset_dirty_descendants(); }
}

unsafe { node.clear_dirty_bits(); }
remove_from_bloom_filter(context, root, node);
}

@@ -37,8 +37,6 @@ use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElemen
use script_layout_interface::wrapper_traits::GetLayoutData;
use style::atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use style::computed_values::content::{self, ContentItem};
use style::dom::TElement;
use style::traversal::prepare_for_styling;

pub type NonOpaqueStyleAndLayoutData = AtomicRefCell<PersistentLayoutData>;

@@ -97,9 +95,6 @@ impl<T: LayoutNode> LayoutNodeHelpers for T {
ptr: unsafe { NonZero::new(ptr as *mut AtomicRefCell<PartialPersistentLayoutData>) }
};
unsafe { self.init_style_and_layout_data(opaque) };
if let Some(el) = self.as_element() {
let _ = prepare_for_styling(el, el.get_data().unwrap());
}
};
}

@@ -86,7 +86,7 @@ use parking_lot::RwLock;
use profile_traits::mem::{self, Report, ReportKind, ReportsChan};
use profile_traits::time::{self, TimerMetadata, profile};
use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
use script::layout_wrapper::{ServoLayoutDocument, ServoLayoutNode};
use script::layout_wrapper::{ServoLayoutElement, ServoLayoutDocument, ServoLayoutNode};
use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow};
use script_layout_interface::reporter::CSSErrorReporter;
use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse};
@@ -105,7 +105,8 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use style::animation::Animation;
use style::context::{LocalStyleContextCreationInfo, ReflowGoal, SharedStyleContext};
use style::dom::{TElement, TNode};
use style::data::StoredRestyleHint;
use style::dom::{StylingMode, TElement, TNode};
use style::error_reporting::{ParseErrorReporter, StdoutErrorReporter};
use style::logical_geometry::LogicalPoint;
use style::media_queries::{Device, MediaType};
@@ -980,11 +981,9 @@ impl LayoutThread {
(data.reflow_info.goal == ReflowGoal::ForScriptQuery &&
data.query_type != ReflowQueryType::NoQuery));

debug!("layout: received layout request for: {}", self.url);

let mut rw_data = possibly_locked_rw_data.lock();

let node: ServoLayoutNode = match document.root_node() {
let element: ServoLayoutElement = match document.root_node() {
None => {
// Since we cannot compute anything, give spec-required placeholders.
debug!("layout: No root node: bailing");
@@ -1020,12 +1019,13 @@ impl LayoutThread {
}
return;
},
Some(x) => x,
Some(x) => x.as_element().unwrap(),
};

debug!("layout: received layout request for: {}", self.url);
debug!("layout: processing reflow request for: {:?} ({}) (query={:?})",
element, self.url, data.query_type);
if log_enabled!(log::LogLevel::Debug) {
node.dump();
element.as_node().dump();
}

let initial_viewport = data.window_size.initial_viewport;
@@ -1061,15 +1061,15 @@ impl LayoutThread {
.unwrap();
}
if data.document_stylesheets.iter().any(|sheet| sheet.dirty_on_viewport_size_change()) {
let mut iter = node.traverse_preorder();
let mut iter = element.as_node().traverse_preorder();

let mut next = iter.next();
while let Some(node) = next {
if node.needs_dirty_on_viewport_size_changed() {
// NB: The dirty bit is propagated down the tree.
unsafe { node.set_dirty(); }

if let Some(p) = node.parent_node().and_then(|n| n.as_element()) {
let el = node.as_element().unwrap();
el.mutate_data().map(|mut d| d.restyle()
.map(|mut r| r.hint.insert(&StoredRestyleHint::subtree())));
if let Some(p) = el.parent_element() {
unsafe { p.note_dirty_descendant() };
}

@@ -1086,20 +1086,17 @@ impl LayoutThread {
Some(&*UA_STYLESHEETS),
data.stylesheets_changed);
let needs_reflow = viewport_size_changed && !needs_dirtying;
unsafe {
if needs_dirtying {
// NB: The dirty flag is propagated down during the restyle
// process.
node.set_dirty();
}
if needs_dirtying {
element.mutate_data().map(|mut d| d.restyle().map(|mut r| r.hint.insert(&StoredRestyleHint::subtree())));
}
if needs_reflow {
if let Some(mut flow) = self.try_get_layout_root(node) {
if let Some(mut flow) = self.try_get_layout_root(element.as_node()) {
LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow));
}
}

let restyles = document.drain_pending_restyles();
debug!("Draining restyles: {}", restyles.len());
if !needs_dirtying {
for (el, restyle) in restyles {
// Propagate the descendant bit up the ancestors. Do this before
@@ -1109,30 +1106,23 @@ impl LayoutThread {
unsafe { parent.note_dirty_descendant() };
}

if el.get_data().is_none() {
// If we haven't styled this node yet, we don't need to track
// a restyle.
continue;
}

// Start with the explicit hint, if any.
let mut hint = restyle.hint;

// Expand any snapshots.
if let Some(s) = restyle.snapshot {
hint |= rw_data.stylist.compute_restyle_hint(&el, &s, el.get_state());
}

// Apply the cumulative hint.
if !hint.is_empty() {
el.note_restyle_hint::<RecalcStyleAndConstructFlows>(hint);
}

// Apply explicit damage, if any.
if !restyle.damage.is_empty() {
let mut d = el.mutate_layout_data().unwrap();
d.base.restyle_damage |= restyle.damage;
}
// If we haven't styled this node yet, we don't need to track a restyle.
let mut data = match el.mutate_layout_data() {
Some(d) => d,
None => continue,
};
let mut style_data = &mut data.base.style_data;
debug_assert!(!style_data.is_restyle());
let mut restyle_data = match style_data.restyle() {
Some(d) => d,
None => continue,
};

// Stash the data on the element for processing by the style system.
restyle_data.hint = restyle.hint.into();
restyle_data.damage = restyle.damage;
restyle_data.snapshot = restyle.snapshot;
debug!("Noting restyle for {:?}: {:?}", el, restyle_data);
}
}

@@ -1141,8 +1131,7 @@ impl LayoutThread {
viewport_size_changed,
data.reflow_info.goal);

let el = node.as_element();
if el.is_some() && (el.unwrap().deprecated_dirty_bit_is_set() || el.unwrap().has_dirty_descendants()) {
if element.styling_mode() != StylingMode::Stop {
// Recalculate CSS styles and rebuild flows and fragments.
profile(time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
@@ -1152,11 +1141,11 @@ impl LayoutThread {
match self.parallel_traversal {
None => {
sequential::traverse_dom::<ServoLayoutNode, RecalcStyleAndConstructFlows>(
node, &shared_layout_context);
element.as_node(), &shared_layout_context);
}
Some(ref mut traversal) => {
parallel::traverse_dom::<ServoLayoutNode, RecalcStyleAndConstructFlows>(
node, &shared_layout_context, traversal);
element.as_node(), &shared_layout_context, traversal);
}
}
});
@@ -1174,11 +1163,11 @@ impl LayoutThread {
0);

// Retrieve the (possibly rebuilt) root flow.
self.root_flow = self.try_get_layout_root(node);
self.root_flow = self.try_get_layout_root(element.as_node());
}

if opts::get().dump_style_tree {
node.dump_style();
element.as_node().dump_style();
}

if opts::get().dump_rule_tree {
@@ -434,7 +434,6 @@ impl Document {
// that workable.
match self.GetDocumentElement() {
Some(root) => {
root.upcast::<Node>().is_dirty() ||
root.upcast::<Node>().has_dirty_descendants() ||
!self.pending_restyles.borrow().is_empty() ||
self.needs_paint()
@@ -149,9 +149,6 @@ bitflags! {
#[doc = "Specifies whether this node is in a document."]
const IS_IN_DOC = 0x01,
#[doc = "Specifies whether this node needs style recalc on next reflow."]
const IS_DIRTY = 0x04,
#[doc = "Specifies whether this node has descendants (inclusive of itself) which \
have changed since the last reflow."]
const HAS_DIRTY_DESCENDANTS = 0x08,
// TODO: find a better place to keep this (#4105)
// https://critic.hoppipolla.co.uk/showcomment?chain=8873
@@ -172,7 +169,7 @@ bitflags! {

impl NodeFlags {
pub fn new() -> NodeFlags {
IS_DIRTY
NodeFlags::empty()
}
}

@@ -428,14 +425,6 @@ impl Node {
self.flags.set(flags);
}

pub fn is_dirty(&self) -> bool {
self.get_flag(IS_DIRTY)
}

pub fn set_is_dirty(&self, state: bool) {
self.set_flag(IS_DIRTY, state)
}

pub fn has_dirty_descendants(&self) -> bool {
self.get_flag(HAS_DIRTY_DESCENDANTS)
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.