Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: third_party/WebKit/Source/core/dom/shadow/SlotScopedTraversalTest.cpp

Issue 2432293002: Fix focus navigation for nested slot case (Closed)
Patch Set: Fix bugs, add unit tests Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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/dom/shadow/SlotScopedTraversal.h"
6
7 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
8 #include "core/dom/Document.h"
9 #include "core/dom/Element.h"
10 #include "core/dom/Node.h"
11 #include "core/dom/NodeTraversal.h"
12 #include "core/dom/shadow/ShadowRoot.h"
13 #include "core/html/HTMLElement.h"
14 #include "core/html/HTMLSlotElement.h"
15 #include "core/testing/DummyPageHolder.h"
16 #include "platform/geometry/IntSize.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #include <memory>
19
20 namespace blink {
21
22 class SlotScopedTraversalTest : public ::testing::Test {
23 protected:
24 Document& document() const;
25
26 void setupSampleHTML(const char* mainHTML, const char* shadowHTML, unsigned);
27 void attachOpenShadowRoot(Element& shadowHost, const char* shadowInnerHTML);
28 void testExpectedSequence(const Element* list[], int length);
29
30 private:
31 void SetUp() override;
32
33 Persistent<Document> m_document;
34 std::unique_ptr<DummyPageHolder> m_dummyPageHolder;
35 };
36
37 void SlotScopedTraversalTest::SetUp() {
38 m_dummyPageHolder = DummyPageHolder::create(IntSize(800, 600));
39 m_document = &m_dummyPageHolder->document();
40 DCHECK(m_document);
41 }
42
43 Document& SlotScopedTraversalTest::document() const {
44 return *m_document;
45 }
46
47 void SlotScopedTraversalTest::setupSampleHTML(const char* mainHTML,
48 const char* shadowHTML,
49 unsigned index) {
50 Element* body = document().body();
51 body->setInnerHTML(String::fromUTF8(mainHTML), ASSERT_NO_EXCEPTION);
52 if (shadowHTML) {
53 Element* shadowHost = toElement(NodeTraversal::childAt(*body, index));
54 attachOpenShadowRoot(*shadowHost, shadowHTML);
55 }
56 }
57
58 void SlotScopedTraversalTest::attachOpenShadowRoot(
59 Element& shadowHost,
60 const char* shadowInnerHTML) {
61 ShadowRoot* shadowRoot = shadowHost.createShadowRootInternal(
62 ShadowRootType::Open, ASSERT_NO_EXCEPTION);
63 shadowRoot->setInnerHTML(String::fromUTF8(shadowInnerHTML),
64 ASSERT_NO_EXCEPTION);
65 document().body()->updateDistribution();
66 }
67
68 TEST_F(SlotScopedTraversalTest, emptySlot) {
69 const char* mainHTML = "<div id='host'></div>";
70 const char* shadowHTML = "<slot></slot>";
71 setupSampleHTML(mainHTML, shadowHTML, 0);
72
73 Element* host = document().querySelector("#host");
74 ShadowRoot* shadowRoot = host->openShadowRoot();
75 Element* slotElement = shadowRoot->querySelector("slot");
76 DCHECK(isHTMLSlotElement(slotElement));
77 HTMLSlotElement* slot = toHTMLSlotElement(slotElement);
78
79 EXPECT_EQ(nullptr, SlotScopedTraversal::firstAssignedToSlot(*slot));
80 EXPECT_EQ(nullptr, SlotScopedTraversal::lastAssignedToSlot(*slot));
81 }
82
83 void SlotScopedTraversalTest::testExpectedSequence(const Element* list[],
84 int length) {
85 for (int i = 0; i < length; ++i) {
86 const Element* expected = i == length - 1 ? nullptr : list[i + 1];
87 EXPECT_EQ(expected, SlotScopedTraversal::next(*list[i]));
88 }
89
90 for (int i = length - 1; i >= 0; --i) {
91 const Element* expected = i == 0 ? nullptr : list[i - 1];
92 EXPECT_EQ(expected, SlotScopedTraversal::previous(*list[i]));
93 }
94 }
95
96 TEST_F(SlotScopedTraversalTest, simpleSlot) {
97 const char* mainHTML =
98 "<div id='host'>"
99 "<div id='inner1'></div>"
100 "<div id='inner2'></div>"
101 "</div>";
102
103 const char* shadowHTML = "<slot></slot>";
104
105 setupSampleHTML(mainHTML, shadowHTML, 0);
106
107 Element* host = document().querySelector("#host");
108 Element* inner1 = document().querySelector("#inner1");
109 Element* inner2 = document().querySelector("#inner2");
110 ShadowRoot* shadowRoot = host->openShadowRoot();
111 Element* slotElement = shadowRoot->querySelector("slot");
112 DCHECK(isHTMLSlotElement(slotElement));
113 HTMLSlotElement* slot = toHTMLSlotElement(slotElement);
114
115 EXPECT_EQ(inner1, SlotScopedTraversal::firstAssignedToSlot(*slot));
116 EXPECT_EQ(inner2, SlotScopedTraversal::lastAssignedToSlot(*slot));
117 EXPECT_FALSE(SlotScopedTraversal::isSlotScoped(*host));
118 EXPECT_FALSE(SlotScopedTraversal::isSlotScoped(*slot));
119 EXPECT_TRUE(SlotScopedTraversal::isSlotScoped(*inner1));
120 EXPECT_TRUE(SlotScopedTraversal::isSlotScoped(*inner2));
121 EXPECT_EQ(inner1, SlotScopedTraversal::nearestInclusiveAncestorAssignedToSlot(
122 *inner1));
123 EXPECT_EQ(inner2, SlotScopedTraversal::nearestInclusiveAncestorAssignedToSlot(
124 *inner2));
125 EXPECT_EQ(slot, SlotScopedTraversal::findScopeOwnerSlot(*inner1));
126 EXPECT_EQ(slot, SlotScopedTraversal::findScopeOwnerSlot(*inner2));
127
128 const int kLength = 2;
129 const Element* expectedSequence[kLength] = {inner1, inner2};
hayato 2016/10/31 05:27:13 Use vector, instead of an array.
kochi 2016/10/31 07:30:25 Done.
130 testExpectedSequence(expectedSequence, kLength);
131 }
132
133 TEST_F(SlotScopedTraversalTest, multipleSlots) {
134 const char* mainHTML =
135 "<div id='host'>"
136 "<div id='inner0' slot='slot0'></div>"
137 "<div id='inner1' slot='slot1'></div>"
138 "<div id='inner2'></div>"
139 "<div id='inner3' slot='slot1'></div>"
140 "<div id='inner4' slot='slot0'></div>"
141 "<div id='inner5'></div>"
142 "</div>";
143
144 const char* shadowHTML =
145 "<slot id='unnamedslot'></slot>"
146 "<slot id='slot0' name='slot0'></slot>"
147 "<slot id='slot1' name='slot1'></slot>";
148
149 setupSampleHTML(mainHTML, shadowHTML, 0);
150
151 Element* host = document().querySelector("#host");
152 Element* inner[6];
153 inner[0] = document().querySelector("#inner0");
154 inner[1] = document().querySelector("#inner1");
155 inner[2] = document().querySelector("#inner2");
156 inner[3] = document().querySelector("#inner3");
157 inner[4] = document().querySelector("#inner4");
158 inner[5] = document().querySelector("#inner5");
159
160 ShadowRoot* shadowRoot = host->openShadowRoot();
161 Element* slotElement[3];
162 slotElement[0] = shadowRoot->querySelector("#slot0");
163 slotElement[1] = shadowRoot->querySelector("#slot1");
164 slotElement[2] = shadowRoot->querySelector("#unnamedslot");
165
166 HTMLSlotElement* slot[3];
167 slot[0] = toHTMLSlotElement(slotElement[0]);
168 slot[1] = toHTMLSlotElement(slotElement[1]);
169 slot[2] = toHTMLSlotElement(slotElement[2]);
170
171 {
172 // <slot id='slot0'> : Expected assigned nodes: inner0, inner4
173 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot[0]));
174 EXPECT_EQ(inner[4], SlotScopedTraversal::lastAssignedToSlot(*slot[0]));
175 const int kLength = 2;
176 const Element* expectedSequence[kLength] = {inner[0], inner[4]};
177 testExpectedSequence(expectedSequence, kLength);
178 }
179
180 {
181 // <slot name='slot1'> : Expected assigned nodes: inner1, inner3
182 EXPECT_EQ(inner[1], SlotScopedTraversal::firstAssignedToSlot(*slot[1]));
183 EXPECT_EQ(inner[3], SlotScopedTraversal::lastAssignedToSlot(*slot[1]));
184 const int kLength = 2;
185 const Element* expectedSequence[kLength] = {inner[1], inner[3]};
186 testExpectedSequence(expectedSequence, kLength);
187 }
188
189 {
190 // <slot id='unnamedslot'> : Expected assigned nodes: inner2, inner5
191 EXPECT_EQ(inner[2], SlotScopedTraversal::firstAssignedToSlot(*slot[2]));
192 EXPECT_EQ(inner[5], SlotScopedTraversal::lastAssignedToSlot(*slot[2]));
193 const int kLength = 2;
194 const Element* expectedSequence[kLength] = {inner[2], inner[5]};
195 testExpectedSequence(expectedSequence, kLength);
196 }
197 }
198
199 TEST_F(SlotScopedTraversalTest, shadowHostAtTopLevel) {
200 // This covers a shadow host is directly assigned to a slot.
201 //
202 // We build the following tree:
203 // host
204 // |
205 // ShadowRoot
206 // |
207 // ____<slot>____
208 // | | |
209 // inner0 inner1 inner2
210 // | | |
211 // child0 child1 child2
212 //
213 // And iterate on inner0, inner1, and inner2 to attach shadow on each of
214 // them, and check if elements that are slotted to another slot are skipped
215 // in traversal.
216
217 const char* mainHTML =
218 "<div id='host'>"
219 "<div id='inner0'><div id='child0'></div></div>"
220 "<div id='inner1'><div id='child1'></div></div>"
221 "<div id='inner2'><div id='child2'></div></div>"
222 "</div>";
223
224 const char* shadowHTML = "<slot></slot>";
225
226 for (int i = 0; i < 3; ++i) {
227 setupSampleHTML(mainHTML, shadowHTML, 0);
228
229 Element* host = document().querySelector("#host");
230 Element* inner[3];
231 inner[0] = document().querySelector("#inner0");
232 inner[1] = document().querySelector("#inner1");
233 inner[2] = document().querySelector("#inner2");
234 Element* child[3];
235 child[0] = document().querySelector("#child0");
236 child[1] = document().querySelector("#child1");
237 child[2] = document().querySelector("#child2");
238
239 attachOpenShadowRoot(*inner[i], shadowHTML);
240
241 ShadowRoot* shadowRoot = host->openShadowRoot();
242 Element* slotElement = shadowRoot->querySelector("slot");
243 DCHECK(isHTMLSlotElement(slotElement));
244 HTMLSlotElement* slot = toHTMLSlotElement(slotElement);
245
246 switch (i) {
247 case 0: {
248 // inner0 is a shadow host.
249 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
250 EXPECT_EQ(child[2], SlotScopedTraversal::lastAssignedToSlot(*slot));
251
252 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*child[0]));
hayato 2016/10/31 05:27:13 Is this an intentional behavior? Is there any use
kochi 2016/10/31 07:30:25 child[0] is slotted to another slot, and it is the
hayato 2016/10/31 08:12:01 Ah, I see. You are using the same "shadowHTML" bot
253 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*child[0]));
254
255 const int kLength = 5;
256 const Element* expectedSequence[kLength] = {
257 inner[0], inner[1], child[1], inner[2], child[2]};
258 testExpectedSequence(expectedSequence, kLength);
259 } break;
260
261 case 1: {
262 // inner1 is a shadow host.
263 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
264 EXPECT_EQ(child[2], SlotScopedTraversal::lastAssignedToSlot(*slot));
265
266 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*child[1]));
267 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*child[1]));
268
269 const int kLength = 5;
270 const Element* expectedSequence[kLength] = {
271 inner[0], child[0], inner[1], inner[2], child[2]};
272 testExpectedSequence(expectedSequence, kLength);
273 } break;
274
275 case 2: {
276 // inner2 is a shadow host.
277 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
278 EXPECT_EQ(inner[2], SlotScopedTraversal::lastAssignedToSlot(*slot));
279
280 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*child[2]));
281 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*child[2]));
282
283 const int kLength = 5;
284 const Element* expectedSequence[kLength] = {
285 inner[0], child[0], inner[1], child[1], inner[2]};
286 testExpectedSequence(expectedSequence, kLength);
287 } break;
288 }
289 }
290 }
291
292 TEST_F(SlotScopedTraversalTest, shadowHostAtSecondLevel) {
293 // This covers cases where a shadow host exists in a child of a slotted
294 // element.
295 //
296 // We build the following tree:
297 // host
298 // |
299 // ShadowRoot
300 // |
301 // ____<slot>____
302 // | |
303 // _inner0_ _inner1_
304 // | | | |
305 // child0 child1 child2 child3
306 // | | | |
307 // span0 span1 span2 span3
308 //
309 // And iterate on child0, child1, child2, and child3 to attach shadow on each
310 // of them, and check if elements that are slotted to another slot are skipped
311 // in traversal.
312
313 const char* mainHTML =
314 "<div id='host'>"
315 "<div id='inner0'>"
316 "<div id='child0'><span id='span0'></span></div>"
317 "<div id='child1'><span id='span1'></span></div>"
318 "</div>"
319 "<div id='inner1'>"
320 "<div id='child2'><span id='span2'></span></div>"
321 "<div id='child3'><span id='span3'></span></div>"
322 "</div>"
323 "</div>";
324
325 const char* shadowHTML = "<slot></slot>";
326
327 for (int i = 0; i < 4; ++i) {
328 setupSampleHTML(mainHTML, shadowHTML, 0);
329
330 Element* host = document().querySelector("#host");
331 Element* inner[2];
332 inner[0] = document().querySelector("#inner0");
333 inner[1] = document().querySelector("#inner1");
334 Element* child[4];
335 child[0] = document().querySelector("#child0");
336 child[1] = document().querySelector("#child1");
337 child[2] = document().querySelector("#child2");
338 child[3] = document().querySelector("#child3");
339 Element* span[4];
340 span[0] = document().querySelector("#span0");
341 span[1] = document().querySelector("#span1");
342 span[2] = document().querySelector("#span2");
343 span[3] = document().querySelector("#span3");
344
345 attachOpenShadowRoot(*child[i], shadowHTML);
346
347 for (int j = 0; j < 4; ++j) {
348 DCHECK(child[i]);
349 DCHECK(span[i]);
350 }
351
352 ShadowRoot* shadowRoot = host->openShadowRoot();
353 Element* slotElement = shadowRoot->querySelector("slot");
354 DCHECK(isHTMLSlotElement(slotElement));
355 HTMLSlotElement* slot = toHTMLSlotElement(slotElement);
356
357 switch (i) {
358 case 0: {
359 // child0 is a shadow host.
360 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
361 EXPECT_EQ(span[3], SlotScopedTraversal::lastAssignedToSlot(*slot));
362
363 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*span[0]));
364 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*span[0]));
365
366 const int kLength = 9;
367 const Element* expectedSequence[kLength] = {
368 inner[0], child[0], child[1], span[1], inner[1],
369 child[2], span[2], child[3], span[3]};
370 testExpectedSequence(expectedSequence, kLength);
371 } break;
372
373 case 1: {
374 // child1 is a shadow host.
375 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
376 EXPECT_EQ(span[3], SlotScopedTraversal::lastAssignedToSlot(*slot));
377
378 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*span[1]));
379 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*span[1]));
380
381 const int kLength = 9;
382 const Element* expectedSequence[kLength] = {
383 inner[0], child[0], span[0], child[1], inner[1],
384 child[2], span[2], child[3], span[3]};
385 testExpectedSequence(expectedSequence, kLength);
386 } break;
387
388 case 2: {
389 // child2 is a shadow host.
390 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
391 EXPECT_EQ(span[3], SlotScopedTraversal::lastAssignedToSlot(*slot));
392
393 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*span[2]));
394 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*span[2]));
395
396 const int kLength = 9;
397 const Element* expectedSequence[kLength] = {
398 inner[0], child[0], span[0], child[1], span[1],
399 inner[1], child[2], child[3], span[3]};
400 testExpectedSequence(expectedSequence, kLength);
401 } break;
402
403 case 3: {
404 // child3 is a shadow host.
405 EXPECT_EQ(inner[0], SlotScopedTraversal::firstAssignedToSlot(*slot));
406 EXPECT_EQ(child[3], SlotScopedTraversal::lastAssignedToSlot(*slot));
407
408 EXPECT_EQ(nullptr, SlotScopedTraversal::next(*span[3]));
409 EXPECT_EQ(nullptr, SlotScopedTraversal::previous(*span[3]));
410
411 const int kLength = 9;
412 const Element* expectedSequence[kLength] = {
413 inner[0], child[0], span[0], child[1], span[1],
414 inner[1], child[2], span[2], child[3]};
415 testExpectedSequence(expectedSequence, kLength);
416 } break;
417 }
418 }
419 }
420
421 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698