diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/document.rs | 119 | ||||
-rw-r--r-- | components/script/dom/element.rs | 2 | ||||
-rw-r--r-- | components/script/dom/node.rs | 52 | ||||
-rw-r--r-- | components/script/dom/range.rs | 15 | ||||
-rw-r--r-- | components/script/dom/window.rs | 28 |
5 files changed, 182 insertions, 34 deletions
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 {:?})", |