1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/css/style_recalc_root.h"
6 #include "third_party/blink/renderer/core/dom/document.h"
7 #include "third_party/blink/renderer/core/dom/element.h"
8 #include "third_party/blink/renderer/core/dom/shadow_root.h"
9 #include "third_party/blink/renderer/core/dom/shadow_root_v0.h"
10 #include "third_party/blink/renderer/core/dom/slot_assignment.h"
11 #include "third_party/blink/renderer/core/html/html_slot_element.h"
12
13 namespace blink {
14
RootElement() const15 Element& StyleRecalcRoot::RootElement() const {
16 Node* root_node = GetRootNode();
17 DCHECK(root_node);
18 if (root_node->IsDocumentNode())
19 return *root_node->GetDocument().documentElement();
20 if (root_node->IsPseudoElement()) {
21 // We could possibly have called UpdatePseudoElement, but start at the
22 // originating element for simplicity.
23 return *root_node->parentElement();
24 }
25 if (!RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled()) {
26 if (root_node->IsInShadowTree()) {
27 // Since we traverse in light tree order, we might need to traverse
28 // slotted shadow host children for inheritance for which the recalc root
29 // is not an ancestor. Since we might re-slot slots, we need to start at
30 // the outermost shadow host.
31 TreeScope* tree_scope = &root_node->GetTreeScope();
32 while (!tree_scope->ParentTreeScope()->RootNode().IsDocumentNode())
33 tree_scope = tree_scope->ParentTreeScope();
34 return To<ShadowRoot>(tree_scope->RootNode()).host();
35 }
36 }
37 if (root_node->IsTextNode())
38 root_node = root_node->GetStyleRecalcParent();
39 return To<Element>(*root_node);
40 }
41
42 #if DCHECK_IS_ON()
Parent(const Node & node) const43 ContainerNode* StyleRecalcRoot::Parent(const Node& node) const {
44 return node.GetStyleRecalcParent();
45 }
46 #endif // DCHECK_IS_ON()
47
IsDirty(const Node & node) const48 bool StyleRecalcRoot::IsDirty(const Node& node) const {
49 return node.IsDirtyForStyleRecalc();
50 }
51
52 namespace {
53
FirstFlatTreeAncestorForChildDirty(ContainerNode & parent)54 base::Optional<Member<Element>> FirstFlatTreeAncestorForChildDirty(
55 ContainerNode& parent) {
56 DCHECK(RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled());
57 if (!parent.IsElementNode()) {
58 // The flat tree does not contain shadow roots or the document node. The
59 // closest ancestor for dirty bits is the shadow host or nullptr.
60 return parent.ParentOrShadowHostElement();
61 }
62 ShadowRoot* root = parent.GetShadowRoot();
63 if (!root)
64 return To<Element>(&parent);
65 if (root->IsV0()) {
66 // The child has already been removed, so we cannot look up its insertion
67 // point directly. Find the insertion point which was part of the ancestor
68 // chain before the removal by checking the child-dirty bits. Since the
69 // recalc root was removed, there is at most one such child-dirty insertion
70 // point.
71 for (const auto& insertion_point : root->V0().DescendantInsertionPoints()) {
72 if (insertion_point->ChildNeedsStyleRecalc())
73 return insertion_point;
74 }
75 return base::nullopt;
76 }
77 if (!root->HasSlotAssignment())
78 return base::nullopt;
79 // The child has already been removed, so we cannot look up its slot
80 // assignment directly. Find the slot which was part of the ancestor chain
81 // before the removal by checking the child-dirty bits. Since the recalc root
82 // was removed, there is at most one such child-dirty slot.
83 for (const auto& slot : root->GetSlotAssignment().Slots()) {
84 if (slot->ChildNeedsStyleRecalc())
85 return slot;
86 }
87 // The slot has also been removed. Fall back to using the light tree parent as
88 // the new recalc root.
89 return base::nullopt;
90 }
91
92 } // namespace
93
RootRemoved(ContainerNode & parent)94 void StyleRecalcRoot::RootRemoved(ContainerNode& parent) {
95 ContainerNode* ancestor = &parent;
96 if (RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled()) {
97 // We are notified with the light tree parent of the node(s) which were
98 // removed from the DOM. If 'parent' is a shadow host, there are elements in
99 // its shadow tree which are marked child-dirty which needs to be cleared in
100 // order to clear the recalc root below. If we are not able to find the
101 // closest flat tree ancestor for traversal, fall back to using the 'parent'
102 // as the new recalc root to allow the child-dirty bits to be cleared on the
103 // next style recalc.
104 auto opt_ancestor = FirstFlatTreeAncestorForChildDirty(parent);
105 if (!opt_ancestor) {
106 Update(&parent, &parent);
107 DCHECK(!IsSingleRoot());
108 DCHECK_EQ(GetRootNode(), ancestor);
109 return;
110 }
111 ancestor = opt_ancestor.value();
112 }
113 for (; ancestor; ancestor = ancestor->GetStyleRecalcParent()) {
114 DCHECK(ancestor->ChildNeedsStyleRecalc());
115 DCHECK(!ancestor->NeedsStyleRecalc());
116 ancestor->ClearChildNeedsStyleRecalc();
117 }
118 Clear();
119 }
120
RemovedFromFlatTree(const Node & node)121 void StyleRecalcRoot::RemovedFromFlatTree(const Node& node) {
122 if (!RuntimeEnabledFeatures::FlatTreeStyleRecalcEnabled())
123 return;
124 if (!GetRootNode())
125 return;
126 if (GetRootNode()->IsDocumentNode())
127 return;
128 // If the recalc root is the removed node, or if it's a descendant of the root
129 // node, the recalc flags will be cleared in DetachLayoutTree() since
130 // performing_reattach=false. If that's the case, call RootRemoved() below to
131 // make sure we don't have a recalc root outside the flat tree, which is not
132 // allowed with FlatTreeStyleRecalc enabled.
133 if (GetRootNode()->NeedsStyleRecalc() ||
134 GetRootNode()->GetForceReattachLayoutTree() ||
135 GetRootNode()->ChildNeedsStyleRecalc()) {
136 return;
137 }
138 DCHECK(node.parentElement());
139 RootRemoved(*node.parentElement());
140 }
141
142 } // namespace blink
143