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