Commit 244b32e0 authored by Rune Lillesveen's avatar Rune Lillesveen Committed by Commit Bot

Clear style recalc root for flat tree.

For FlatTreeStyleRecalc enabled, we did not walk the slot and ancestors
clearing child-dirty bits when clearing a style recalc root as part of
removing shadow host children. That would leave stray child-dirty bits.

Here, we find the child-dirty slot and start clearing child-dirty bits
from the slot and up the flat tree ancestor chain. If the slot was
removed before the shadow host child, fall back to use the light tree
parent of the removed nodes as the new style recalc root instead.

Fall back to use the light tree parent for Shadow DOM V0 as trying to
find the flat tree parent to start from is too complicated.

TEST=shadow-dom/crashes/cyclic-detach-crash2.html

Bug: 972752
Change-Id: If503bb3e102208440dae5b42c9231e680dc4d0b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1899372Reviewed-by: default avatarMorten Stenshorne <mstensho@chromium.org>
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712937}
parent ef9a3b0c
...@@ -48,8 +48,7 @@ bool LayoutTreeRebuildRoot::IsDirty(const Node& node) const { ...@@ -48,8 +48,7 @@ bool LayoutTreeRebuildRoot::IsDirty(const Node& node) const {
return node.NeedsReattachLayoutTree(); return node.NeedsReattachLayoutTree();
} }
void LayoutTreeRebuildRoot::ClearChildDirtyForAncestors( void LayoutTreeRebuildRoot::RootRemoved(ContainerNode& parent) {
ContainerNode& parent) const {
Element* ancestor = DynamicTo<Element>(parent); Element* ancestor = DynamicTo<Element>(parent);
if (!ancestor) if (!ancestor)
ancestor = parent.ParentOrShadowHostElement(); ancestor = parent.ParentOrShadowHostElement();
...@@ -58,6 +57,7 @@ void LayoutTreeRebuildRoot::ClearChildDirtyForAncestors( ...@@ -58,6 +57,7 @@ void LayoutTreeRebuildRoot::ClearChildDirtyForAncestors(
DCHECK(!ancestor->NeedsReattachLayoutTree()); DCHECK(!ancestor->NeedsReattachLayoutTree());
ancestor->ClearChildNeedsReattachLayoutTree(); ancestor->ClearChildNeedsReattachLayoutTree();
} }
Clear();
} }
} // namespace blink } // namespace blink
...@@ -21,7 +21,7 @@ class CORE_EXPORT LayoutTreeRebuildRoot : public StyleTraversalRoot { ...@@ -21,7 +21,7 @@ class CORE_EXPORT LayoutTreeRebuildRoot : public StyleTraversalRoot {
bool IsChildDirty(const ContainerNode& node) const final; bool IsChildDirty(const ContainerNode& node) const final;
#endif // DCHECK_IS_ON() #endif // DCHECK_IS_ON()
bool IsDirty(const Node& node) const final; bool IsDirty(const Node& node) const final;
void ClearChildDirtyForAncestors(ContainerNode& parent) const final; void RootRemoved(ContainerNode& parent) final;
}; };
} // namespace blink } // namespace blink
......
...@@ -33,14 +33,14 @@ bool StyleInvalidationRoot::IsDirty(const Node& node) const { ...@@ -33,14 +33,14 @@ bool StyleInvalidationRoot::IsDirty(const Node& node) const {
return node.NeedsStyleInvalidation(); return node.NeedsStyleInvalidation();
} }
void StyleInvalidationRoot::ClearChildDirtyForAncestors( void StyleInvalidationRoot::RootRemoved(ContainerNode& parent) {
ContainerNode& parent) const {
for (Node* ancestor = &parent; ancestor; for (Node* ancestor = &parent; ancestor;
ancestor = ancestor->ParentOrShadowHostNode()) { ancestor = ancestor->ParentOrShadowHostNode()) {
DCHECK(ancestor->ChildNeedsStyleInvalidation()); DCHECK(ancestor->ChildNeedsStyleInvalidation());
DCHECK(!ancestor->NeedsStyleInvalidation()); DCHECK(!ancestor->NeedsStyleInvalidation());
ancestor->ClearChildNeedsStyleInvalidation(); ancestor->ClearChildNeedsStyleInvalidation();
} }
Clear();
} }
} // namespace blink } // namespace blink
...@@ -21,7 +21,7 @@ class CORE_EXPORT StyleInvalidationRoot : public StyleTraversalRoot { ...@@ -21,7 +21,7 @@ class CORE_EXPORT StyleInvalidationRoot : public StyleTraversalRoot {
bool IsChildDirty(const ContainerNode& node) const final; bool IsChildDirty(const ContainerNode& node) const final;
#endif // DCHECK_IS_ON() #endif // DCHECK_IS_ON()
bool IsDirty(const Node& node) const final; bool IsDirty(const Node& node) const final;
void ClearChildDirtyForAncestors(ContainerNode& parent) const final; void RootRemoved(ContainerNode& parent) final;
}; };
} // namespace blink } // namespace blink
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/slot_assignment.h"
#include "third_party/blink/renderer/core/html/html_slot_element.h"
namespace blink { namespace blink {
...@@ -48,17 +50,64 @@ bool StyleRecalcRoot::IsDirty(const Node& node) const { ...@@ -48,17 +50,64 @@ bool StyleRecalcRoot::IsDirty(const Node& node) const {
return node.IsDirtyForStyleRecalc(); return node.IsDirtyForStyleRecalc();
} }
void StyleRecalcRoot::ClearChildDirtyForAncestors(ContainerNode& parent) const { namespace {
base::Optional<Member<Element>> FirstFlatTreeAncestorForChildDirty(
ContainerNode& parent) {
DCHECK(RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled());
if (!parent.IsElementNode()) {
// The flat tree does not contain shadow roots or the document node. The
// closest ancestor for dirty bits is the shadow host or nullptr.
return parent.ParentOrShadowHostElement();
}
ShadowRoot* root = parent.GetShadowRoot();
if (!root)
return To<Element>(&parent);
if (root->IsV0()) {
// Fall back to use the parent as the new style recalc root for Shadow DOM
// V0. It is too complicated to try to find the closest flat tree parent.
return base::nullopt;
}
// The child has already been removed, so we cannot look up its slot
// assignment directly. Find the slot which was part of the ancestor chain
// before the removal by checking the child-dirty bits. Since the recalc root
// was removed, there is at most one such child-dirty slot.
for (const auto slot : root->GetSlotAssignment().Slots()) {
if (slot->ChildNeedsStyleRecalc())
return slot;
}
// The slot has also been removed. Fall back to using the light tree parent as
// the new recalc root.
return base::nullopt;
}
} // namespace
void StyleRecalcRoot::RootRemoved(ContainerNode& parent) {
ContainerNode* ancestor = &parent; ContainerNode* ancestor = &parent;
if (RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled() && if (RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled()) {
!parent.IsElementNode()) { // We are notified with the light tree parent of the node(s) which were
ancestor = parent.ParentOrShadowHostElement(); // removed from the DOM. If 'parent' is a shadow host, there are elements in
// its shadow tree which are marked child-dirty which needs to be cleared in
// order to clear the recalc root below. If we are not able to find the
// closest flat tree ancestor for traversal, fall back to using the 'parent'
// as the new recalc root to allow the child-dirty bits to be cleared on the
// next style recalc.
auto opt_ancestor = FirstFlatTreeAncestorForChildDirty(parent);
if (!opt_ancestor) {
Update(&parent, &parent);
DCHECK(!IsSingleRoot());
DCHECK_EQ(GetRootNode(), ancestor);
return;
}
ancestor = opt_ancestor.value();
} }
for (; ancestor; ancestor = ancestor->GetStyleRecalcParent()) { for (; ancestor; ancestor = ancestor->GetStyleRecalcParent()) {
DCHECK(ancestor->ChildNeedsStyleRecalc()); DCHECK(ancestor->ChildNeedsStyleRecalc());
DCHECK(!ancestor->NeedsStyleRecalc()); DCHECK(!ancestor->NeedsStyleRecalc());
ancestor->ClearChildNeedsStyleRecalc(); ancestor->ClearChildNeedsStyleRecalc();
} }
Clear();
} }
} // namespace blink } // namespace blink
...@@ -21,7 +21,7 @@ class CORE_EXPORT StyleRecalcRoot : public StyleTraversalRoot { ...@@ -21,7 +21,7 @@ class CORE_EXPORT StyleRecalcRoot : public StyleTraversalRoot {
bool IsChildDirty(const ContainerNode& node) const final; bool IsChildDirty(const ContainerNode& node) const final;
#endif // DCHECK_IS_ON() #endif // DCHECK_IS_ON()
bool IsDirty(const Node& node) const final; bool IsDirty(const Node& node) const final;
void ClearChildDirtyForAncestors(ContainerNode& parent) const final; void RootRemoved(ContainerNode& parent) final;
}; };
} // namespace blink } // namespace blink
......
...@@ -53,10 +53,8 @@ void StyleTraversalRoot::Update(ContainerNode* common_ancestor, ...@@ -53,10 +53,8 @@ void StyleTraversalRoot::Update(ContainerNode* common_ancestor,
} }
void StyleTraversalRoot::ChildrenRemoved(ContainerNode& parent) { void StyleTraversalRoot::ChildrenRemoved(ContainerNode& parent) {
if (!root_node_ || root_node_->isConnected()) if (root_node_ && !root_node_->isConnected())
return; RootRemoved(parent);
ClearChildDirtyForAncestors(parent);
Clear();
} }
} // namespace blink } // namespace blink
...@@ -63,8 +63,8 @@ class CORE_EXPORT StyleTraversalRoot { ...@@ -63,8 +63,8 @@ class CORE_EXPORT StyleTraversalRoot {
// Return true if the given node is dirty. // Return true if the given node is dirty.
virtual bool IsDirty(const Node&) const = 0; virtual bool IsDirty(const Node&) const = 0;
// Clear the child-dirty flag on the ancestors of the given node. // Update the root node when removed.
virtual void ClearChildDirtyForAncestors(ContainerNode& parent) const = 0; virtual void RootRemoved(ContainerNode& parent) = 0;
bool IsSingleRoot() const { return root_type_ == RootType::kSingleRoot; } bool IsSingleRoot() const { return root_type_ == RootType::kSingleRoot; }
......
...@@ -38,11 +38,12 @@ class StyleTraversalRootTestImpl : public StyleTraversalRoot { ...@@ -38,11 +38,12 @@ class StyleTraversalRootTestImpl : public StyleTraversalRoot {
bool IsDirty(const Node& node) const final { bool IsDirty(const Node& node) const final {
return dirty_nodes_.Contains(&node); return dirty_nodes_.Contains(&node);
} }
void ClearChildDirtyForAncestors(ContainerNode& parent) const override { void RootRemoved(ContainerNode& parent) override {
for (ContainerNode* ancestor = &parent; ancestor; for (ContainerNode* ancestor = &parent; ancestor;
ancestor = ParentInternal(*ancestor)) { ancestor = ParentInternal(*ancestor)) {
child_dirty_nodes_.erase(ancestor); child_dirty_nodes_.erase(ancestor);
} }
Clear();
} }
HeapHashSet<Member<const Node>> dirty_nodes_; HeapHashSet<Member<const Node>> dirty_nodes_;
......
...@@ -431,4 +431,75 @@ TEST_F(NodeTest, UpdateChildDirtyAncestorsOnSlotAssignment) { ...@@ -431,4 +431,75 @@ TEST_F(NodeTest, UpdateChildDirtyAncestorsOnSlotAssignment) {
EXPECT_TRUE(ancestor->ChildNeedsStyleRecalc()); EXPECT_TRUE(ancestor->ChildNeedsStyleRecalc());
} }
TEST_F(NodeTest, UpdateChildDirtySlotAfterRemoval) {
ScopedFlatTreeStyleRecalcForTest scope(true);
SetBodyContent("<div id=host><span></span></div>");
Element* host = GetDocument().getElementById("host");
ShadowRoot& shadow_root =
host->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.SetInnerHTMLFromString("<slot></slot>");
UpdateAllLifecyclePhasesForTest();
auto* span = To<Element>(host->firstChild());
auto* slot = shadow_root.firstChild();
// Make sure the span is dirty, and the slot marked child-dirty before the
// removal.
span->setAttribute("style", "color:green");
EXPECT_TRUE(span->NeedsStyleRecalc());
EXPECT_TRUE(slot->ChildNeedsStyleRecalc());
EXPECT_TRUE(host->ChildNeedsStyleRecalc());
EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
// The StyleRecalcRoot is now the span. Removing the span should clear the
// root and the child-dirty bits on the ancestors.
span->remove();
EXPECT_FALSE(slot->ChildNeedsStyleRecalc());
EXPECT_FALSE(host->ChildNeedsStyleRecalc());
EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_FALSE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
}
TEST_F(NodeTest, UpdateChildDirtyAfterSlotRemoval) {
ScopedFlatTreeStyleRecalcForTest scope(true);
SetBodyContent("<div id=host><span></span></div>");
Element* host = GetDocument().getElementById("host");
ShadowRoot& shadow_root =
host->AttachShadowRootInternal(ShadowRootType::kOpen);
shadow_root.SetInnerHTMLFromString("<div><slot></slot></div>");
UpdateAllLifecyclePhasesForTest();
auto* span = To<Element>(host->firstChild());
auto* div = shadow_root.firstChild();
auto* slot = div->firstChild();
// Make sure the span is dirty, and the slot marked child-dirty before the
// removal.
span->setAttribute("style", "color:green");
EXPECT_TRUE(span->NeedsStyleRecalc());
EXPECT_TRUE(slot->ChildNeedsStyleRecalc());
EXPECT_TRUE(div->ChildNeedsStyleRecalc());
EXPECT_TRUE(host->ChildNeedsStyleRecalc());
EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
// The StyleRecalcRoot is now the span. Removing the slot would break the flat
// tree ancestor chain so that when removing the span we would no longer be
// able to clear the dirty bits for all of the previous ancestor chain. Thus,
// we fall back to use the host as the style recalc root to be able to
// traverse and clear the dirty bit of the shadow tree div element on the next
// style recalc.
slot->remove();
span->remove();
EXPECT_TRUE(div->ChildNeedsStyleRecalc());
EXPECT_TRUE(host->ChildNeedsStyleRecalc());
EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
}
} // namespace blink } // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment