OLD | NEW |
| (Empty) |
1 // Copyright 2014 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 "core/HTMLNames.h" | |
6 #include "core/dom/Document.h" | |
7 #include "core/dom/Element.h" | |
8 #include "core/dom/ElementTraversal.h" | |
9 #include "core/dom/NodeComputedStyle.h" | |
10 #include "core/dom/StyleEngine.h" | |
11 #include "core/frame/FrameView.h" | |
12 #include "core/html/HTMLElement.h" | |
13 #include "core/testing/DummyPageHolder.h" | |
14 #include "testing/gtest/include/gtest/gtest.h" | |
15 #include <memory> | |
16 | |
17 namespace blink { | |
18 | |
19 using namespace HTMLNames; | |
20 | |
21 class AffectedByFocusTest : public ::testing::Test { | |
22 protected: | |
23 struct ElementResult { | |
24 const blink::HTMLQualifiedName tag; | |
25 bool affected_by; | |
26 bool children_or_siblings_affected_by; | |
27 }; | |
28 | |
29 void SetUp() override; | |
30 | |
31 Document& GetDocument() const { return *document_; } | |
32 | |
33 void SetHtmlInnerHTML(const char* html_content); | |
34 | |
35 void CheckElements(ElementResult expected[], unsigned expected_count) const; | |
36 | |
37 private: | |
38 std::unique_ptr<DummyPageHolder> dummy_page_holder_; | |
39 | |
40 Persistent<Document> document_; | |
41 }; | |
42 | |
43 void AffectedByFocusTest::SetUp() { | |
44 dummy_page_holder_ = DummyPageHolder::Create(IntSize(800, 600)); | |
45 document_ = &dummy_page_holder_->GetDocument(); | |
46 DCHECK(document_); | |
47 } | |
48 | |
49 void AffectedByFocusTest::SetHtmlInnerHTML(const char* html_content) { | |
50 GetDocument().documentElement()->setInnerHTML(String::FromUTF8(html_content)); | |
51 GetDocument().View()->UpdateAllLifecyclePhases(); | |
52 } | |
53 | |
54 void AffectedByFocusTest::CheckElements(ElementResult expected[], | |
55 unsigned expected_count) const { | |
56 unsigned i = 0; | |
57 HTMLElement* element = GetDocument().body(); | |
58 | |
59 for (; element && i < expected_count; | |
60 element = Traversal<HTMLElement>::Next(*element), ++i) { | |
61 ASSERT_TRUE(element->HasTagName(expected[i].tag)); | |
62 DCHECK(element->GetComputedStyle()); | |
63 ASSERT_EQ(expected[i].affected_by, | |
64 element->GetComputedStyle()->AffectedByFocus()); | |
65 ASSERT_EQ(expected[i].children_or_siblings_affected_by, | |
66 element->ChildrenOrSiblingsAffectedByFocus()); | |
67 } | |
68 | |
69 DCHECK(!element); | |
70 DCHECK_EQ(i, expected_count); | |
71 } | |
72 | |
73 // A global :focus rule in html.css currently causes every single element to be | |
74 // affectedByFocus. Check that all elements in a document with no :focus rules | |
75 // gets the affectedByFocus set on ComputedStyle and not | |
76 // childrenOrSiblingsAffectedByFocus. | |
77 TEST_F(AffectedByFocusTest, UAUniversalFocusRule) { | |
78 ElementResult expected[] = {{bodyTag, true, false}, | |
79 {divTag, true, false}, | |
80 {divTag, true, false}, | |
81 {divTag, true, false}, | |
82 {spanTag, true, false}}; | |
83 | |
84 SetHtmlInnerHTML( | |
85 "<body>" | |
86 "<div><div></div></div>" | |
87 "<div><span></span></div>" | |
88 "</body>"); | |
89 | |
90 CheckElements(expected, sizeof(expected) / sizeof(ElementResult)); | |
91 } | |
92 | |
93 // ":focus div" will mark ascendants of all divs with | |
94 // childrenOrSiblingsAffectedByFocus. | |
95 TEST_F(AffectedByFocusTest, FocusedAscendant) { | |
96 ElementResult expected[] = {{bodyTag, true, true}, | |
97 {divTag, true, true}, | |
98 {divTag, true, false}, | |
99 {divTag, true, false}, | |
100 {spanTag, true, false}}; | |
101 | |
102 SetHtmlInnerHTML( | |
103 "<head>" | |
104 "<style>:focus div { background-color: pink }</style>" | |
105 "</head>" | |
106 "<body>" | |
107 "<div><div></div></div>" | |
108 "<div><span></span></div>" | |
109 "</body>"); | |
110 | |
111 CheckElements(expected, sizeof(expected) / sizeof(ElementResult)); | |
112 } | |
113 | |
114 // "body:focus div" will mark the body element with | |
115 // childrenOrSiblingsAffectedByFocus. | |
116 TEST_F(AffectedByFocusTest, FocusedAscendantWithType) { | |
117 ElementResult expected[] = {{bodyTag, true, true}, | |
118 {divTag, true, false}, | |
119 {divTag, true, false}, | |
120 {divTag, true, false}, | |
121 {spanTag, true, false}}; | |
122 | |
123 SetHtmlInnerHTML( | |
124 "<head>" | |
125 "<style>body:focus div { background-color: pink }</style>" | |
126 "</head>" | |
127 "<body>" | |
128 "<div><div></div></div>" | |
129 "<div><span></span></div>" | |
130 "</body>"); | |
131 | |
132 CheckElements(expected, sizeof(expected) / sizeof(ElementResult)); | |
133 } | |
134 | |
135 // ":not(body):focus div" should not mark the body element with | |
136 // childrenOrSiblingsAffectedByFocus. | |
137 // Note that currently ":focus:not(body)" does not do the same. Then the :focus | |
138 // is checked and the childrenOrSiblingsAffectedByFocus flag set before the | |
139 // negated type selector is found. | |
140 TEST_F(AffectedByFocusTest, FocusedAscendantWithNegatedType) { | |
141 ElementResult expected[] = {{bodyTag, true, false}, | |
142 {divTag, true, true}, | |
143 {divTag, true, false}, | |
144 {divTag, true, false}, | |
145 {spanTag, true, false}}; | |
146 | |
147 SetHtmlInnerHTML( | |
148 "<head>" | |
149 "<style>:not(body):focus div { background-color: pink }</style>" | |
150 "</head>" | |
151 "<body>" | |
152 "<div><div></div></div>" | |
153 "<div><span></span></div>" | |
154 "</body>"); | |
155 | |
156 CheckElements(expected, sizeof(expected) / sizeof(ElementResult)); | |
157 } | |
158 | |
159 // Checking current behavior for ":focus + div", but this is a BUG or at best | |
160 // sub-optimal. The focused element will also in this case get | |
161 // childrenOrSiblingsAffectedByFocus even if it's really a sibling. Effectively, | |
162 // the whole sub-tree of the focused element will have styles recalculated even | |
163 // though none of the children are affected. There are other mechanisms that | |
164 // makes sure the sibling also gets its styles recalculated. | |
165 TEST_F(AffectedByFocusTest, FocusedSibling) { | |
166 ElementResult expected[] = {{bodyTag, true, false}, | |
167 {divTag, true, true}, | |
168 {spanTag, true, false}, | |
169 {divTag, true, false}}; | |
170 | |
171 SetHtmlInnerHTML( | |
172 "<head>" | |
173 "<style>:focus + div { background-color: pink }</style>" | |
174 "</head>" | |
175 "<body>" | |
176 "<div>" | |
177 " <span></span>" | |
178 "</div>" | |
179 "<div></div>" | |
180 "</body>"); | |
181 | |
182 CheckElements(expected, sizeof(expected) / sizeof(ElementResult)); | |
183 } | |
184 | |
185 TEST_F(AffectedByFocusTest, AffectedByFocusUpdate) { | |
186 // Check that when focussing the outer div in the document below, you only | |
187 // get a single element style recalc. | |
188 | |
189 SetHtmlInnerHTML( | |
190 "<style>:focus { border: 1px solid lime; }</style>" | |
191 "<div id=d tabIndex=1>" | |
192 "<div></div>" | |
193 "<div></div>" | |
194 "<div></div>" | |
195 "<div></div>" | |
196 "<div></div>" | |
197 "<div></div>" | |
198 "<div></div>" | |
199 "<div></div>" | |
200 "<div></div>" | |
201 "<div></div>" | |
202 "</div>"); | |
203 | |
204 GetDocument().View()->UpdateAllLifecyclePhases(); | |
205 | |
206 unsigned start_count = GetDocument().GetStyleEngine().StyleForElementCount(); | |
207 | |
208 GetDocument().getElementById("d")->focus(); | |
209 GetDocument().View()->UpdateAllLifecyclePhases(); | |
210 | |
211 unsigned element_count = | |
212 GetDocument().GetStyleEngine().StyleForElementCount() - start_count; | |
213 | |
214 ASSERT_EQ(1U, element_count); | |
215 } | |
216 | |
217 TEST_F(AffectedByFocusTest, ChildrenOrSiblingsAffectedByFocusUpdate) { | |
218 // Check that when focussing the outer div in the document below, you get a | |
219 // style recalc for the whole subtree. | |
220 | |
221 SetHtmlInnerHTML( | |
222 "<style>:focus div { border: 1px solid lime; }</style>" | |
223 "<div id=d tabIndex=1>" | |
224 "<div></div>" | |
225 "<div></div>" | |
226 "<div></div>" | |
227 "<div></div>" | |
228 "<div></div>" | |
229 "<div></div>" | |
230 "<div></div>" | |
231 "<div></div>" | |
232 "<div></div>" | |
233 "<div></div>" | |
234 "</div>"); | |
235 | |
236 GetDocument().View()->UpdateAllLifecyclePhases(); | |
237 | |
238 unsigned start_count = GetDocument().GetStyleEngine().StyleForElementCount(); | |
239 | |
240 GetDocument().getElementById("d")->focus(); | |
241 GetDocument().View()->UpdateAllLifecyclePhases(); | |
242 | |
243 unsigned element_count = | |
244 GetDocument().GetStyleEngine().StyleForElementCount() - start_count; | |
245 | |
246 ASSERT_EQ(11U, element_count); | |
247 } | |
248 | |
249 TEST_F(AffectedByFocusTest, InvalidationSetFocusUpdate) { | |
250 // Check that when focussing the outer div in the document below, you get a | |
251 // style recalc for the outer div and the class=a div only. | |
252 | |
253 SetHtmlInnerHTML( | |
254 "<style>:focus .a { border: 1px solid lime; }</style>" | |
255 "<div id=d tabIndex=1>" | |
256 "<div></div>" | |
257 "<div></div>" | |
258 "<div></div>" | |
259 "<div></div>" | |
260 "<div></div>" | |
261 "<div></div>" | |
262 "<div></div>" | |
263 "<div></div>" | |
264 "<div></div>" | |
265 "<div class='a'></div>" | |
266 "</div>"); | |
267 | |
268 GetDocument().View()->UpdateAllLifecyclePhases(); | |
269 | |
270 unsigned start_count = GetDocument().GetStyleEngine().StyleForElementCount(); | |
271 | |
272 GetDocument().getElementById("d")->focus(); | |
273 GetDocument().View()->UpdateAllLifecyclePhases(); | |
274 | |
275 unsigned element_count = | |
276 GetDocument().GetStyleEngine().StyleForElementCount() - start_count; | |
277 | |
278 ASSERT_EQ(2U, element_count); | |
279 } | |
280 | |
281 TEST_F(AffectedByFocusTest, NoInvalidationSetFocusUpdate) { | |
282 // Check that when focussing the outer div in the document below, you get a | |
283 // style recalc for the outer div only. The invalidation set for :focus will | |
284 // include 'a', but the id=d div should be affectedByFocus, not | |
285 // childrenOrSiblingsAffectedByFocus. | |
286 | |
287 SetHtmlInnerHTML( | |
288 "<style>#nomatch:focus .a { border: 1px solid lime; }</style>" | |
289 "<div id=d tabIndex=1>" | |
290 "<div></div>" | |
291 "<div></div>" | |
292 "<div></div>" | |
293 "<div></div>" | |
294 "<div></div>" | |
295 "<div></div>" | |
296 "<div></div>" | |
297 "<div></div>" | |
298 "<div></div>" | |
299 "<div class='a'></div>" | |
300 "</div>"); | |
301 | |
302 GetDocument().View()->UpdateAllLifecyclePhases(); | |
303 | |
304 unsigned start_count = GetDocument().GetStyleEngine().StyleForElementCount(); | |
305 | |
306 GetDocument().getElementById("d")->focus(); | |
307 GetDocument().View()->UpdateAllLifecyclePhases(); | |
308 | |
309 unsigned element_count = | |
310 GetDocument().GetStyleEngine().StyleForElementCount() - start_count; | |
311 | |
312 ASSERT_EQ(1U, element_count); | |
313 } | |
314 | |
315 TEST_F(AffectedByFocusTest, FocusWithinCommonAncestor) { | |
316 // Check that when changing the focus between 2 elements we don't need a style | |
317 // recalc for all the ancestors affected by ":focus-within". | |
318 | |
319 SetHtmlInnerHTML( | |
320 "<style>div:focus-within { background-color: lime; }</style>" | |
321 "<div>" | |
322 " <div>" | |
323 " <div id=focusme1 tabIndex=1></div>" | |
324 " <div id=focusme2 tabIndex=2></div>" | |
325 " <div>" | |
326 "</div>"); | |
327 | |
328 GetDocument().View()->UpdateAllLifecyclePhases(); | |
329 | |
330 unsigned start_count = GetDocument().GetStyleEngine().StyleForElementCount(); | |
331 | |
332 GetDocument().getElementById("focusme1")->focus(); | |
333 GetDocument().View()->UpdateAllLifecyclePhases(); | |
334 | |
335 unsigned element_count = | |
336 GetDocument().GetStyleEngine().StyleForElementCount() - start_count; | |
337 | |
338 EXPECT_EQ(3U, element_count); | |
339 | |
340 start_count += element_count; | |
341 | |
342 GetDocument().getElementById("focusme2")->focus(); | |
343 GetDocument().View()->UpdateAllLifecyclePhases(); | |
344 | |
345 element_count = | |
346 GetDocument().GetStyleEngine().StyleForElementCount() - start_count; | |
347 | |
348 // Only "focusme1" & "focusme2" elements need a recalc thanks to the common | |
349 // ancestor strategy. | |
350 EXPECT_EQ(2U, element_count); | |
351 } | |
352 | |
353 } // namespace blink | |
OLD | NEW |