Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / css / AffectedByFocusTest.cpp
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/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>
16
17 using namespace blink;
18 using namespace HTMLNames;
19
20 namespace {
21
22 class AffectedByFocusTest : public ::testing::Test {
23
24 protected:
25
26     struct ElementResult {
27         const blink::HTMLQualifiedName tag;
28         bool affectedBy;
29         bool childrenOrSiblingsAffectedBy;
30     };
31
32     virtual void SetUp() override;
33
34     HTMLDocument& document() const { return *m_document; }
35
36     void setHtmlInnerHTML(const char* htmlContent);
37
38     void checkElements(ElementResult expected[], unsigned expectedCount) const;
39
40 private:
41     OwnPtr<DummyPageHolder> m_dummyPageHolder;
42
43     HTMLDocument* m_document;
44 };
45
46 void AffectedByFocusTest::SetUp()
47 {
48     m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
49     m_document = toHTMLDocument(&m_dummyPageHolder->document());
50     ASSERT(m_document);
51 }
52
53 void AffectedByFocusTest::setHtmlInnerHTML(const char* htmlContent)
54 {
55     document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent), ASSERT_NO_EXCEPTION);
56     document().view()->updateLayoutAndStyleIfNeededRecursive();
57 }
58
59 void AffectedByFocusTest::checkElements(ElementResult expected[], unsigned expectedCount) const
60 {
61     unsigned i = 0;
62     HTMLElement* element = document().body();
63
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());
69     }
70
71     ASSERT(!element && i == expectedCount);
72 }
73
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)
78 {
79     ElementResult expected[] = {
80         { bodyTag, true, false },
81         { divTag, true, false },
82         { divTag, true, false },
83         { divTag, true, false },
84         { spanTag, true, false }
85     };
86
87     setHtmlInnerHTML("<body>"
88         "<div><div></div></div>"
89         "<div><span></span></div>"
90         "</body>");
91
92     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
93 }
94
95 // ":focus div" will mark ascendants of all divs with childrenOrSiblingsAffectedByFocus.
96 TEST_F(AffectedByFocusTest, FocusedAscendant)
97 {
98     ElementResult expected[] = {
99         { bodyTag, true, true },
100         { divTag, true, true },
101         { divTag, true, false },
102         { divTag, true, false },
103         { spanTag, true, false }
104     };
105
106     setHtmlInnerHTML("<head>"
107         "<style>:focus div { background-color: pink }</style>"
108         "</head>"
109         "<body>"
110         "<div><div></div></div>"
111         "<div><span></span></div>"
112         "</body>");
113
114     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
115 }
116
117 // "body:focus div" will mark the body element with childrenOrSiblingsAffectedByFocus.
118 TEST_F(AffectedByFocusTest, FocusedAscendantWithType)
119 {
120     ElementResult expected[] = {
121         { bodyTag, true, true },
122         { divTag, true, false },
123         { divTag, true, false },
124         { divTag, true, false },
125         { spanTag, true, false }
126     };
127
128     setHtmlInnerHTML("<head>"
129         "<style>body:focus div { background-color: pink }</style>"
130         "</head>"
131         "<body>"
132         "<div><div></div></div>"
133         "<div><span></span></div>"
134         "</body>");
135
136     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
137 }
138
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
142 // is found.
143 TEST_F(AffectedByFocusTest, FocusedAscendantWithNegatedType)
144 {
145     ElementResult expected[] = {
146         { bodyTag, true, false },
147         { divTag, true, true },
148         { divTag, true, false },
149         { divTag, true, false },
150         { spanTag, true, false }
151     };
152
153     setHtmlInnerHTML("<head>"
154         "<style>:not(body):focus div { background-color: pink }</style>"
155         "</head>"
156         "<body>"
157         "<div><div></div></div>"
158         "<div><span></span></div>"
159         "</body>");
160
161     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
162 }
163
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)
171 {
172     ElementResult expected[] = {
173         { bodyTag, true, false },
174         { divTag, true, true },
175         { spanTag, true, false },
176         { divTag, true, false }
177     };
178
179     setHtmlInnerHTML("<head>"
180         "<style>:focus + div { background-color: pink }</style>"
181         "</head>"
182         "<body>"
183         "<div>"
184         "  <span></span>"
185         "</div>"
186         "<div></div>"
187         "</body>");
188
189     checkElements(expected, sizeof(expected) / sizeof(ElementResult));
190 }
191
192 TEST_F(AffectedByFocusTest, AffectedByFocusUpdate)
193 {
194     // Check that when focussing the outer div in the document below, you only
195     // get a single element style recalc.
196
197     setHtmlInnerHTML("<style>:focus { border: 1px solid lime; }</style>"
198         "<div id=d tabIndex=1>"
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></div>"
209         "</div>");
210
211     document().view()->updateLayoutAndStyleIfNeededRecursive();
212
213     unsigned startCount = document().styleEngine()->resolverAccessCount();
214
215     document().getElementById("d")->focus();
216     document().view()->updateLayoutAndStyleIfNeededRecursive();
217
218     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
219
220     ASSERT_EQ(1U, accessCount);
221 }
222
223 TEST_F(AffectedByFocusTest, ChildrenOrSiblingsAffectedByFocusUpdate)
224 {
225     // Check that when focussing the outer div in the document below, you get a
226     // style recalc for the whole subtree.
227
228     setHtmlInnerHTML("<style>:focus div { border: 1px solid lime; }</style>"
229         "<div id=d tabIndex=1>"
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></div>"
240         "</div>");
241
242     document().view()->updateLayoutAndStyleIfNeededRecursive();
243
244     unsigned startCount = document().styleEngine()->resolverAccessCount();
245
246     document().getElementById("d")->focus();
247     document().view()->updateLayoutAndStyleIfNeededRecursive();
248
249     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
250
251     ASSERT_EQ(11U, accessCount);
252 }
253
254 TEST_F(AffectedByFocusTest, InvalidationSetFocusUpdate)
255 {
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.
258
259     setHtmlInnerHTML("<style>:focus .a { border: 1px solid lime; }</style>"
260         "<div id=d tabIndex=1>"
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></div>"
270         "<div class='a'></div>"
271         "</div>");
272
273     document().view()->updateLayoutAndStyleIfNeededRecursive();
274
275     unsigned startCount = document().styleEngine()->resolverAccessCount();
276
277     document().getElementById("d")->focus();
278     document().view()->updateLayoutAndStyleIfNeededRecursive();
279
280     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
281
282     ASSERT_EQ(2U, accessCount);
283 }
284
285 TEST_F(AffectedByFocusTest, NoInvalidationSetFocusUpdate)
286 {
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.
290
291     setHtmlInnerHTML("<style>#nomatch:focus .a { border: 1px solid lime; }</style>"
292         "<div id=d tabIndex=1>"
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></div>"
302         "<div class='a'></div>"
303         "</div>");
304
305     document().view()->updateLayoutAndStyleIfNeededRecursive();
306
307     unsigned startCount = document().styleEngine()->resolverAccessCount();
308
309     document().getElementById("d")->focus();
310     document().view()->updateLayoutAndStyleIfNeededRecursive();
311
312     unsigned accessCount = document().styleEngine()->resolverAccessCount() - startCount;
313
314     ASSERT_EQ(1U, accessCount);
315 }
316
317 } // namespace