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