1 // Copyright 2015 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/dom/node.h"
6 
7 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
8 #include "third_party/blink/renderer/bindings/core/v8/v8_shadow_root_init.h"
9 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
10 #include "third_party/blink/renderer/core/css/style_engine.h"
11 #include "third_party/blink/renderer/core/dom/comment.h"
12 #include "third_party/blink/renderer/core/dom/element.h"
13 #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
14 #include "third_party/blink/renderer/core/dom/layout_tree_builder.h"
15 #include "third_party/blink/renderer/core/dom/processing_instruction.h"
16 #include "third_party/blink/renderer/core/dom/pseudo_element.h"
17 #include "third_party/blink/renderer/core/dom/shadow_root.h"
18 #include "third_party/blink/renderer/core/dom/slot_assignment_engine.h"
19 #include "third_party/blink/renderer/core/editing/testing/editing_test_base.h"
20 #include "third_party/blink/renderer/core/html/html_div_element.h"
21 #include "third_party/blink/renderer/platform/heap/heap.h"
22 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
23 
24 namespace blink {
25 
26 class FakeMediaControlElement : public HTMLDivElement {
27  public:
FakeMediaControlElement(Document & document)28   FakeMediaControlElement(Document& document) : HTMLDivElement(document) {}
29 
IsMediaControlElement() const30   bool IsMediaControlElement() const override { return true; }
31 };
32 
33 class FakeMediaControls : public HTMLDivElement {
34  public:
FakeMediaControls(Document & document)35   FakeMediaControls(Document& document) : HTMLDivElement(document) {}
36 
IsMediaControls() const37   bool IsMediaControls() const override { return true; }
38 };
39 
40 class NodeTest : public EditingTestBase {
41  protected:
ReattachLayoutTreeForNode(Node & node)42   LayoutObject* ReattachLayoutTreeForNode(Node& node) {
43     node.SetForceReattachLayoutTree();
44     GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
45     GetDocument().GetStyleEngine().RecalcStyle();
46     Node::AttachContext context;
47     context.parent = LayoutTreeBuilderTraversal::ParentLayoutObject(node);
48     GetDocument().GetStyleEngine().in_layout_tree_rebuild_ = true;
49     node.ReattachLayoutTree(context);
50     return context.previous_in_flow;
51   }
52 
53   // Generate the following DOM structure and return the innermost <div>.
54   //  + div#root
55   //    + #shadow
56   //      + test node
57   //      |  + #shadow
58   //      |    + div class="test"
InitializeUserAgentShadowTree(Element * test_node)59   Node* InitializeUserAgentShadowTree(Element* test_node) {
60     SetBodyContent("<div id=\"root\"></div>");
61     Element* root = GetDocument().getElementById("root");
62     ShadowRoot& first_shadow = root->CreateUserAgentShadowRoot();
63 
64     first_shadow.AppendChild(test_node);
65     ShadowRoot& second_shadow = test_node->CreateUserAgentShadowRoot();
66 
67     auto* class_div = MakeGarbageCollected<HTMLDivElement>(GetDocument());
68     class_div->setAttribute("class", "test");
69     second_shadow.AppendChild(class_div);
70     return class_div;
71   }
72 };
73 
TEST_F(NodeTest,canStartSelection)74 TEST_F(NodeTest, canStartSelection) {
75   const char* body_content =
76       "<a id=one href='http://www.msn.com'>one</a><b id=two>two</b>";
77   SetBodyContent(body_content);
78   Node* one = GetDocument().getElementById("one");
79   Node* two = GetDocument().getElementById("two");
80 
81   EXPECT_FALSE(one->CanStartSelection());
82   EXPECT_FALSE(one->firstChild()->CanStartSelection());
83   EXPECT_TRUE(two->CanStartSelection());
84   EXPECT_TRUE(two->firstChild()->CanStartSelection());
85 }
86 
TEST_F(NodeTest,canStartSelectionWithShadowDOM)87 TEST_F(NodeTest, canStartSelectionWithShadowDOM) {
88   const char* body_content = "<div id=host><span id=one>one</span></div>";
89   const char* shadow_content =
90       "<a href='http://www.msn.com'><content></content></a>";
91   SetBodyContent(body_content);
92   SetShadowContent(shadow_content, "host");
93   Node* one = GetDocument().getElementById("one");
94 
95   EXPECT_FALSE(one->CanStartSelection());
96   EXPECT_FALSE(one->firstChild()->CanStartSelection());
97 }
98 
TEST_F(NodeTest,customElementState)99 TEST_F(NodeTest, customElementState) {
100   const char* body_content = "<div id=div></div>";
101   SetBodyContent(body_content);
102   Element* div = GetDocument().getElementById("div");
103   EXPECT_EQ(CustomElementState::kUncustomized, div->GetCustomElementState());
104   EXPECT_TRUE(div->IsDefined());
105   EXPECT_EQ(Node::kV0NotCustomElement, div->GetV0CustomElementState());
106 
107   div->SetCustomElementState(CustomElementState::kUndefined);
108   EXPECT_EQ(CustomElementState::kUndefined, div->GetCustomElementState());
109   EXPECT_FALSE(div->IsDefined());
110   EXPECT_EQ(Node::kV0NotCustomElement, div->GetV0CustomElementState());
111 
112   div->SetCustomElementState(CustomElementState::kCustom);
113   EXPECT_EQ(CustomElementState::kCustom, div->GetCustomElementState());
114   EXPECT_TRUE(div->IsDefined());
115   EXPECT_EQ(Node::kV0NotCustomElement, div->GetV0CustomElementState());
116 }
117 
TEST_F(NodeTest,AttachContext_PreviousInFlow_TextRoot)118 TEST_F(NodeTest, AttachContext_PreviousInFlow_TextRoot) {
119   SetBodyContent("Text");
120   Node* root = GetDocument().body()->firstChild();
121   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
122 
123   EXPECT_TRUE(previous_in_flow);
124   EXPECT_EQ(root->GetLayoutObject(), previous_in_flow);
125 }
126 
TEST_F(NodeTest,AttachContext_PreviousInFlow_InlineRoot)127 TEST_F(NodeTest, AttachContext_PreviousInFlow_InlineRoot) {
128   SetBodyContent("<span id=root>Text <span></span></span>");
129   Element* root = GetDocument().getElementById("root");
130   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
131 
132   EXPECT_TRUE(previous_in_flow);
133   EXPECT_EQ(root->GetLayoutObject(), previous_in_flow);
134 }
135 
TEST_F(NodeTest,AttachContext_PreviousInFlow_BlockRoot)136 TEST_F(NodeTest, AttachContext_PreviousInFlow_BlockRoot) {
137   SetBodyContent("<div id=root>Text <span></span></div>");
138   Element* root = GetDocument().getElementById("root");
139   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
140 
141   EXPECT_TRUE(previous_in_flow);
142   EXPECT_EQ(root->GetLayoutObject(), previous_in_flow);
143 }
144 
TEST_F(NodeTest,AttachContext_PreviousInFlow_FloatRoot)145 TEST_F(NodeTest, AttachContext_PreviousInFlow_FloatRoot) {
146   SetBodyContent("<div id=root style='float:left'><span></span></div>");
147   Element* root = GetDocument().getElementById("root");
148   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
149 
150   EXPECT_FALSE(previous_in_flow);
151 }
152 
TEST_F(NodeTest,AttachContext_PreviousInFlow_AbsoluteRoot)153 TEST_F(NodeTest, AttachContext_PreviousInFlow_AbsoluteRoot) {
154   SetBodyContent("<div id=root style='position:absolute'><span></span></div>");
155   Element* root = GetDocument().getElementById("root");
156   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
157 
158   EXPECT_FALSE(previous_in_flow);
159 }
160 
TEST_F(NodeTest,AttachContext_PreviousInFlow_Text)161 TEST_F(NodeTest, AttachContext_PreviousInFlow_Text) {
162   SetBodyContent("<div id=root style='display:contents'>Text</div>");
163   Element* root = GetDocument().getElementById("root");
164   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
165 
166   EXPECT_TRUE(previous_in_flow);
167   EXPECT_EQ(root->firstChild()->GetLayoutObject(), previous_in_flow);
168 }
169 
TEST_F(NodeTest,AttachContext_PreviousInFlow_Inline)170 TEST_F(NodeTest, AttachContext_PreviousInFlow_Inline) {
171   SetBodyContent("<div id=root style='display:contents'><span></span></div>");
172   Element* root = GetDocument().getElementById("root");
173   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
174 
175   EXPECT_TRUE(previous_in_flow);
176   EXPECT_EQ(root->firstChild()->GetLayoutObject(), previous_in_flow);
177 }
178 
TEST_F(NodeTest,AttachContext_PreviousInFlow_Block)179 TEST_F(NodeTest, AttachContext_PreviousInFlow_Block) {
180   SetBodyContent("<div id=root style='display:contents'><div></div></div>");
181   Element* root = GetDocument().getElementById("root");
182   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
183 
184   EXPECT_TRUE(previous_in_flow);
185   EXPECT_EQ(root->firstChild()->GetLayoutObject(), previous_in_flow);
186 }
187 
TEST_F(NodeTest,AttachContext_PreviousInFlow_Float)188 TEST_F(NodeTest, AttachContext_PreviousInFlow_Float) {
189   SetBodyContent(
190       "<style>"
191       "  #root { display:contents }"
192       "  .float { float:left }"
193       "</style>"
194       "<div id=root><div class=float></div></div>");
195   Element* root = GetDocument().getElementById("root");
196   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
197 
198   EXPECT_FALSE(previous_in_flow);
199 }
200 
TEST_F(NodeTest,AttachContext_PreviousInFlow_AbsolutePositioned)201 TEST_F(NodeTest, AttachContext_PreviousInFlow_AbsolutePositioned) {
202   SetBodyContent(
203       "<style>"
204       "  #root { display:contents }"
205       "  .abs { position:absolute }"
206       "</style>"
207       "<div id=root><div class=abs></div></div>");
208   Element* root = GetDocument().getElementById("root");
209   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
210 
211   EXPECT_FALSE(previous_in_flow);
212 }
213 
TEST_F(NodeTest,AttachContext_PreviousInFlow_SkipAbsolute)214 TEST_F(NodeTest, AttachContext_PreviousInFlow_SkipAbsolute) {
215   SetBodyContent(
216       "<style>"
217       "  #root { display:contents }"
218       "  .abs { position:absolute }"
219       "</style>"
220       "<div id=root>"
221       "<div class=abs></div><span id=inline></span><div class=abs></div>"
222       "</div>");
223   Element* root = GetDocument().getElementById("root");
224   Element* span = GetDocument().getElementById("inline");
225   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
226 
227   EXPECT_TRUE(previous_in_flow);
228   EXPECT_EQ(span->GetLayoutObject(), previous_in_flow);
229 }
230 
TEST_F(NodeTest,AttachContext_PreviousInFlow_SkipFloats)231 TEST_F(NodeTest, AttachContext_PreviousInFlow_SkipFloats) {
232   SetBodyContent(
233       "<style>"
234       "  #root { display:contents }"
235       "  .float { float:left }"
236       "</style>"
237       "<div id=root>"
238       "<div class=float></div>"
239       "<span id=inline></span>"
240       "<div class=float></div>"
241       "</div>");
242   Element* root = GetDocument().getElementById("root");
243   Element* span = GetDocument().getElementById("inline");
244   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
245 
246   EXPECT_TRUE(previous_in_flow);
247   EXPECT_EQ(span->GetLayoutObject(), previous_in_flow);
248 }
249 
TEST_F(NodeTest,AttachContext_PreviousInFlow_InsideDisplayContents)250 TEST_F(NodeTest, AttachContext_PreviousInFlow_InsideDisplayContents) {
251   SetBodyContent(
252       "<style>"
253       "  #root, .contents { display:contents }"
254       "  .float { float:left }"
255       "</style>"
256       "<div id=root>"
257       "<span></span><div class=contents><span id=inline></span></div>"
258       "</div>");
259   Element* root = GetDocument().getElementById("root");
260   Element* span = GetDocument().getElementById("inline");
261   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
262 
263   EXPECT_TRUE(previous_in_flow);
264   EXPECT_EQ(span->GetLayoutObject(), previous_in_flow);
265 }
266 
TEST_F(NodeTest,AttachContext_PreviousInFlow_Slotted)267 TEST_F(NodeTest, AttachContext_PreviousInFlow_Slotted) {
268   SetBodyContent("<div id=host><span id=inline></span></div>");
269   ShadowRoot& shadow_root =
270       GetDocument().getElementById("host")->AttachShadowRootInternal(
271           ShadowRootType::kOpen);
272   shadow_root.setInnerHTML(
273       "<div id=root style='display:contents'><span></span><slot></slot></div>");
274   UpdateAllLifecyclePhasesForTest();
275 
276   Element* root = shadow_root.getElementById("root");
277   Element* span = GetDocument().getElementById("inline");
278   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
279 
280   EXPECT_TRUE(previous_in_flow);
281   EXPECT_EQ(span->GetLayoutObject(), previous_in_flow);
282 }
283 
TEST_F(NodeTest,AttachContext_PreviousInFlow_V0Content)284 TEST_F(NodeTest, AttachContext_PreviousInFlow_V0Content) {
285   SetBodyContent("<div id=host><span id=inline></span></div>");
286   ShadowRoot* shadow_root = CreateShadowRootForElementWithIDAndSetInnerHTML(
287       GetDocument(), "host",
288       "<div id=root style='display:contents'><span></span><content /></div>");
289   Element* root = shadow_root->getElementById("root");
290   Element* span = GetDocument().getElementById("inline");
291   LayoutObject* previous_in_flow = ReattachLayoutTreeForNode(*root);
292 
293   EXPECT_TRUE(previous_in_flow);
294   EXPECT_EQ(span->GetLayoutObject(), previous_in_flow);
295 }
296 
TEST_F(NodeTest,HasMediaControlAncestor_Fail)297 TEST_F(NodeTest, HasMediaControlAncestor_Fail) {
298   auto* node = MakeGarbageCollected<HTMLDivElement>(GetDocument());
299   EXPECT_FALSE(node->HasMediaControlAncestor());
300   EXPECT_FALSE(InitializeUserAgentShadowTree(node)->HasMediaControlAncestor());
301 }
302 
TEST_F(NodeTest,HasMediaControlAncestor_MediaControlElement)303 TEST_F(NodeTest, HasMediaControlAncestor_MediaControlElement) {
304   FakeMediaControlElement* node =
305       MakeGarbageCollected<FakeMediaControlElement>(GetDocument());
306   EXPECT_TRUE(node->HasMediaControlAncestor());
307   EXPECT_TRUE(InitializeUserAgentShadowTree(node)->HasMediaControlAncestor());
308 }
309 
TEST_F(NodeTest,HasMediaControlAncestor_MediaControls)310 TEST_F(NodeTest, HasMediaControlAncestor_MediaControls) {
311   FakeMediaControls* node =
312       MakeGarbageCollected<FakeMediaControls>(GetDocument());
313   EXPECT_TRUE(node->HasMediaControlAncestor());
314   EXPECT_TRUE(InitializeUserAgentShadowTree(node)->HasMediaControlAncestor());
315 }
316 
TEST_F(NodeTest,appendChildProcessingInstructionNoStyleRecalc)317 TEST_F(NodeTest, appendChildProcessingInstructionNoStyleRecalc) {
318   UpdateAllLifecyclePhasesForTest();
319   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
320   auto* pi =
321       MakeGarbageCollected<ProcessingInstruction>(GetDocument(), "A", "B");
322   GetDocument().body()->appendChild(pi, ASSERT_NO_EXCEPTION);
323   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
324 }
325 
TEST_F(NodeTest,appendChildCommentNoStyleRecalc)326 TEST_F(NodeTest, appendChildCommentNoStyleRecalc) {
327   UpdateAllLifecyclePhasesForTest();
328   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
329   Comment* comment = Comment::Create(GetDocument(), "comment");
330   GetDocument().body()->appendChild(comment, ASSERT_NO_EXCEPTION);
331   EXPECT_FALSE(GetDocument().ChildNeedsStyleRecalc());
332 }
333 
TEST_F(NodeTest,MutationOutsideFlatTreeStyleDirty)334 TEST_F(NodeTest, MutationOutsideFlatTreeStyleDirty) {
335   SetBodyContent("<div id=host><span id=nonslotted></span></div>");
336   GetDocument().getElementById("host")->AttachShadowRootInternal(
337       ShadowRootType::kOpen);
338   UpdateAllLifecyclePhasesForTest();
339 
340   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdate());
341   GetDocument()
342       .getElementById("nonslotted")
343       ->setAttribute("style", "color:green");
344   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdate());
345 }
346 
TEST_F(NodeTest,SkipStyleDirtyHostChild)347 TEST_F(NodeTest, SkipStyleDirtyHostChild) {
348   SetBodyContent("<div id=host><span></span></div>");
349   Element* host = GetDocument().getElementById("host");
350   ShadowRoot& shadow_root =
351       host->AttachShadowRootInternal(ShadowRootType::kOpen);
352   shadow_root.setInnerHTML("<div style='display:none'><slot></slot></div>");
353   UpdateAllLifecyclePhasesForTest();
354   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdate());
355 
356   // Check that we do not mark an element for style recalc when the element and
357   // its flat tree parent are display:none.
358   To<Element>(host->firstChild())->setAttribute("style", "color:green");
359   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdate());
360 }
361 
TEST_F(NodeTest,ContainsChild)362 TEST_F(NodeTest, ContainsChild) {
363   SetBodyContent("<div id=a><div id=b></div></div>");
364   Element* a = GetDocument().getElementById("a");
365   Element* b = GetDocument().getElementById("b");
366   EXPECT_TRUE(a->contains(b));
367 }
368 
TEST_F(NodeTest,ContainsNoSibling)369 TEST_F(NodeTest, ContainsNoSibling) {
370   SetBodyContent("<div id=a></div><div id=b></div>");
371   Element* a = GetDocument().getElementById("a");
372   Element* b = GetDocument().getElementById("b");
373   EXPECT_FALSE(a->contains(b));
374 }
375 
TEST_F(NodeTest,ContainsPseudo)376 TEST_F(NodeTest, ContainsPseudo) {
377   SetBodyContent(
378       "<style>#a::before{content:'aaa';}</style>"
379       "<div id=a></div>");
380   Element* a = GetDocument().getElementById("a");
381   PseudoElement* pseudo = a->GetPseudoElement(kPseudoIdBefore);
382   ASSERT_TRUE(pseudo);
383   EXPECT_TRUE(a->contains(pseudo));
384 }
385 
TEST_F(NodeTest,SkipForceReattachDisplayNone)386 TEST_F(NodeTest, SkipForceReattachDisplayNone) {
387   SetBodyContent("<div id=host><span style='display:none'></span></div>");
388   Element* host = GetDocument().getElementById("host");
389   ShadowRoot& shadow_root =
390       host->AttachShadowRootInternal(ShadowRootType::kOpen);
391   shadow_root.setInnerHTML("<slot name='target'></slot>");
392   UpdateAllLifecyclePhasesForTest();
393 
394   Element* span = To<Element>(host->firstChild());
395   span->setAttribute(html_names::kSlotAttr, "target");
396   GetDocument().GetSlotAssignmentEngine().RecalcSlotAssignments();
397 
398   // Node::FlatTreeParentChanged for a display:none could trigger style recalc,
399   // but we should skip a forced re-attach for nodes with a null ComputedStyle.
400   EXPECT_TRUE(GetDocument().NeedsLayoutTreeUpdate());
401   EXPECT_TRUE(span->NeedsStyleRecalc());
402   EXPECT_FALSE(span->GetForceReattachLayoutTree());
403 }
404 
TEST_F(NodeTest,UpdateChildDirtyAncestorsOnSlotAssignment)405 TEST_F(NodeTest, UpdateChildDirtyAncestorsOnSlotAssignment) {
406   SetBodyContent("<div id=host><span></span></div>");
407   Element* host = GetDocument().getElementById("host");
408   ShadowRoot& shadow_root =
409       host->AttachShadowRootInternal(ShadowRootType::kOpen);
410   shadow_root.setInnerHTML(
411       "<div><slot></slot></div><div id='child-dirty'><slot "
412       "name='target'></slot></div>");
413   UpdateAllLifecyclePhasesForTest();
414   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdate());
415 
416   auto* span = To<Element>(host->firstChild());
417   auto* ancestor = shadow_root.getElementById("child-dirty");
418 
419   // Make sure the span is dirty before the re-assignment.
420   span->setAttribute("style", "color:green");
421   EXPECT_FALSE(ancestor->ChildNeedsStyleRecalc());
422 
423   // Re-assign to second slot.
424   span->setAttribute(html_names::kSlotAttr, "target");
425   GetDocument().GetSlotAssignmentEngine().RecalcSlotAssignments();
426   EXPECT_TRUE(ancestor->ChildNeedsStyleRecalc());
427 }
428 
TEST_F(NodeTest,UpdateChildDirtySlotAfterRemoval)429 TEST_F(NodeTest, UpdateChildDirtySlotAfterRemoval) {
430   SetBodyContent("<div id=host><span></span></div>");
431   Element* host = GetDocument().getElementById("host");
432   ShadowRoot& shadow_root =
433       host->AttachShadowRootInternal(ShadowRootType::kOpen);
434   shadow_root.setInnerHTML("<slot></slot>");
435   UpdateAllLifecyclePhasesForTest();
436 
437   auto* span = To<Element>(host->firstChild());
438   auto* slot = shadow_root.firstChild();
439 
440   // Make sure the span is dirty, and the slot marked child-dirty before the
441   // removal.
442   span->setAttribute("style", "color:green");
443   EXPECT_TRUE(span->NeedsStyleRecalc());
444   EXPECT_TRUE(slot->ChildNeedsStyleRecalc());
445   EXPECT_TRUE(host->ChildNeedsStyleRecalc());
446   EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
447   EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
448 
449   // The StyleRecalcRoot is now the span. Removing the span should clear the
450   // root and the child-dirty bits on the ancestors.
451   span->remove();
452 
453   EXPECT_FALSE(slot->ChildNeedsStyleRecalc());
454   EXPECT_FALSE(host->ChildNeedsStyleRecalc());
455   EXPECT_FALSE(GetDocument().body()->ChildNeedsStyleRecalc());
456   EXPECT_FALSE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
457 }
458 
TEST_F(NodeTest,UpdateChildDirtyAfterSlotRemoval)459 TEST_F(NodeTest, UpdateChildDirtyAfterSlotRemoval) {
460   SetBodyContent("<div id=host><span></span></div>");
461   Element* host = GetDocument().getElementById("host");
462   ShadowRoot& shadow_root =
463       host->AttachShadowRootInternal(ShadowRootType::kOpen);
464   shadow_root.setInnerHTML("<div><slot></slot></div>");
465   UpdateAllLifecyclePhasesForTest();
466 
467   auto* span = To<Element>(host->firstChild());
468   auto* div = shadow_root.firstChild();
469   auto* slot = div->firstChild();
470 
471   // Make sure the span is dirty, and the slot marked child-dirty before the
472   // removal.
473   span->setAttribute("style", "color:green");
474   EXPECT_TRUE(span->NeedsStyleRecalc());
475   EXPECT_TRUE(slot->ChildNeedsStyleRecalc());
476   EXPECT_TRUE(div->ChildNeedsStyleRecalc());
477   EXPECT_TRUE(host->ChildNeedsStyleRecalc());
478   EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
479   EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
480 
481   // The StyleRecalcRoot is now the span. Removing the slot would break the flat
482   // tree ancestor chain so that when removing the span we would no longer be
483   // able to clear the dirty bits for all of the previous ancestor chain. Thus,
484   // we fall back to use the host as the style recalc root to be able to
485   // traverse and clear the dirty bit of the shadow tree div element on the next
486   // style recalc.
487   slot->remove();
488   span->remove();
489 
490   EXPECT_TRUE(div->ChildNeedsStyleRecalc());
491   EXPECT_TRUE(host->ChildNeedsStyleRecalc());
492   EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
493   EXPECT_TRUE(GetDocument().GetStyleEngine().NeedsStyleRecalc());
494 }
495 
TEST_F(NodeTest,UpdateChildDirtyAfterSlottingDirtyNode)496 TEST_F(NodeTest, UpdateChildDirtyAfterSlottingDirtyNode) {
497   SetBodyContent("<div id=host><span></span></div>");
498 
499   auto* host = GetDocument().getElementById("host");
500   auto* span = To<Element>(host->firstChild());
501 
502   ShadowRoot& shadow_root =
503       host->AttachShadowRootInternal(ShadowRootType::kOpen);
504   shadow_root.setInnerHTML("<div><slot name=x></slot></div>");
505   UpdateAllLifecyclePhasesForTest();
506 
507   // Make sure the span is style dirty.
508   span->setAttribute("style", "color:green");
509 
510   // Assign span to slot.
511   span->setAttribute("slot", "x");
512 
513   GetDocument().GetSlotAssignmentEngine().RecalcSlotAssignments();
514 
515   // Make sure shadow tree div and slot are marked with ChildNeedsStyleRecalc
516   // when the dirty span is slotted in.
517   EXPECT_TRUE(shadow_root.firstChild()->ChildNeedsStyleRecalc());
518   EXPECT_TRUE(shadow_root.firstChild()->firstChild()->ChildNeedsStyleRecalc());
519   EXPECT_TRUE(span->NeedsStyleRecalc());
520 
521   // This used to call a DCHECK failure. Make sure we don't regress.
522   UpdateAllLifecyclePhasesForTest();
523 }
524 
TEST_F(NodeTest,ChildDirtyNeedsV0Distribution)525 TEST_F(NodeTest, ChildDirtyNeedsV0Distribution) {
526   SetBodyContent("<div id=host><span></span> </div>");
527   ShadowRoot* shadow_root = CreateShadowRootForElementWithIDAndSetInnerHTML(
528       GetDocument(), "host", "<content />");
529   UpdateAllLifecyclePhasesForTest();
530 
531 #if DCHECK_IS_ON()
532   GetDocument().SetAllowDirtyShadowV0Traversal(true);
533 #endif
534 
535   auto* host = GetDocument().getElementById("host");
536   auto* span = To<Element>(host->firstChild());
537   auto* content = shadow_root->firstChild();
538 
539   host->lastChild()->remove();
540 
541   EXPECT_FALSE(GetDocument().documentElement()->ChildNeedsStyleRecalc());
542   EXPECT_TRUE(GetDocument().documentElement()->ChildNeedsDistributionRecalc());
543   EXPECT_EQ(content, host->firstChild()->GetStyleRecalcParent());
544   EXPECT_FALSE(content->ChildNeedsStyleRecalc());
545 
546   // Make the span style dirty.
547   span->setAttribute("style", "color:green");
548 
549   // Check that the flat tree ancestor chain is child-dirty while the
550   // shadow distribution is still dirty.
551   EXPECT_TRUE(GetDocument().documentElement()->ChildNeedsStyleRecalc());
552   EXPECT_TRUE(GetDocument().body()->ChildNeedsStyleRecalc());
553   EXPECT_TRUE(host->ChildNeedsStyleRecalc());
554   EXPECT_TRUE(content->ChildNeedsStyleRecalc());
555   EXPECT_TRUE(GetDocument().documentElement()->ChildNeedsDistributionRecalc());
556 
557 #if DCHECK_IS_ON()
558   GetDocument().SetAllowDirtyShadowV0Traversal(false);
559 #endif
560 }
561 
562 }  // namespace blink
563