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 = >::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::( - &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::::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, /// Animations for this Document animations: DomRefCell, + /// The nearest inclusive ancestors to all the nodes that require a restyle. + dirty_root: MutNullableDom, } #[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::() + .inclusive_ancestors(ShadowIncluding::Yes) + { + if ancestor.is::() { + ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + } + } + } + self.dirty_root.set(Some(&document_element)); + return; + }, + }; + + if parent.is::() { + if !parent.is_styled() { + return; + } + + if parent.is_display_none() { + return; + } + } + + let element_parent: DomRoot; + let element = match node.downcast::() { + 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::(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::() + .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + self.dirty_root.set(Some(element)); + return; + }, + Some(root) => root, + }; + + for ancestor in element + .upcast::() + .inclusive_ancestors(ShadowIncluding::Yes) + { + if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) { + return; + } + if ancestor.is::() { + ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + } + } + + let new_dirty_root = element + .upcast::() + .common_ancestor(dirty_root.upcast(), ShadowIncluding::Yes); + + let mut has_dirty_descendants = true; + for ancestor in dirty_root + .upcast::() + .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::().unwrap())); + } + + pub fn take_dirty_root(&self) -> Option> { + self.dirty_root.take() + } + #[inline] pub fn loader(&self) -> Ref { self.loader.borrow() @@ -967,8 +1075,14 @@ impl Document { } pub fn dirty_all_nodes(&self) { - let root = self.upcast::(); - for node in root.traverse_preorder(ShadowIncluding::Yes) { + let root = match self.GetDocumentElement() { + Some(root) => root, + None => return, + }; + for node in root + .upcast::() + .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::().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) { 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::() { - 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 { + 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> { 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 { - 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::().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::().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, /// 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( traversal: &D, token: PreTraverseToken, pool: Option<&rayon::ThreadPool>, -) where +) -> E +where E: TElement, D: DomTraversal, { @@ -187,4 +188,6 @@ pub fn traverse_dom( } } } + + root }