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