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