aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/traversal.rs17
-rw-r--r--components/layout/wrapper.rs10
-rw-r--r--components/layout_thread/lib.rs42
-rw-r--r--components/layout_thread_2020/lib.rs23
-rw-r--r--components/script/dom/document.rs119
-rw-r--r--components/script/dom/element.rs2
-rw-r--r--components/script/dom/node.rs52
-rw-r--r--components/script/dom/range.rs15
-rw-r--r--components/script/dom/window.rs28
-rw-r--r--components/script_layout_interface/message.rs2
-rw-r--r--components/style/driver.rs5
11 files changed, 251 insertions, 64 deletions
diff --git a/components/layout/traversal.rs b/components/layout/traversal.rs
index 4fb3838d18e..88a6514433b 100644
--- a/components/layout/traversal.rs
+++ b/components/layout/traversal.rs
@@ -30,6 +30,10 @@ impl<'a> RecalcStyleAndConstructFlows<'a> {
RecalcStyleAndConstructFlows { context: context }
}
+ pub fn context(&self) -> &LayoutContext<'a> {
+ &self.context
+ }
+
/// Consumes this traversal context, returning ownership of the shared layout
/// context to the caller.
pub fn destroy(self) -> LayoutContext<'a> {
@@ -183,6 +187,19 @@ where
fn process(&mut self, node: &ConcreteThreadSafeLayoutNode);
}
+#[allow(unsafe_code)]
+#[inline]
+pub unsafe fn construct_flows_at_ancestors<'dom>(
+ context: &LayoutContext,
+ mut node: impl LayoutNode<'dom>,
+) {
+ while let Some(element) = node.traversal_parent() {
+ element.set_dirty_descendants();
+ node = element.as_node();
+ construct_flows_at(context, node);
+ }
+}
+
/// The flow construction traversal, which builds flows for styled nodes.
#[inline]
#[allow(unsafe_code)]
diff --git a/components/layout/wrapper.rs b/components/layout/wrapper.rs
index 126c74a886f..41e054bbe83 100644
--- a/components/layout/wrapper.rs
+++ b/components/layout/wrapper.rs
@@ -34,7 +34,7 @@ use crate::data::{LayoutData, LayoutDataFlags, StyleAndLayoutData};
use atomic_refcell::{AtomicRef, AtomicRefMut};
use script_layout_interface::wrapper_traits::GetStyleAndOpaqueLayoutData;
use script_layout_interface::wrapper_traits::{ThreadSafeLayoutElement, ThreadSafeLayoutNode};
-use style::dom::{NodeInfo, TNode};
+use style::dom::{NodeInfo, TElement, TNode};
use style::selector_parser::RestyleDamage;
use style::values::computed::counters::ContentItem;
use style::values::generics::counters::Content;
@@ -148,7 +148,13 @@ where
}
let damage = {
- let data = node.get_style_and_layout_data().unwrap();
+ let data = match node.get_style_and_layout_data() {
+ Some(data) => data,
+ None => panic!(
+ "could not get style and layout data for <{}>",
+ node.as_element().unwrap().local_name()
+ ),
+ };
if !data
.layout_data
diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs
index 037fffc34ec..82da02d1ce3 100644
--- a/components/layout_thread/lib.rs
+++ b/components/layout_thread/lib.rs
@@ -57,7 +57,8 @@ use layout::query::{process_node_scroll_area_request, process_node_scroll_id_req
use layout::query::{process_offset_parent_query, process_resolved_style_request};
use layout::sequential;
use layout::traversal::{
- ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows,
+ construct_flows_at_ancestors, ComputeStackingRelativePositions, PreorderFlowTraversal,
+ RecalcStyleAndConstructFlows,
};
use layout::wrapper::LayoutNodeLayoutData;
use layout_traits::LayoutThreadFactory;
@@ -1200,7 +1201,7 @@ impl LayoutThread {
.expect("layout: wrong layout query timestamp");
};
- let element = match document.root_element() {
+ let root_element = match document.root_element() {
None => {
// Since we cannot compute anything, give spec-required placeholders.
debug!("layout: No root node: bailing");
@@ -1250,9 +1251,9 @@ impl LayoutThread {
debug!(
"layout: processing reflow request for: {:?} ({}) (query={:?})",
- element, self.url, data.reflow_goal
+ root_element, self.url, data.reflow_goal
);
- trace!("{:?}", ShowSubtree(element.as_node()));
+ trace!("{:?}", ShowSubtree(root_element.as_node()));
let initial_viewport = data.window_size.initial_viewport;
let device_pixel_ratio = data.window_size.device_pixel_ratio;
@@ -1309,7 +1310,7 @@ impl LayoutThread {
.unwrap();
}
if had_used_viewport_units {
- if let Some(mut data) = element.mutate_data() {
+ if let Some(mut data) = root_element.mutate_data() {
data.hint.insert(RestyleHint::recascade_subtree());
}
}
@@ -1344,7 +1345,7 @@ impl LayoutThread {
}
if viewport_size_changed {
- if let Some(mut flow) = self.try_get_layout_root(element.as_node()) {
+ if let Some(mut flow) = self.try_get_layout_root(root_element.as_node()) {
LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow));
}
}
@@ -1395,7 +1396,7 @@ impl LayoutThread {
debug!("Noting restyle for {:?}: {:?}", el, style_data);
}
- self.stylist.flush(&guards, Some(element), Some(&map));
+ self.stylist.flush(&guards, Some(root_element), Some(&map));
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(
@@ -1414,13 +1415,19 @@ impl LayoutThread {
(None, 1)
};
+ let dirty_root = unsafe {
+ ServoLayoutNode::new(&data.dirty_root.unwrap())
+ .as_element()
+ .unwrap()
+ };
+
let traversal = RecalcStyleAndConstructFlows::new(layout_context);
let token = {
let shared =
<RecalcStyleAndConstructFlows as DomTraversal<ServoLayoutElement>>::shared_context(
&traversal,
);
- RecalcStyleAndConstructFlows::pre_traverse(element, shared)
+ RecalcStyleAndConstructFlows::pre_traverse(dirty_root, shared)
};
if token.should_traverse() {
@@ -1431,11 +1438,13 @@ impl LayoutThread {
self.time_profiler_chan.clone(),
|| {
// Perform CSS selector matching and flow construction.
- driver::traverse_dom::<ServoLayoutElement, RecalcStyleAndConstructFlows>(
- &traversal,
- token,
- thread_pool,
- );
+ let root = driver::traverse_dom::<
+ ServoLayoutElement,
+ RecalcStyleAndConstructFlows,
+ >(&traversal, token, thread_pool);
+ unsafe {
+ construct_flows_at_ancestors(traversal.context(), root.as_node());
+ }
},
);
// TODO(pcwalton): Measure energy usage of text shaping, perhaps?
@@ -1452,7 +1461,7 @@ impl LayoutThread {
);
// Retrieve the (possibly rebuilt) root flow.
- *self.root_flow.borrow_mut() = self.try_get_layout_root(element.as_node());
+ *self.root_flow.borrow_mut() = self.try_get_layout_root(root_element.as_node());
}
for element in elements_with_snapshot {
@@ -1462,7 +1471,10 @@ impl LayoutThread {
layout_context = traversal.destroy();
if self.dump_style_tree {
- println!("{:?}", ShowSubtreeDataAndPrimaryValues(element.as_node()));
+ println!(
+ "{:?}",
+ ShowSubtreeDataAndPrimaryValues(root_element.as_node())
+ );
}
if self.dump_rule_tree {
diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs
index a62a172d19d..de2c60c629f 100644
--- a/components/layout_thread_2020/lib.rs
+++ b/components/layout_thread_2020/lib.rs
@@ -87,7 +87,6 @@ use style::dom::{TDocument, TElement, TNode};
use style::driver;
use style::error_reporting::RustLogReporter;
use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL};
-use style::invalidation::element::restyle_hints::RestyleHint;
use style::media_queries::{Device, MediaList, MediaType};
use style::properties::PropertyId;
use style::selector_parser::SnapshotMap;
@@ -888,7 +887,7 @@ impl LayoutThread {
let mut rw_data = possibly_locked_rw_data.lock();
- let element = match document.root_element() {
+ let root_element = match document.root_element() {
None => {
// Since we cannot compute anything, give spec-required placeholders.
debug!("layout: No root node: bailing");
@@ -959,7 +958,6 @@ impl LayoutThread {
ua_or_user: &ua_or_user_guard,
};
- let had_used_viewport_units = self.stylist.device().used_viewport_units();
let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio);
let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards);
@@ -987,11 +985,6 @@ impl LayoutThread {
))
.unwrap();
}
- if had_used_viewport_units {
- if let Some(mut data) = element.mutate_data() {
- data.hint.insert(RestyleHint::recascade_subtree());
- }
- }
}
if self.first_reflow.get() {
@@ -1059,16 +1052,22 @@ impl LayoutThread {
debug!("Noting restyle for {:?}: {:?}", el, style_data);
}
- self.stylist.flush(&guards, Some(element), Some(&map));
+ self.stylist.flush(&guards, Some(root_element), Some(&map));
// Create a layout context for use throughout the following passes.
let mut layout_context =
self.build_layout_context(guards.clone(), &map, origin, data.animation_timeline_value);
+ let dirty_root = unsafe {
+ ServoLayoutNode::new(&data.dirty_root.unwrap())
+ .as_element()
+ .unwrap()
+ };
+
let traversal = RecalcStyle::new(layout_context);
let token = {
let shared = DomTraversal::<ServoLayoutElement>::shared_context(&traversal);
- RecalcStyle::pre_traverse(element, shared)
+ RecalcStyle::pre_traverse(dirty_root, shared)
};
let rayon_pool = STYLE_THREAD_POOL.pool();
@@ -1077,7 +1076,7 @@ impl LayoutThread {
let box_tree = if token.should_traverse() {
driver::traverse_dom(&traversal, token, rayon_pool);
- let root_node = document.root_element().unwrap().as_node();
+ let root_node = root_element.as_node();
let build_box_tree = || BoxTree::construct(traversal.context(), root_node);
let box_tree = if let Some(pool) = rayon_pool {
pool.install(build_box_tree)
@@ -1114,7 +1113,7 @@ impl LayoutThread {
if self.dump_style_tree {
println!(
"{:?}",
- style::dom::ShowSubtreeDataAndPrimaryValues(element.as_node())
+ style::dom::ShowSubtreeDataAndPrimaryValues(root_element.as_node())
);
}
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 4577835d24e..efb3277f1cf 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -387,6 +387,8 @@ pub struct Document {
animation_timeline: DomRefCell<AnimationTimeline>,
/// Animations for this Document
animations: DomRefCell<Animations>,
+ /// The nearest inclusive ancestors to all the nodes that require a restyle.
+ dirty_root: MutNullableDom<Element>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -446,6 +448,112 @@ enum ElementLookupResult {
#[allow(non_snake_case)]
impl Document {
+ pub fn note_node_with_dirty_descendants(&self, node: &Node) {
+ debug_assert!(*node.owner_doc() == *self);
+ if !node.is_connected() {
+ return;
+ }
+
+ let parent = match node.inclusive_ancestors(ShadowIncluding::Yes).nth(1) {
+ Some(parent) => parent,
+ None => {
+ // There is no parent so this is the Document node, so we
+ // behave as if we were called with the document element.
+ let document_element = match self.GetDocumentElement() {
+ Some(element) => element,
+ None => return,
+ };
+ if let Some(dirty_root) = self.dirty_root.get() {
+ // There was an existing dirty root so we mark its
+ // ancestors as dirty until the document element.
+ for ancestor in dirty_root
+ .upcast::<Node>()
+ .inclusive_ancestors(ShadowIncluding::Yes)
+ {
+ if ancestor.is::<Element>() {
+ ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
+ }
+ }
+ }
+ self.dirty_root.set(Some(&document_element));
+ return;
+ },
+ };
+
+ if parent.is::<Element>() {
+ if !parent.is_styled() {
+ return;
+ }
+
+ if parent.is_display_none() {
+ return;
+ }
+ }
+
+ let element_parent: DomRoot<Element>;
+ let element = match node.downcast::<Element>() {
+ Some(element) => element,
+ None => {
+ // Current node is not an element, it's probably a text node,
+ // we try to get its element parent.
+ match DomRoot::downcast::<Element>(parent) {
+ Some(parent) => {
+ element_parent = parent;
+ &element_parent
+ },
+ None => {
+ // Parent is not an element so it must be a document,
+ // and this is not an element either, so there is
+ // nothing to do.
+ return;
+ },
+ }
+ },
+ };
+
+ let dirty_root = match self.dirty_root.get() {
+ None => {
+ element
+ .upcast::<Node>()
+ .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
+ self.dirty_root.set(Some(element));
+ return;
+ },
+ Some(root) => root,
+ };
+
+ for ancestor in element
+ .upcast::<Node>()
+ .inclusive_ancestors(ShadowIncluding::Yes)
+ {
+ if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
+ return;
+ }
+ if ancestor.is::<Element>() {
+ ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
+ }
+ }
+
+ let new_dirty_root = element
+ .upcast::<Node>()
+ .common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes);
+
+ let mut has_dirty_descendants = true;
+ for ancestor in dirty_root
+ .upcast::<Node>()
+ .inclusive_ancestors(ShadowIncluding::Yes)
+ {
+ ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, has_dirty_descendants);
+ has_dirty_descendants &= *ancestor != *new_dirty_root;
+ }
+ self.dirty_root
+ .set(Some(new_dirty_root.downcast::<Element>().unwrap()));
+ }
+
+ pub fn take_dirty_root(&self) -> Option<DomRoot<Element>> {
+ self.dirty_root.take()
+ }
+
#[inline]
pub fn loader(&self) -> Ref<DocumentLoader> {
self.loader.borrow()
@@ -967,8 +1075,14 @@ impl Document {
}
pub fn dirty_all_nodes(&self) {
- let root = self.upcast::<Node>();
- for node in root.traverse_preorder(ShadowIncluding::Yes) {
+ let root = match self.GetDocumentElement() {
+ Some(root) => root,
+ None => return,
+ };
+ for node in root
+ .upcast::<Node>()
+ .traverse_preorder(ShadowIncluding::Yes)
+ {
node.dirty(NodeDamage::OtherNodeDamage)
}
}
@@ -2917,6 +3031,7 @@ impl Document {
DomRefCell::new(AnimationTimeline::new())
},
animations: DomRefCell::new(Animations::new()),
+ dirty_root: Default::default(),
}
}
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index 6f6f33f56a8..458f301e238 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -309,6 +309,7 @@ impl Element {
restyle.hint.insert(RestyleHint::RESTYLE_SELF);
if damage == NodeDamage::OtherNodeDamage {
+ doc.note_node_with_dirty_descendants(self.upcast());
restyle.damage = RestyleDamage::rebuild_and_reflow();
}
}
@@ -515,6 +516,7 @@ impl Element {
pub fn detach_shadow(&self) {
if let Some(ref shadow_root) = self.shadow_root() {
+ self.upcast::<Node>().note_dirty_descendants();
shadow_root.detach();
self.ensure_rare_data().shadow_root = None;
} else {
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index 7a75ea51503..f17c22163d0 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -315,6 +315,8 @@ impl Node {
/// Fails unless `child` is a child of this node.
fn remove_child(&self, child: &Node, cached_index: Option<u32>) {
assert!(child.parent_node.get().as_deref() == Some(self));
+ self.note_dirty_descendants();
+
let prev_sibling = child.GetPreviousSibling();
match prev_sibling {
None => {
@@ -627,17 +629,7 @@ impl Node {
// FIXME(emilio): This and the function below should move to Element.
pub fn note_dirty_descendants(&self) {
- debug_assert!(self.is_connected());
-
- for ancestor in self.inclusive_ancestors(ShadowIncluding::Yes) {
- if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) {
- return;
- }
-
- if ancestor.is::<Element>() {
- ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true);
- }
- }
+ self.owner_doc().note_node_with_dirty_descendants(self);
}
pub fn has_dirty_descendants(&self) -> bool {
@@ -705,6 +697,22 @@ impl Node {
}
}
+ pub fn common_ancestor(
+ &self,
+ other: &Node,
+ shadow_including: ShadowIncluding,
+ ) -> DomRoot<Node> {
+ for ancestor in self.inclusive_ancestors(shadow_including) {
+ if other
+ .inclusive_ancestors(shadow_including)
+ .any(|node| node == ancestor)
+ {
+ return ancestor;
+ }
+ }
+ unreachable!();
+ }
+
pub fn is_inclusive_ancestor_of(&self, parent: &Node) -> bool {
self == parent || self.is_ancestor_of(parent)
}
@@ -1243,6 +1251,26 @@ impl Node {
}
}
+ pub fn is_styled(&self) -> bool {
+ self.style_and_layout_data.borrow().is_some()
+ }
+
+ pub fn is_display_none(&self) -> bool {
+ self.style_and_layout_data
+ .borrow()
+ .as_ref()
+ .map_or(true, |data| {
+ data.style_data
+ .element_data
+ .borrow()
+ .styles
+ .primary()
+ .get_box()
+ .display
+ .is_none()
+ })
+ }
+
pub fn style(&self) -> Option<Arc<ComputedValues>> {
if !window_from_node(self).layout_reflow(QueryMsg::StyleQuery) {
return None;
@@ -1653,7 +1681,7 @@ where
}
/// Whether a tree traversal should pass shadow tree boundaries.
-#[derive(PartialEq)]
+#[derive(Clone, Copy, PartialEq)]
pub enum ShadowIncluding {
No,
Yes,
diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs
index 0b1c8f16de1..e9b3a2e34cb 100644
--- a/components/script/dom/range.rs
+++ b/components/script/dom/range.rs
@@ -296,19 +296,8 @@ impl RangeMethods for Range {
// https://dom.spec.whatwg.org/#dom-range-commonancestorcontainer
fn CommonAncestorContainer(&self) -> DomRoot<Node> {
- let end_container = self.EndContainer();
- // Step 1.
- for container in self
- .StartContainer()
- .inclusive_ancestors(ShadowIncluding::No)
- {
- // Step 2.
- if container.is_inclusive_ancestor_of(&end_container) {
- // Step 3.
- return container;
- }
- }
- unreachable!();
+ self.EndContainer()
+ .common_ancestor(&self.StartContainer(), ShadowIncluding::No)
}
// https://dom.spec.whatwg.org/#dom-range-setstart
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 73d3f163ec7..946bcdcfea9 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -1659,6 +1659,14 @@ impl Window {
document.flush_dirty_canvases();
}
+ let pending_restyles = document.drain_pending_restyles();
+
+ let dirty_root = document
+ .take_dirty_root()
+ .filter(|_| !stylesheets_changed)
+ .or_else(|| document.GetDocumentElement())
+ .map(|root| root.upcast::<Node>().to_trusted_node_address());
+
// Send new document and relevant styles to layout.
let needs_display = reflow_goal.needs_display();
let reflow = ScriptReflow {
@@ -1666,13 +1674,14 @@ impl Window {
page_clip_rect: self.page_clip_rect.get(),
},
document: document.upcast::<Node>().to_trusted_node_address(),
+ dirty_root,
stylesheets_changed,
window_size: self.window_size.get(),
origin: self.origin().immutable().clone(),
reflow_goal,
script_join_chan: join_chan,
dom_count: document.dom_count(),
- pending_restyles: document.drain_pending_restyles(),
+ pending_restyles,
animation_timeline_value: document.current_animation_timeline_value(),
animations: document.animations().sets.clone(),
};
@@ -1770,12 +1779,17 @@ impl Window {
// We shouldn't need a reflow immediately after a
// reflow, except if we're waiting for a deferred paint.
- assert!({
- let condition = self.Document().needs_reflow();
- condition.is_none() ||
- (!for_display && condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
- self.suppress_reflow.get()
- });
+ let condition = self.Document().needs_reflow();
+ assert!(
+ {
+ condition.is_none() ||
+ (!for_display &&
+ condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
+ self.suppress_reflow.get()
+ },
+ "condition was {:?}",
+ condition
+ );
} else {
debug!(
"Document doesn't need reflow - skipping it (reason {:?})",
diff --git a/components/script_layout_interface/message.rs b/components/script_layout_interface/message.rs
index 1dcbfe7eb33..8495d61fc8a 100644
--- a/components/script_layout_interface/message.rs
+++ b/components/script_layout_interface/message.rs
@@ -193,6 +193,8 @@ pub struct ScriptReflow {
pub reflow_info: Reflow,
/// The document node.
pub document: TrustedNodeAddress,
+ /// The dirty root from which to restyle.
+ pub dirty_root: Option<TrustedNodeAddress>,
/// Whether the document's stylesheets have changed since the last script reflow.
pub stylesheets_changed: bool,
/// The current window size.
diff --git a/components/style/driver.rs b/components/style/driver.rs
index aa39f3482f4..eab7c3a6a31 100644
--- a/components/style/driver.rs
+++ b/components/style/driver.rs
@@ -63,7 +63,8 @@ pub fn traverse_dom<E, D>(
traversal: &D,
token: PreTraverseToken<E>,
pool: Option<&rayon::ThreadPool>,
-) where
+) -> E
+where
E: TElement,
D: DomTraversal<E>,
{
@@ -187,4 +188,6 @@ pub fn traverse_dom<E, D>(
}
}
}
+
+ root
}