| 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 |