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 "config.h" 6 #include "core/HTMLNames.h" 7 #include "core/dom/Element.h" 8 #include "core/dom/ElementTraversal.h" 9 #include "core/dom/NodeRenderStyle.h" 10 #include "core/frame/FrameView.h" 11 #include "core/html/HTMLDocument.h" 12 #include "core/html/HTMLElement.h" 13 #include "core/testing/DummyPageHolder.h" 14 #include <gtest/gtest.h> 15 16 using namespace WebCore; 17 using namespace HTMLNames; 18 19 namespace { 20 21 class AffectedByFocusTest : public ::testing::Test { 22 23 protected: 24 25 struct ElementResult { 26 const WebCore::QualifiedName tag; 27 bool affectedBy; 28 bool childrenOrSiblingsAffectedBy; 29 }; 30 31 virtual void SetUp() OVERRIDE; 32 33 HTMLDocument& document() const { return *m_document; } 34 35 void setHtmlInnerHTML(const char* htmlContent); 36 37 void checkElements(ElementResult expected[], unsigned expectedCount) const; 38 39 private: 40 OwnPtr<DummyPageHolder> m_dummyPageHolder; 41 42 HTMLDocument* m_document; 43 }; 44 45 void AffectedByFocusTest::SetUp() 46 { 47 m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600)); 48 m_document = toHTMLDocument(&m_dummyPageHolder->document()); 49 ASSERT(m_document); 50 } 51 52 void AffectedByFocusTest::setHtmlInnerHTML(const char* htmlContent) 53 { 54 document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent), ASSERT_NO_EXCEPTION); 55 document().view()->updateLayoutAndStyleIfNeededRecursive(); 56 } 57 58 void AffectedByFocusTest::checkElements(ElementResult expected[], unsigned expectedCount) const 59 { 60 unsigned i = 0; 61 Element* elm = document().body(); 62 63 for (; elm && i < expectedCount; elm = ElementTraversal::next(*elm), ++i) { 64 ASSERT_TRUE(elm->hasTagName(expected[i].tag)); 65 ASSERT(elm->renderStyle()); 66 ASSERT_EQ(expected[i].affectedBy, elm->renderStyle()->affectedByFocus()); 67 ASSERT_EQ(expected[i].childrenOrSiblingsAffectedBy, elm->childrenOrSiblingsAffectedByFocus()); 68 } 69 70 ASSERT(!elm && i == expectedCount); 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 RenderStyle and not childrenOrSiblingsAffectedByFocus. 76 TEST_F(AffectedByFocusTest, UAUniversalFocusRule) 77 { 78 ElementResult expected[] = { 79 { bodyTag, true, false }, 80 { divTag, true, false }, 81 { divTag, true, false }, 82 { divTag, true, false }, 83 { spanTag, true, false } 84 }; 85 86 setHtmlInnerHTML("<body>" 87 "<div><div></div></div>" 88 "<div><span></span></div>" 89 "</body>"); 90 91 checkElements(expected, sizeof(expected) / sizeof(ElementResult)); 92 } 93 94 // ":focus div" will mark ascendants of all divs with childrenOrSiblingsAffectedByFocus. 95 TEST_F(AffectedByFocusTest, FocusedAscendant) 96 { 97 ElementResult expected[] = { 98 { bodyTag, true, true }, 99 { divTag, true, true }, 100 { divTag, true, false }, 101 { divTag, true, false }, 102 { spanTag, true, false } 103 }; 104 105 setHtmlInnerHTML("<head>" 106 "<style>:focus div { background-color: pink }</style>" 107 "</head>" 108 "<body>" 109 "<div><div></div></div>" 110 "<div><span></span></div>" 111 "</body>"); 112 113 checkElements(expected, sizeof(expected) / sizeof(ElementResult)); 114 } 115 116 // "body:focus div" will mark the body element with childrenOrSiblingsAffectedByFocus. 117 TEST_F(AffectedByFocusTest, FocusedAscendantWithType) 118 { 119 ElementResult expected[] = { 120 { bodyTag, true, true }, 121 { divTag, true, false }, 122 { divTag, true, false }, 123 { divTag, true, false }, 124 { spanTag, true, false } 125 }; 126 127 setHtmlInnerHTML("<head>" 128 "<style>body:focus div { background-color: pink }</style>" 129 "</head>" 130 "<body>" 131 "<div><div></div></div>" 132 "<div><span></span></div>" 133 "</body>"); 134 135 checkElements(expected, sizeof(expected) / sizeof(ElementResult)); 136 } 137 138 // ":not(body):focus div" should not mark the body element with childrenOrSiblingsAffectedByFocus. 139 // Note that currently ":focus:not(body)" does not do the same. Then the :focus is 140 // checked and the childrenOrSiblingsAffectedByFocus flag set before the negated type selector 141 // is found. 142 TEST_F(AffectedByFocusTest, FocusedAscendantWithNegatedType) 143 { 144 ElementResult expected[] = { 145 { bodyTag, true, false }, 146 { divTag, true, true }, 147 { divTag, true, false }, 148 { divTag, true, false }, 149 { spanTag, true, false } 150 }; 151 152 setHtmlInnerHTML("<head>" 153 "<style>:not(body):focus div { background-color: pink }</style>" 154 "</head>" 155 "<body>" 156 "<div><div></div></div>" 157 "<div><span></span></div>" 158 "</body>"); 159 160 checkElements(expected, sizeof(expected) / sizeof(ElementResult)); 161 } 162 163 // Checking current behavior for ":focus + div", but this is a BUG or at best 164 // sub-optimal. The focused element will also in this case get childrenOrSiblingsAffectedByFocus 165 // even if it's really a sibling. Effectively, the whole sub-tree of the focused 166 // element will have styles recalculated even though none of the children are 167 // affected. There are other mechanisms that makes sure the sibling also gets its 168 // styles recalculated. 169 TEST_F(AffectedByFocusTest, FocusedSibling) 170 { 171 ElementResult expected[] = { 172 { bodyTag, true, false }, 173 { divTag, true, true }, 174 { spanTag, true, false }, 175 { divTag, true, false } 176 }; 177 178 setHtmlInnerHTML("<head>" 179 "<style>:focus + div { background-color: pink }</style>" 180 "</head>" 181 "<body>" 182 "<div>" 183 " <span></span>" 184 "</div>" 185 "<div></div>" 186 "</body>"); 187 188 checkElements(expected, sizeof(expected) / sizeof(ElementResult)); 189 } 190 191 TEST_F(AffectedByFocusTest, AffectedByFocusUpdate) 192 { 193 // Check that when focussing the outer div in the document below, you only 194 // get a single element style recalc. 195 196 setHtmlInnerHTML("<style>:focus { border: 1px solid lime; }</style>" 197 "<div id=d tabIndex=1>" 198 "<div></div>" 199 "<div></div>" 200 "<div></div>" 201 "<div></div>" 202 "<div></div>" 203 "<div></div>" 204 "<div></div>" 205 "<div></div>" 206 "<div></div>" 207 "<div></div>" 208 "</div>"); 209 210 document().view()->updateLayoutAndStyleIfNeededRecursive(); 211 212 unsigned startCount = document().styleEngine()->resolverAccessCount(); 213 214 document().getElementById("d")->focus(); 215 document().view()->updateLayoutAndStyleIfNeededRecursive(); 216 217 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; 218 219 ASSERT_EQ(1U, accessCount); 220 } 221 222 TEST_F(AffectedByFocusTest, ChildrenOrSiblingsAffectedByFocusUpdate) 223 { 224 // Check that when focussing the outer div in the document below, you get a 225 // style recalc for the whole subtree. 226 227 setHtmlInnerHTML("<style>:focus div { border: 1px solid lime; }</style>" 228 "<div id=d tabIndex=1>" 229 "<div></div>" 230 "<div></div>" 231 "<div></div>" 232 "<div></div>" 233 "<div></div>" 234 "<div></div>" 235 "<div></div>" 236 "<div></div>" 237 "<div></div>" 238 "<div></div>" 239 "</div>"); 240 241 document().view()->updateLayoutAndStyleIfNeededRecursive(); 242 243 unsigned startCount = document().styleEngine()->resolverAccessCount(); 244 245 document().getElementById("d")->focus(); 246 document().view()->updateLayoutAndStyleIfNeededRecursive(); 247 248 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; 249 250 ASSERT_EQ(11U, accessCount); 251 } 252 253 TEST_F(AffectedByFocusTest, InvalidationSetFocusUpdate) 254 { 255 // Check that when focussing the outer div in the document below, you get a 256 // style recalc for the outer div and the class=a div only. 257 258 setHtmlInnerHTML("<style>:focus .a { border: 1px solid lime; }</style>" 259 "<div id=d tabIndex=1>" 260 "<div></div>" 261 "<div></div>" 262 "<div></div>" 263 "<div></div>" 264 "<div></div>" 265 "<div></div>" 266 "<div></div>" 267 "<div></div>" 268 "<div></div>" 269 "<div class='a'></div>" 270 "</div>"); 271 272 document().view()->updateLayoutAndStyleIfNeededRecursive(); 273 274 unsigned startCount = document().styleEngine()->resolverAccessCount(); 275 276 document().getElementById("d")->focus(); 277 document().view()->updateLayoutAndStyleIfNeededRecursive(); 278 279 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; 280 281 ASSERT_EQ(2U, accessCount); 282 } 283 284 TEST_F(AffectedByFocusTest, NoInvalidationSetFocusUpdate) 285 { 286 // Check that when focussing the outer div in the document below, you get a 287 // style recalc for the outer div only. The invalidation set for :focus will 288 // include 'a', but the id=d div should be affectedByFocus, not childrenOrSiblingsAffectedByFocus. 289 290 setHtmlInnerHTML("<style>#nomatch:focus .a { border: 1px solid lime; }</style>" 291 "<div id=d tabIndex=1>" 292 "<div></div>" 293 "<div></div>" 294 "<div></div>" 295 "<div></div>" 296 "<div></div>" 297 "<div></div>" 298 "<div></div>" 299 "<div></div>" 300 "<div></div>" 301 "<div class='a'></div>" 302 "</div>"); 303 304 document().view()->updateLayoutAndStyleIfNeededRecursive(); 305 306 unsigned startCount = document().styleEngine()->resolverAccessCount(); 307 308 document().getElementById("d")->focus(); 309 document().view()->updateLayoutAndStyleIfNeededRecursive(); 310 311 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount; 312 313 ASSERT_EQ(1U, accessCount); 314 } 315 316 } // namespace 317