1 // Copyright 2019 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/svg/svg_element.h"
6
7 #include "third_party/blink/renderer/core/dom/dom_implementation.h"
8 #include "third_party/blink/renderer/core/dom/shadow_root.h"
9 #include "third_party/blink/renderer/core/html/html_element.h"
10 #include "third_party/blink/renderer/core/svg/svg_use_element.h"
11 #include "third_party/blink/renderer/core/testing/page_test_base.h"
12
13 namespace blink {
14
15 using LifecycleUpdateReason = DocumentUpdateReason;
16
17 class SVGUseElementTest : public PageTestBase {};
18
TEST_F(SVGUseElementTest,InstanceInvalidatedWhenNonAttachedTargetRemoved)19 TEST_F(SVGUseElementTest, InstanceInvalidatedWhenNonAttachedTargetRemoved) {
20 GetDocument().body()->setInnerHTML(R"HTML(
21 <style></style>
22 <svg>
23 <unknown>
24 <g id="parent">
25 <a id="target">
26 </g>
27 <use id="use" href="#parent">
28 </unknown>
29 </svg>
30 )HTML");
31 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
32
33 // Remove #target.
34 ASSERT_TRUE(GetDocument().getElementById("target"));
35 GetDocument().getElementById("target")->remove();
36
37 // This should cause a rebuild of the <use> shadow tree.
38 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
39
40 // There should be no instance for #target anymore, since that element was
41 // removed.
42 auto* use = To<SVGUseElement>(GetDocument().getElementById("use"));
43 ASSERT_TRUE(use);
44 ASSERT_TRUE(use->GetShadowRoot());
45 ASSERT_FALSE(use->GetShadowRoot()->getElementById("target"));
46 }
47
TEST_F(SVGUseElementTest,InstanceInvalidatedWhenNonAttachedTargetMovedInDocument)48 TEST_F(SVGUseElementTest,
49 InstanceInvalidatedWhenNonAttachedTargetMovedInDocument) {
50 GetDocument().body()->setInnerHTML(R"HTML(
51 <svg>
52 <use id="use" href="#path"/>
53 <textPath id="path">
54 <textPath>
55 <a id="target" systemLanguage="th"></a>
56 </textPath>
57 </textPath>
58 </svg>
59 )HTML");
60 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
61
62 // Move #target in the document (leaving it still "connected").
63 Element* target = GetDocument().getElementById("target");
64 ASSERT_TRUE(target);
65 GetDocument().body()->appendChild(target);
66
67 // This should cause a rebuild of the <use> shadow tree.
68 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
69
70 // There should be no instance for #target anymore, since that element was
71 // removed.
72 auto* use = To<SVGUseElement>(GetDocument().getElementById("use"));
73 ASSERT_TRUE(use);
74 ASSERT_TRUE(use->GetShadowRoot());
75 ASSERT_FALSE(use->GetShadowRoot()->getElementById("target"));
76 }
77
TEST_F(SVGUseElementTest,NullInstanceRootWhenNotConnectedToDocument)78 TEST_F(SVGUseElementTest, NullInstanceRootWhenNotConnectedToDocument) {
79 GetDocument().body()->setInnerHTML(R"HTML(
80 <svg>
81 <defs>
82 <rect id="r" width="100" height="100" fill="blue"/>
83 </defs>
84 <use id="target" href="#r"/>
85 </svg>
86 )HTML");
87 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
88
89 auto* target = To<SVGUseElement>(GetDocument().getElementById("target"));
90 ASSERT_TRUE(target);
91 ASSERT_TRUE(target->InstanceRoot());
92
93 target->remove();
94
95 ASSERT_FALSE(target->InstanceRoot());
96 }
97
TEST_F(SVGUseElementTest,NullInstanceRootWhenConnectedToInactiveDocument)98 TEST_F(SVGUseElementTest, NullInstanceRootWhenConnectedToInactiveDocument) {
99 GetDocument().body()->setInnerHTML(R"HTML(
100 <svg>
101 <defs>
102 <rect id="r" width="100" height="100" fill="blue"/>
103 </defs>
104 <use id="target" href="#r"/>
105 </svg>
106 )HTML");
107 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
108
109 auto* target = To<SVGUseElement>(GetDocument().getElementById("target"));
110 ASSERT_TRUE(target);
111 ASSERT_TRUE(target->InstanceRoot());
112
113 Document* other_document =
114 GetDocument().implementation().createHTMLDocument();
115 other_document->body()->appendChild(target);
116
117 ASSERT_FALSE(target->InstanceRoot());
118 }
119
TEST_F(SVGUseElementTest,NullInstanceRootWhenShadowTreePendingRebuild)120 TEST_F(SVGUseElementTest, NullInstanceRootWhenShadowTreePendingRebuild) {
121 GetDocument().body()->setInnerHTML(R"HTML(
122 <svg>
123 <defs>
124 <rect id="r" width="100" height="100" fill="blue"/>
125 </defs>
126 <use id="target" href="#r"/>
127 </svg>
128 )HTML");
129 GetDocument().View()->UpdateAllLifecyclePhases(LifecycleUpdateReason::kTest);
130
131 auto* target = To<SVGUseElement>(GetDocument().getElementById("target"));
132 ASSERT_TRUE(target);
133 ASSERT_TRUE(target->InstanceRoot());
134
135 GetDocument().getElementById("r")->setAttribute(html_names::kWidthAttr, "50");
136
137 ASSERT_FALSE(target->InstanceRoot());
138 }
139
140 } // namespace blink
141