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.
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/dom/StyleEngine.h"
11 #include "core/frame/FrameView.h"
12 #include "core/html/HTMLDocument.h"
13 #include "core/html/HTMLElement.h"
14 #include "core/testing/DummyPageHolder.h"
15 #include <gtest/gtest.h>
17 using namespace blink;
18 using namespace HTMLNames;
22 class AffectedByFocusTest : public ::testing::Test {
26 struct ElementResult {
27 const blink::HTMLQualifiedName tag;
29 bool childrenOrSiblingsAffectedBy;
32 virtual void SetUp() override;
34 HTMLDocument& document() const { return *m_document; }
36 void setHtmlInnerHTML(const char* htmlContent);
38 void checkElements(ElementResult expected[], unsigned expectedCount) const;
41 OwnPtr<DummyPageHolder> m_dummyPageHolder;
43 HTMLDocument* m_document;
46 void AffectedByFocusTest::SetUp()
48 m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
49 m_document = toHTMLDocument(&m_dummyPageHolder->document());
53 void AffectedByFocusTest::setHtmlInnerHTML(const char* htmlContent)
55 document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent), ASSERT_NO_EXCEPTION);
56 document().view()->updateLayoutAndStyleIfNeededRecursive();
59 void AffectedByFocusTest::checkElements(ElementResult expected[], unsigned expectedCount) const
62 HTMLElement* element = document().body();
64 for (; element && i < expectedCount; element = Traversal<HTMLElement>::next(*element), ++i) {
65 ASSERT_TRUE(element->hasTagName(expected[i].tag));
66 ASSERT(element->renderStyle());
67 ASSERT_EQ(expected[i].affectedBy, element->renderStyle()->affectedByFocus());
68 ASSERT_EQ(expected[i].childrenOrSiblingsAffectedBy, element->childrenOrSiblingsAffectedByFocus());
71 ASSERT(!element && i == expectedCount);
74 // A global :focus rule in html.css currently causes every single element to be
75 // affectedByFocus. Check that all elements in a document with no :focus rules
76 // gets the affectedByFocus set on RenderStyle and not childrenOrSiblingsAffectedByFocus.
77 TEST_F(AffectedByFocusTest, UAUniversalFocusRule)
79 ElementResult expected[] = {
80 { bodyTag, true, false },
81 { divTag, true, false },
82 { divTag, true, false },
83 { divTag, true, false },
84 { spanTag, true, false }
87 setHtmlInnerHTML("<body>"
88 "<div><div></div></div>"
89 "<div><span></span></div>"
92 checkElements(expected, sizeof(expected) / sizeof(ElementResult));
95 // ":focus div" will mark ascendants of all divs with childrenOrSiblingsAffectedByFocus.
96 TEST_F(AffectedByFocusTest, FocusedAscendant)
98 ElementResult expected[] = {
99 { bodyTag, true, true },
100 { divTag, true, true },
101 { divTag, true, false },
102 { divTag, true, false },
103 { spanTag, true, false }
106 setHtmlInnerHTML("<head>"
107 "<style>:focus div { background-color: pink }</style>"
110 "<div><div></div></div>"
111 "<div><span></span></div>"
114 checkElements(expected, sizeof(expected) / sizeof(ElementResult));
117 // "body:focus div" will mark the body element with childrenOrSiblingsAffectedByFocus.
118 TEST_F(AffectedByFocusTest, FocusedAscendantWithType)
120 ElementResult expected[] = {
121 { bodyTag, true, true },
122 { divTag, true, false },
123 { divTag, true, false },
124 { divTag, true, false },
125 { spanTag, true, false }
128 setHtmlInnerHTML("<head>"
129 "<style>body:focus div { background-color: pink }</style>"
132 "<div><div></div></div>"
133 "<div><span></span></div>"
136 checkElements(expected, sizeof(expected) / sizeof(ElementResult));
139 // ":not(body):focus div" should not mark the body element with childrenOrSiblingsAffectedByFocus.
140 // Note that currently ":focus:not(body)" does not do the same. Then the :focus is
141 // checked and the childrenOrSiblingsAffectedByFocus flag set before the negated type selector
143 TEST_F(AffectedByFocusTest, FocusedAscendantWithNegatedType)
145 ElementResult expected[] = {
146 { bodyTag, true, false },
147 { divTag, true, true },
148 { divTag, true, false },
149 { divTag, true, false },
150 { spanTag, true, false }
153 setHtmlInnerHTML("<head>"
154 "<style>:not(body):focus div { background-color: pink }</style>"
157 "<div><div></div></div>"
158 "<div><span></span></div>"
161 checkElements(expected, sizeof(expected) / sizeof(ElementResult));
164 // Checking current behavior for ":focus + div", but this is a BUG or at best
165 // sub-optimal. The focused element will also in this case get childrenOrSiblingsAffectedByFocus
166 // even if it's really a sibling. Effectively, the whole sub-tree of the focused
167 // element will have styles recalculated even though none of the children are
168 // affected. There are other mechanisms that makes sure the sibling also gets its
169 // styles recalculated.
170 TEST_F(AffectedByFocusTest, FocusedSibling)
172 ElementResult expected[] = {
173 { bodyTag, true, false },
174 { divTag, true, true },
175 { spanTag, true, false },
176 { divTag, true, false }
179 setHtmlInnerHTML("<head>"
180 "<style>:focus + div { background-color: pink }</style>"
189 checkElements(expected, sizeof(expected) / sizeof(ElementResult));
192 TEST_F(AffectedByFocusTest, AffectedByFocusUpdate)
194 // Check that when focussing the outer div in the document below, you only
195 // get a single element style recalc.
197 setHtmlInnerHTML("<style>:focus { border: 1px solid lime; }</style>"
198 "<div id=d tabIndex=1>"
211 document().view()->updateLayoutAndStyleIfNeededRecursive();
213 unsigned startCount = document().styleEngine()->resolverAccessCount();
215 document().getElementById("d")->focus();
216 document().view()->updateLayoutAndStyleIfNeededRecursive();
218 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
220 ASSERT_EQ(1U, accessCount);
223 TEST_F(AffectedByFocusTest, ChildrenOrSiblingsAffectedByFocusUpdate)
225 // Check that when focussing the outer div in the document below, you get a
226 // style recalc for the whole subtree.
228 setHtmlInnerHTML("<style>:focus div { border: 1px solid lime; }</style>"
229 "<div id=d tabIndex=1>"
242 document().view()->updateLayoutAndStyleIfNeededRecursive();
244 unsigned startCount = document().styleEngine()->resolverAccessCount();
246 document().getElementById("d")->focus();
247 document().view()->updateLayoutAndStyleIfNeededRecursive();
249 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
251 ASSERT_EQ(11U, accessCount);
254 TEST_F(AffectedByFocusTest, InvalidationSetFocusUpdate)
256 // Check that when focussing the outer div in the document below, you get a
257 // style recalc for the outer div and the class=a div only.
259 setHtmlInnerHTML("<style>:focus .a { border: 1px solid lime; }</style>"
260 "<div id=d tabIndex=1>"
270 "<div class='a'></div>"
273 document().view()->updateLayoutAndStyleIfNeededRecursive();
275 unsigned startCount = document().styleEngine()->resolverAccessCount();
277 document().getElementById("d")->focus();
278 document().view()->updateLayoutAndStyleIfNeededRecursive();
280 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
282 ASSERT_EQ(2U, accessCount);
285 TEST_F(AffectedByFocusTest, NoInvalidationSetFocusUpdate)
287 // Check that when focussing the outer div in the document below, you get a
288 // style recalc for the outer div only. The invalidation set for :focus will
289 // include 'a', but the id=d div should be affectedByFocus, not childrenOrSiblingsAffectedByFocus.
291 setHtmlInnerHTML("<style>#nomatch:focus .a { border: 1px solid lime; }</style>"
292 "<div id=d tabIndex=1>"
302 "<div class='a'></div>"
305 document().view()->updateLayoutAndStyleIfNeededRecursive();
307 unsigned startCount = document().styleEngine()->resolverAccessCount();
309 document().getElementById("d")->focus();
310 document().view()->updateLayoutAndStyleIfNeededRecursive();
312 unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
314 ASSERT_EQ(1U, accessCount);