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