Use testRunner instead of layoutTestController in fast/doctypes, dom, dynamic, encodi...
[WebKit-https.git] / LayoutTests / fast / dom / shadow / shadow-boundary-events.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <script src="../../js/resources/js-test-pre.js"></script>
5 <script src="resources/shadow-dom.js"></script>
6 </head>
7 <body>
8 <p id="description"></p>
9 <div id="sandbox"></div>
10 <pre id="console"></pre>
11 <script>
12 description("Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.");
13
14 function moveMouseOver(element)
15 {
16     if (!window.eventSender || !window.internals)
17         return;
18
19     var defaultPaddingSize = 20;
20     var x = element.offsetLeft + element.offsetWidth / 2;
21     var y;
22     if (element.hasChildNodes() || window.internals.shadowRoot(element))
23         y = element.offsetTop + defaultPaddingSize;
24     else
25         y = element.offsetTop + element.offsetHeight / 2;
26     eventSender.mouseMoveTo(x, y);
27 }
28
29 var eventRecords = {};
30
31 function clearEventRecords()
32 {
33     eventRecords = {};
34 }
35
36 function dispatchedEvent(eventType)
37 {
38     var events = eventRecords[eventType];
39     if (!events)
40         return [];
41     return events;
42 }
43
44 function recordEvent(event)
45 {
46     var eventType = event.type
47     if (!eventRecords[eventType]) {
48         eventRecords[eventType] = []
49     }
50     // Records each event in the following format per event type:
51     //   eventRecords[eventType] = ['target.id(<-relatedTarget.id)(@currentTarget.id)',,,]
52     //   * RelatedTarget and currentTarget may be omitted if they are not defined.
53     // A new event is pushed back to the array of its event type.
54     var eventString = '';
55     eventString += event.target.id;
56     if (event.relatedTarget)
57         eventString += '(<-' + event.relatedTarget.id + ')';
58     if (event.currentTarget)
59         eventString += '(@' + event.currentTarget.id + ')';
60     if (event.eventPhase == 1)
61         eventString += '(capturing phase)';
62     if (event.target && event.currentTarget && event.target.id == event.currentTarget.id)
63         shouldBe("event.eventPhase", "2", true);
64     eventRecords[eventType].push(eventString);
65 }
66
67 function prepareDOMTree(parent)
68 {
69     parent.appendChild(
70         createDOM('div', {'id': 'divA', 'style': 'padding-top: 40px'},
71                   createDOM('div', {'id': 'divB', 'style': 'width: 40px; height: 40px', 'tabindex': 0}),
72                   createDOM('div', {'id': 'divC', 'style': 'width: 40px; height: 40px', 'tabindex': 0}),
73                   createDOM('div', {'id': 'shadowD', 'style': 'padding-top: 40px'},
74                             createShadowRoot(
75                                 createDOM('div', {'id': 'divE', 'style': 'padding-top: 40px'},
76                                           createDOM('div', {'id': 'shadowF', 'style': 'padding-top: 40px'},
77                                                     createShadowRoot(
78                                                         createDOM('div', {'id': 'shadowG', 'style': 'padding-top: 40px'},
79                                                                   createShadowRoot(
80                                                                       createDOM('div', {'id': 'divH', 'style': 'width: 40px; height: 40px', 'tabindex': 0}),
81                                                                       createDOM('div', {'id': 'divI', 'style': 'width: 40px; height: 40px', 'tabindex': 0})))))),
82                                 createDOM('div', {'id': 'divJ', 'style': 'padding-top: 40px'},
83                                           createDOM('div', {'id': 'shadowK', 'style': 'padding-top: 40px'},
84                                                     createShadowRoot(
85                                                         createDOM('div', {'id': 'divL', 'style': 'width: 40px; height: 40px', 'tabindex': 0}))))))));
86
87     var ids = ['divA', 'divB', 'divC',
88                'shadowD', 'shadowD/divE', 'shadowD/shadowF', 'shadowD/shadowF/shadowG',
89                'shadowD/shadowF/shadowG/divH', 'shadowD/shadowF/shadowG/divI',
90                'shadowD/divJ', 'shadowD/shadowK', 'shadowD/shadowK/divL'];
91     for (var i = 0; i < ids.length; ++i) {
92         var element = getNodeInShadowTreeStack(ids[i]);
93         element.addEventListener('mouseover', recordEvent, false);
94         element.addEventListener('mouseout', recordEvent, false);
95         element.addEventListener('focusin', recordEvent, false);
96         element.addEventListener('focusout', recordEvent, false);
97         element.addEventListener('focus', recordEvent, true);  // capturing phase
98         element.addEventListener('blur', recordEvent, true);  // capturing phase
99     }
100 }
101
102 function moveMouse(oldElementId, newElementId, message)
103 {
104     debug('\n' + message + '\n' + 'Moving mouse from ' + oldElementId + ' to ' + newElementId);
105     moveMouseOver(getNodeInShadowTreeStack(oldElementId));
106     clearEventRecords();
107     moveMouseOver(getNodeInShadowTreeStack(newElementId));
108 }
109
110 function moveFocus(oldElementId, newElementId, message)
111 {
112     debug('\n' + message + '\n' + 'Moving focus from ' + oldElementId + ' to ' + newElementId);
113     getNodeInShadowTreeStack(oldElementId).focus();
114     clearEventRecords();
115     getNodeInShadowTreeStack(newElementId).focus();
116 }
117
118 function test()
119 {
120     if (window.testRunner)
121         testRunner.dumpAsText();
122     prepareDOMTree(document.getElementById('sandbox'));
123
124     // Test for mouseover/mouseout events.
125     moveMouse('divB', 'divC',
126               'Move mouse from a node to its sibling node. All nodes are outside of shadow boundary.');
127     shouldBe('dispatchedEvent("mouseover")', '["divC(<-divB)(@divC)", "divC(<-divB)(@divA)"]');
128     shouldBe('dispatchedEvent("mouseout")', '["divB(<-divC)(@divB)", "divB(<-divC)(@divA)"]');
129
130     moveMouse('divB', 'divA',
131               'Target is an ancestor of relatedTarget. All nodes are outside of shadow boundary.');
132     shouldBe('dispatchedEvent("mouseover")', '["divA(<-divB)(@divA)"]');
133     shouldBe('dispatchedEvent("mouseout")', '["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]');
134
135     moveMouse('divA', 'divB',
136               'RelatedTarget is an ancestor of target. All nodes are outside of shadow boundary.');
137     shouldBe('dispatchedEvent("mouseover")', '["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]');
138     shouldBe('dispatchedEvent("mouseout")', '["divA(<-divB)(@divA)"]');
139
140     moveMouse('shadowD/shadowF/shadowG/divH', 'shadowD/shadowF/shadowG/divI',
141               'Both target and relatedTarget are immediate children of the same shadow root.');
142     shouldBe('dispatchedEvent("mouseover")', '["divI(<-divH)(@divI)"]');
143     shouldBe('dispatchedEvent("mouseout")', '["divH(<-divI)(@divH)"]');
144
145     moveMouse('shadowD/shadowF/shadowG/divI', 'shadowD/divE',
146               'Target is an ancestor of relatedTarget.');
147     shouldBe('dispatchedEvent("mouseover")', '["divE(<-shadowF)(@divE)"]');
148     shouldBe('dispatchedEvent("mouseout")', '["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]');
149
150     moveMouse('shadowD/shadowF/shadowG/divI', 'shadowD/shadowF',
151               'Target (shadow host) is an ancestor of relatedTarget.');
152     shouldBe('dispatchedEvent("mouseover")', '[]');
153     shouldBe('dispatchedEvent("mouseout")', '["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]');
154
155     moveMouse('shadowD/shadowF/shadowG', 'shadowD',
156               'Target (shadow host) is an ancestor of relatedTarget (shadow host).');
157     shouldBe('dispatchedEvent("mouseover")', '[]');
158     shouldBe('dispatchedEvent("mouseout")', '["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]');
159
160     moveMouse('shadowD/divE', 'shadowD/shadowF/shadowG/divI',
161               'RelatedTarget is ancestor of target.');
162     shouldBe('dispatchedEvent("mouseover")', '["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]');
163     shouldBe('dispatchedEvent("mouseout")', '["divE(<-shadowF)(@divE)"]');
164
165     moveMouse('shadowD/shadowF', 'shadowD/shadowF/shadowG/divI',
166               'RelatedTarget (shadow host) is ancestor of target.');
167     shouldBe('dispatchedEvent("mouseover")', '["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]');
168     shouldBe('dispatchedEvent("mouseout")', '[]');
169
170     moveMouse('shadowD', 'shadowD/shadowF/shadowG',
171               'RelatedTarget (shadow host) is an ancestor of target (shadow host).');
172     shouldBe('dispatchedEvent("mouseover")', '["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]');
173     shouldBe('dispatchedEvent("mouseout")', '[]');
174
175     moveMouse('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
176               'Target and relatedTarget exist in separated subtree, crossing shadow boundaries. Making sure that event is not dispatched beyond the lowest common boundary.');
177     shouldBe('dispatchedEvent("mouseover")', '["divL(<-shadowF)(@divL)", "shadowK(<-shadowF)(@shadowK)", "shadowK(<-shadowF)(@divJ)"]');
178     shouldBe('dispatchedEvent("mouseout")', '["divH(<-shadowK)(@divH)", "shadowG(<-shadowK)(@shadowG)", "shadowF(<-shadowK)(@shadowF)", "shadowF(<-shadowK)(@divE)"]');
179
180     // Test for focusin/focusout events.
181     moveFocus('divB', 'divC',
182               'Move focus from a node to its sibling node. All nodes are outside of shadow boundary.');
183     shouldBe('dispatchedEvent("focusin")', '["divC(@divC)", "divC(@divA)"]');
184     shouldBe('dispatchedEvent("focusout")', '["divB(@divB)", "divB(@divA)"]');
185
186     moveFocus('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
187               'Old focused node and new focused node exist in separated subtrees, crossing shadow boundaries. Making sure that an event is not dispatched beyond the lowest common boundary.');
188     shouldBe('dispatchedEvent("focusin")', '["divL(@divL)", "shadowK(@shadowK)", "shadowK(@divJ)"]');
189     shouldBe('dispatchedEvent("focusout")', '["divH(@divH)", "shadowG(@shadowG)", "shadowF(@shadowF)", "shadowF(@divE)"]');
190
191     // Omitted test cases where either a oldFocusedNode or newFocusedNode is an ancestor of the other.
192     // Due to a focus transfer mechanism on shadow hosts, a focused node should be a leaf node in general.
193
194     // Test for focus/blur events. Event listners should be registerd on captureing phase.
195     moveFocus('divB', 'divC',
196               'Move focus from a node to its sibling node. All nodes are outside of shadow boundary.');
197     shouldBe('dispatchedEvent("focus")', '["divC(@divA)(capturing phase)", "divC(@divC)"]');
198     shouldBe('dispatchedEvent("blur")', '["divB(@divA)(capturing phase)", "divB(@divB)"]');
199
200     moveFocus('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
201               'Old focused node and new focused node exist in separated subtrees, crossing shadow boundaries. Making sure that an event is not dispatched beyond the lowest common boundary.');
202     shouldBe('dispatchedEvent("focus")', '["shadowK(@divJ)(capturing phase)", "shadowK(@shadowK)", "divL(@divL)"]');
203     shouldBe('dispatchedEvent("blur")', '["shadowF(@divE)(capturing phase)", "shadowF(@shadowF)", "shadowG(@shadowG)", "divH(@divH)"]');
204 }
205
206 test();
207 </script>
208 <script src="../../js/resources/js-test-post.js"></script>
209 </body>
210 </html>