<!DOCTYPE html>
<html>
<head>
<title>Shadow DOM: Capturing event listeners should be invoked before bubbling event listeners</title>
<meta name="author" title="Ryosuke Niwa" href="mailto:[email protected]">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="./resources/shadow-dom.js"></script>
<script>
function attachEventListeners(eventType, tree) {
const eventLogs = [];
const makeComposedPathResult = (event) => event.composedPath().map((node) => node.id)
for (const id in tree) {
const node = tree[id];
node.addEventListener(eventType, event => eventLogs.push(
['bubbling', event.eventPhase, event.target.id, event.currentTarget.id, makeComposedPathResult(event)]), {capture: false});
node.addEventListener(eventType, event => eventLogs.push(
['capturing', event.eventPhase, event.target.id, event.currentTarget.id, makeComposedPathResult(event)]), {capture: true});
}
return eventLogs;
}
</script>
</head>
<body>
<div id="test1">
<div id="parent">
<div id="target"></div>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test1'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));
const composedPath = ['target', 'parent', 'test1'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'target', 'test1', composedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'parent', composedPath],
['capturing', Event.AT_TARGET, 'target', 'target', composedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', composedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'parent', composedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'test1', composedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners on the target without shadow trees');
</script>
<div id="test2">
<div id="host">
<template id="shadowRoot" data-mode="closed">
<div id="target"></div>
</template>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test2'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));
const innerComposedPath = ['target', 'shadowRoot', 'host', 'test2'];
const outerComposedPath = ['host', 'test2'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'host', 'test2', outerComposedPath],
['capturing', Event.AT_TARGET, 'host', 'host', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', innerComposedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', innerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['bubbling', Event.AT_TARGET, 'host', 'host', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'host', 'test2', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree');
</script>
<div id="test3">
<div id="outerHost">
<template id="outerShadowRoot" data-mode="closed">
<div id="innerHost">
<template id="innerShadowRoot" data-mode="closed">
<div id="target"></div>
</template>
</div>
</template>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test3'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));
const innerShadowComposedPath = ['target', 'innerShadowRoot', 'innerHost', 'outerShadowRoot', 'outerHost', 'test3'];
const outerShadowComposedPath = ['innerHost', 'outerShadowRoot', 'outerHost', 'test3'];
const outerComposedPath = ['outerHost', 'test3'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'outerHost', 'test3', outerComposedPath],
['capturing', Event.AT_TARGET, 'outerHost', 'outerHost', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'innerHost', 'outerShadowRoot', outerShadowComposedPath],
['capturing', Event.AT_TARGET, 'innerHost', 'innerHost', outerShadowComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'innerShadowRoot', innerShadowComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', innerShadowComposedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', innerShadowComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'innerShadowRoot', innerShadowComposedPath],
['bubbling', Event.AT_TARGET, 'innerHost', 'innerHost', outerShadowComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'innerHost', 'outerShadowRoot', outerShadowComposedPath],
['bubbling', Event.AT_TARGET, 'outerHost', 'outerHost', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'outerHost', 'test3', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a doubly nested shadow tree');
</script>
<div id="test4">
<div id="host">
<template id="shadowRoot" data-mode="closed">
<slot id="slot"></slot>
</template>
<div id="target"></div>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test4'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));
const innerComposedPath = ['target', 'slot', 'shadowRoot', 'host', 'test4'];
const outerComposedPath = ['target', 'host', 'test4'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'target', 'test4', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'host', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'slot', innerComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', outerComposedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'slot', innerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'shadowRoot', innerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'host', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'test4', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched via a slot');
</script>
<div id="test5">
<div id="upperHost">
<template id="upperShadowRoot" data-mode="closed">
<slot id="upperSlot"></slot>
</template>
<div id="lowerHost">
<template id="lowerShadowRoot" data-mode="closed">
<div id="target"></div>
</template>
</div>
</div>
</div>
<script>
test(() => {
const tree = createTestTree(document.getElementById('test5'));
const logs = attachEventListeners('my-event', tree);
tree.target.dispatchEvent(new Event('my-event', { bubbles: true, composed: true }));
const lowerComposedPath = ['target', 'lowerShadowRoot', 'lowerHost', 'upperHost', 'test5'];
const upperComposedPath = ['lowerHost', 'upperSlot', 'upperShadowRoot', 'upperHost', 'test5'];
const outerComposedPath = ['lowerHost', 'upperHost', 'test5'];
assert_object_equals(logs, [
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'test5', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperHost', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperShadowRoot', upperComposedPath],
['capturing', Event.CAPTURING_PHASE, 'lowerHost', 'upperSlot', upperComposedPath],
['capturing', Event.AT_TARGET, 'lowerHost', 'lowerHost', outerComposedPath],
['capturing', Event.CAPTURING_PHASE, 'target', 'lowerShadowRoot', lowerComposedPath],
['capturing', Event.AT_TARGET, 'target', 'target', lowerComposedPath],
['bubbling', Event.AT_TARGET, 'target', 'target', lowerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'target', 'lowerShadowRoot', lowerComposedPath],
['bubbling', Event.AT_TARGET, 'lowerHost', 'lowerHost', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperSlot', upperComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperShadowRoot', upperComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'upperHost', outerComposedPath],
['bubbling', Event.BUBBLING_PHASE, 'lowerHost', 'test5', outerComposedPath],
]);
}, 'Capturing event listeners should be invoked before bubbling event listeners when an event is dispatched inside a shadow tree which passes through another shadow tree');
</script>
</body>
</html>