| <!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:rniwa@webkit.org"> |
| <script src="../../resources/testharness.js"></script> |
| <script src="../../resources/testharnessreport.js"></script> |
| <script src="../../imported/w3c/web-platform-tests/shadow-dom/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> |