Automatic update from web-platform-tests [Reference Target] Use relatedTarget for event propagation Replace the reference target handling in GetShadowRootParent with an approach that uses Event's existing relatedTarget concept, per discussion at [1]. The way this will work is that for CommandEvent, ToggleEvent, SubmitEvent, and InterestEvent, instead of an internal source/submitter, the events will use relatedTarget. The getters for these no longer need individual retargeting since relatedTarget gets retargeted as part of event dispatch. When relatedTarget is non-null for one of these event types, we'll dispatch it as a composed event. The result is that when the source/submitter is in a parent shadow because of reference target, we'll bubble the event into that shadow (but no further, due to the way relatedTarget already affects event dispatch). [1] https://github.com/whatwg/html/pull/10995#issuecomment-4572681552 Bug: 346835896 Change-Id: I6cb5cde907a3db8492795adb13e971bbd92f5ca5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7902895 Commit-Queue: Dan Clark <daniec@microsoft.com> Reviewed-by: Mason Freed <masonf@chromium.org> Cr-Commit-Position: refs/heads/main@{#1650570} -- wpt-commits: 9911aea5c6a42d47b89c88d43e91ce04d4f1099f wpt-pr: 60816
801 lines
34 KiB
HTML
801 lines
34 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
</head>
|
|
|
|
<body>
|
|
<x-component id="outerA">
|
|
<template shadowRootMode="open">
|
|
<button id="button" command="--foo" commandFor="middleA">Do Foo</button>
|
|
<x-component id="middleA">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerA">
|
|
<x-component id="innerA">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<div id="targetDiv"></div>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerA = document.getElementById("outerA");
|
|
const button = outerA.shadowRoot.getElementById("button");
|
|
const middleA = outerA.shadowRoot.getElementById("middleA");
|
|
const innerA = middleA.shadowRoot.getElementById("innerA");
|
|
const targetDiv = innerA.shadowRoot.getElementById("targetDiv");
|
|
let elementsReceivingEvent = [];
|
|
|
|
targetDiv.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_true(e.composed, "CommandEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
assert_array_equals(e.composedPath(), [targetDiv, innerA.shadowRoot, innerA, middleA.shadowRoot, middleA, outerA.shadowRoot],
|
|
"Event path should include only tree of target, source, and shadows in between");
|
|
});
|
|
|
|
middleA.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(middleA);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_equals(e.target, middleA,
|
|
"target for event listener on middleA is middleA");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on middleA is button");
|
|
});
|
|
|
|
innerA.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(innerA);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_equals(e.target, innerA,
|
|
"target for event listener on innerA is innerA");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerA is button");
|
|
});
|
|
|
|
outerA.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on outer component.");
|
|
});
|
|
|
|
button.click();
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, innerA, middleA], "Expected elements to receive the command event");
|
|
}, "Event propagates to source's shadow tree only; target in deeper shadow root");
|
|
</script>
|
|
|
|
<x-component id="outerB">
|
|
<template shadowRootMode="open">
|
|
<div id="targetDiv"></div>
|
|
<x-component id="middleB">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerB">
|
|
<x-component id="innerB">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="button">
|
|
<button id="button" command="--foo">Do Foo</button>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerB = document.getElementById("outerB");
|
|
const targetDiv = outerB.shadowRoot.getElementById("targetDiv");
|
|
const middleB = outerB.shadowRoot.getElementById("middleB");
|
|
const innerB = middleB.shadowRoot.getElementById("innerB");
|
|
const button = innerB.shadowRoot.getElementById("button");
|
|
button.commandForElement = targetDiv;
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerB.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on outer component.");
|
|
});
|
|
|
|
middleB.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on middle component.");
|
|
});
|
|
|
|
innerB.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on inner component.");
|
|
});
|
|
|
|
targetDiv.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_true(e.composed, "CommandEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, middleB,
|
|
"source for event listener on targetDiv should be retargeted to middleB");
|
|
assert_array_equals(e.composedPath(), [targetDiv, outerB.shadowRoot],
|
|
"Event path should include only tree of target, excluding source if source is not a shadow-including ancestor");
|
|
});
|
|
button.click()
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv], "Expected elements to receive the command event");
|
|
}, "Event propagates to source's shadow tree only; source in deeper shadow root");
|
|
</script>
|
|
|
|
<x-component id="outerC">
|
|
<template shadowRootMode="open">
|
|
<button id="button" command="--foo" commandFor="outerMiddleC">Do Foo</button>
|
|
<x-component id="outerMiddleC">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<x-component id="innerMiddleC">
|
|
<template shadowRootMode="open">
|
|
<x-component id="innerC">
|
|
<template shadowRootMode="open">
|
|
<slot id="innerCSlot"><!--innerMiddleCSlot with targetDiv--></slot>
|
|
</template>
|
|
<slot id="innerMiddleCSlot"><!--targetDiv--></slot>
|
|
</x-component>
|
|
</template>
|
|
<div id="targetDiv"></div>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerC = document.getElementById("outerC");
|
|
const button = outerC.shadowRoot.getElementById("button");
|
|
const outerMiddleC = outerC.shadowRoot.getElementById("outerMiddleC");
|
|
const innerMiddleC = outerMiddleC.shadowRoot.getElementById("innerMiddleC");
|
|
const targetDiv = outerMiddleC.shadowRoot.getElementById("targetDiv");
|
|
const innerC = innerMiddleC.shadowRoot.getElementById("innerC");
|
|
const innerMiddleCSlot = innerMiddleC.shadowRoot.getElementById("innerMiddleCSlot");
|
|
const innerCSlot = innerC.shadowRoot.getElementById("innerCSlot");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerC.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on outer component.");
|
|
});
|
|
|
|
outerMiddleC.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(outerMiddleC);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_equals(e.target, outerMiddleC,
|
|
"target for event listener on outerMiddleC is outerMiddleC");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on outerMiddleC is button");
|
|
});
|
|
|
|
innerMiddleC.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on inner middle component.");
|
|
});
|
|
|
|
innerC.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(innerC);
|
|
assert_unreached("Command event should not fire on inner component.");
|
|
});
|
|
|
|
targetDiv.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_true(e.composed, "CommandEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
assert_array_equals(e.composedPath(), [targetDiv, innerMiddleCSlot, innerCSlot, innerC.shadowRoot, innerC,
|
|
innerMiddleC.shadowRoot, innerMiddleC, outerMiddleC.shadowRoot, outerMiddleC, outerC.shadowRoot],
|
|
"Event path should include all shadows and shadow hosts up through the root of the source");
|
|
});
|
|
|
|
button.click()
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, outerMiddleC], "Expected elements to receive the command event");
|
|
}, "Event path includes all shadow hosts and shadow roots up to the root of the source, but non-bubbling event fires only where currentTarget == retargeted original target");
|
|
</script>
|
|
|
|
<x-component id="outerD">
|
|
<template shadowRootMode="open">
|
|
<button id="button" command="--foo" commandFor="outerMiddleD">Do Foo</button>
|
|
<x-component id="outerMiddleD">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerMiddleD">
|
|
<x-component id="innerMiddleD">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<x-component id="innerD">
|
|
<template shadowRootMode="open">
|
|
<slot id="innerDSlot"><!--targetDiv--></slot>
|
|
</template>
|
|
<div id="targetDiv"></div>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerD = document.getElementById("outerD");
|
|
const button = outerD.shadowRoot.getElementById("button");
|
|
const outerMiddleD = outerD.shadowRoot.getElementById("outerMiddleD");
|
|
const innerMiddleD = outerMiddleD.shadowRoot.getElementById("innerMiddleD");
|
|
const targetDiv = innerMiddleD.shadowRoot.getElementById("targetDiv");
|
|
const innerD = innerMiddleD.shadowRoot.getElementById("innerD");
|
|
const innerDSlot = innerD.shadowRoot.getElementById("innerDSlot");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerD.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on outer component.");
|
|
});
|
|
|
|
outerMiddleD.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(outerMiddleD);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_equals(e.target, outerMiddleD,
|
|
"target for event listener on outerMiddleD is outerMiddleD");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on outerMiddleD is button");
|
|
});
|
|
|
|
innerMiddleD.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(innerMiddleD);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_equals(e.target, innerMiddleD,
|
|
"target for event listener on innerMiddleD is innerMiddleD");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerMiddleD is button");
|
|
});
|
|
|
|
innerD.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on innerD component.");
|
|
});
|
|
|
|
targetDiv.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_true(e.composed, "CommandEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
assert_array_equals(e.composedPath(), [targetDiv, innerDSlot, innerD.shadowRoot, innerD,
|
|
innerMiddleD.shadowRoot, innerMiddleD, outerMiddleD.shadowRoot, outerMiddleD, outerD.shadowRoot],
|
|
"Event path should include all shadows and shadow hosts up through the root of the source");
|
|
});
|
|
|
|
button.click()
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, innerMiddleD, outerMiddleD], "Expected elements to receive the command event");
|
|
}, "Event path skips root containing neither source nor target. Non-bubbling event fires only where currentTarget == retargeted original target");
|
|
</script>
|
|
|
|
<button id="buttonE" command="--foo" commandFor="innerE">Do Foo</button>
|
|
<x-component id="innerE">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<div id="targetDiv"></div>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const button = document.getElementById("buttonE");
|
|
const innerE = document.getElementById("innerE");
|
|
const targetDiv = innerE.shadowRoot.getElementById("targetDiv");
|
|
let elementsReceivingEvent = [];
|
|
|
|
document.body.addEventListener("command", (e) => {
|
|
assert_unreached("non-bubbling command event should not fire on document.body.");
|
|
});
|
|
|
|
document.documentElement.addEventListener("command", (e) => {
|
|
assert_unreached("non-bubbling command event should not fire on document.documentElement.");
|
|
});
|
|
|
|
document.addEventListener("command", (e) => {
|
|
assert_unreached("non-bubbling command event should not fire on document.");
|
|
});
|
|
|
|
window.addEventListener("command", (e) => {
|
|
assert_unreached("non-bubbling command event should not fire on window.");
|
|
});
|
|
|
|
innerE.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(innerE);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_equals(e.target, innerE,
|
|
"target for event listener on innerE is innerE");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerE is button");
|
|
});
|
|
|
|
targetDiv.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_true(e.composed, "CommandEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
assert_array_equals(e.composedPath(), [targetDiv, innerE.shadowRoot, innerE,
|
|
document.body, document.documentElement, document, window],
|
|
"Event path goes all the way to the window since the source isn't in a shadow");
|
|
});
|
|
|
|
button.click();
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, innerE], "Expected elements to receive the command event");
|
|
}, "Event path should go all the way to window if source is not in a shadow, but should only fire when currentTarget == retargeted original target");
|
|
</script>
|
|
|
|
<x-component id="outerF">
|
|
<template shadowRootMode="open">
|
|
<button id="button">Do Foo</button>
|
|
<x-component id="middleF">
|
|
<template shadowRootMode="open">
|
|
<x-component id="innerF">
|
|
<template shadowRootMode="open">
|
|
<div id="targetDiv">Target div</div>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerF = document.getElementById("outerF");
|
|
const button = outerF.shadowRoot.getElementById("button");
|
|
const middleF = outerF.shadowRoot.getElementById("middleF");
|
|
const innerF = middleF.shadowRoot.getElementById("innerF");
|
|
const targetDiv = innerF.shadowRoot.getElementById("targetDiv");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerF.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on outer component.");
|
|
});
|
|
|
|
innerF.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on inner component.");
|
|
});
|
|
|
|
targetDiv.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_false(e.composed, "CommandEvent should not be composed for constructed event");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
assert_array_equals(e.composedPath(), [targetDiv, innerF.shadowRoot],
|
|
"Event path should include only tree of target and tree of source");
|
|
});
|
|
|
|
targetDiv.firstChild.addEventListener("command", (e) => {
|
|
elementsReceivingEvent.push(targetDiv.firstChild);
|
|
assert_true(e instanceof CommandEvent);
|
|
assert_false(e.composed, "CommandEvent should not be composed for constructed event");
|
|
assert_equals(e.relatedTarget, undefined, "CommandEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv.firstChild,
|
|
"target for event listener on targetDiv's first child is targetDiv's first child");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv's first child is button");
|
|
assert_array_equals(e.composedPath(), [targetDiv.firstChild, targetDiv, innerF.shadowRoot],
|
|
"Event path should include only tree of target and tree of source, even when event is dispatched on text node");
|
|
});
|
|
|
|
middleF.addEventListener("command", (e) => {
|
|
assert_unreached("Command event should not fire on middle component.");
|
|
});
|
|
|
|
const commandEvent = new CommandEvent("command", {source: button});
|
|
targetDiv.dispatchEvent(commandEvent);
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv], "Expected elements to receive the command event");
|
|
|
|
elementsReceivingEvent = [];
|
|
const commandEvent2 = new CommandEvent("command", {source: button});
|
|
targetDiv.firstChild.dispatchEvent(commandEvent2);
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv.firstChild], "Expected elements to receive the command event");
|
|
}, "Constructed events given a source don't become composed");
|
|
</script>
|
|
|
|
<x-component id="outerG">
|
|
<template shadowRootMode="open">
|
|
<button id="button" popovertarget="middleG">Toggle</button>
|
|
<x-component id="middleG">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerG">
|
|
<x-component id="innerG">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<div id="targetDiv" popover></div>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerG = document.getElementById("outerG");
|
|
const button = outerG.shadowRoot.getElementById("button");
|
|
const middleG = outerG.shadowRoot.getElementById("middleG");
|
|
const innerG = middleG.shadowRoot.getElementById("innerG");
|
|
const targetDiv = innerG.shadowRoot.getElementById("targetDiv");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerG.addEventListener("beforetoggle", (e) => {
|
|
assert_unreached("beforetoggle event should not fire on outer component.");
|
|
}, {once: true});
|
|
|
|
innerG.addEventListener("beforetoggle", (e) => {
|
|
elementsReceivingEvent.push(innerG);
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_equals(e.target, innerG,
|
|
"target for event listener on innerG is innerG");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerG is button");
|
|
}, {once: true});
|
|
|
|
targetDiv.addEventListener("beforetoggle", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_true(e.composed, "ToggleEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "ToggleEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
}, {once: true});
|
|
|
|
middleG.addEventListener("beforetoggle", (e) => {
|
|
elementsReceivingEvent.push(middleG);
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_equals(e.target, middleG,
|
|
"target for event listener on middleG is middleG");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on middleG is button");
|
|
}, {once: true});
|
|
|
|
button.click();
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, innerG, middleG], "Expected elements to receive the beforetoggle event");
|
|
|
|
t.add_cleanup(() => {
|
|
if (targetDiv.matches(':popover-open')) targetDiv.hidePopover();
|
|
});
|
|
}, "beforetoggle event propagates to source's shadow tree only; target in deeper shadow root");
|
|
</script>
|
|
|
|
<x-component id="outerG2">
|
|
<template shadowRootMode="open">
|
|
<button id="button" popovertarget="middleG2">Toggle</button>
|
|
<x-component id="middleG2">
|
|
<template shadowRootMode="open">
|
|
<x-component id="innerG2">
|
|
<template shadowRootMode="open">
|
|
<div id="targetDiv" popover></div>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerG2 = document.getElementById("outerG2");
|
|
const button = outerG2.shadowRoot.getElementById("button");
|
|
const middleG2 = outerG2.shadowRoot.getElementById("middleG2");
|
|
const innerG2 = middleG2.shadowRoot.getElementById("innerG2");
|
|
const targetDiv = innerG2.shadowRoot.getElementById("targetDiv");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerG2.addEventListener("beforetoggle", (e) => {
|
|
assert_unreached("beforetoggle event should not fire on outer component.");
|
|
}, {once: true});
|
|
|
|
innerG2.addEventListener("beforetoggle", (e) => {
|
|
elementsReceivingEvent.push(innerG2);
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_equals(e.target, innerG2,
|
|
"target for event listener on innerG2 is innerG2");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerG2 is button");
|
|
}, {once: true});
|
|
|
|
targetDiv.addEventListener("beforetoggle", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_true(e.composed, "ToggleEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "ToggleEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
}, {once: true});
|
|
|
|
middleG2.addEventListener("beforetoggle", (e) => {
|
|
elementsReceivingEvent.push(middleG2);
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_equals(e.target, middleG2,
|
|
"target for event listener on middleG2 is middleG2");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on middleG2 is button");
|
|
}, {once: true});
|
|
|
|
targetDiv.showPopover({source: button});
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, innerG2, middleG2], "Expected elements to receive the beforetoggle event");
|
|
t.add_cleanup(() => {
|
|
if (targetDiv.matches(':popover-open')) targetDiv.hidePopover();
|
|
});
|
|
}, "beforetoggle event propagates to source's shadow tree only; target in deeper shadow root; showPopover called directly with source option");
|
|
</script>
|
|
|
|
<x-component id="outerH">
|
|
<template shadowRootMode="open">
|
|
<button id="button" popovertarget="middleH">Toggle</button>
|
|
<x-component id="middleH">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerH">
|
|
<x-component id="innerH">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<div id="targetDiv" popover></div>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
promise_test(async (t) => {
|
|
const outerH = document.getElementById("outerH");
|
|
const button = outerH.shadowRoot.getElementById("button");
|
|
const middleH = outerH.shadowRoot.getElementById("middleH");
|
|
const innerH = middleH.shadowRoot.getElementById("innerH");
|
|
const targetDiv = innerH.shadowRoot.getElementById("targetDiv");
|
|
|
|
const targetDivToggleFired = new Promise((resolve) => {
|
|
targetDiv.addEventListener("toggle", (e) => {
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_true(e.composed, "ToggleEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "ToggleEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
resolve();
|
|
}, {once: true});
|
|
});
|
|
|
|
const middleHToggleFired = new Promise((resolve) => {
|
|
middleH.addEventListener("toggle", (e) => {
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_equals(e.target, middleH,
|
|
"target for event listener on middleH is middleH");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on middleH is button");
|
|
resolve();
|
|
}, {once: true});
|
|
});
|
|
|
|
const innerHToggleFired = new Promise((resolve) => {
|
|
innerH.addEventListener("toggle", (e) => {
|
|
assert_true(e instanceof ToggleEvent);
|
|
assert_equals(e.target, innerH,
|
|
"target for event listener on innerH is innerH");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerH is button");
|
|
resolve();
|
|
}, {once: true});
|
|
});
|
|
|
|
outerH.addEventListener("toggle", (e) => {
|
|
assert_unreached("toggle event should not fire on outer component.");
|
|
}, {once: true});
|
|
|
|
|
|
button.click();
|
|
await Promise.all([targetDivToggleFired, innerHToggleFired, middleHToggleFired]);
|
|
t.add_cleanup(() => {
|
|
if (targetDiv.matches(':popover-open')) targetDiv.hidePopover();
|
|
});
|
|
}, "toggle event propagates to source's shadow tree only; target in deeper shadow root");
|
|
</script>
|
|
|
|
<x-component id="outerI">
|
|
<template shadowRootMode="open">
|
|
<button id="button" type="submit" form="middleI">Submit</button>
|
|
<x-component id="middleI">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerI">
|
|
<x-component id="innerI">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetForm">
|
|
<form id="targetForm"></form>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
|
|
<script>
|
|
test((t) => {
|
|
const outerI = document.getElementById("outerI");
|
|
const button = outerI.shadowRoot.getElementById("button");
|
|
const middleI = outerI.shadowRoot.getElementById("middleI");
|
|
const innerI = middleI.shadowRoot.getElementById("innerI");
|
|
const targetForm = innerI.shadowRoot.getElementById("targetForm");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerI.addEventListener("submit", (e) => {
|
|
assert_unreached("submit event should not fire on outer component.");
|
|
}, {once: true});
|
|
|
|
innerI.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
elementsReceivingEvent.push(innerI);
|
|
assert_true(e instanceof SubmitEvent);
|
|
assert_equals(e.target, innerI,
|
|
"target for event listener on innerI is innerI");
|
|
assert_equals(e.submitter, button,
|
|
"submitter for event listener on innerI is button");
|
|
}, {once: true});
|
|
|
|
targetForm.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
elementsReceivingEvent.push(targetForm);
|
|
assert_true(e instanceof SubmitEvent);
|
|
assert_true(e.composed, "SubmitEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "SubmitEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetForm,
|
|
"target for event listener on targetForm is targetForm");
|
|
assert_equals(e.submitter, button,
|
|
"submitter for event listener on targetForm is button");
|
|
}, {once: true});
|
|
|
|
middleI.addEventListener("submit", (e) => {
|
|
e.preventDefault();
|
|
elementsReceivingEvent.push(middleI);
|
|
assert_true(e instanceof SubmitEvent);
|
|
assert_equals(e.target, middleI,
|
|
"target for event listener on middleI is middleI");
|
|
assert_equals(e.submitter, button,
|
|
"submitter for event listener on middleI is button");
|
|
}, {once: true});
|
|
|
|
button.click();
|
|
assert_array_equals(elementsReceivingEvent, [targetForm, innerI, middleI], "Expected elements to receive the submit event");
|
|
}, "submit event propagates to submitter's shadow tree only; target in deeper shadow root");
|
|
</script>
|
|
|
|
<x-component id="outerJ">
|
|
<template shadowRootMode="open">
|
|
<style>
|
|
button {
|
|
interest-delay: 0s;
|
|
}
|
|
</style>
|
|
<button id="button" interestfor="middleJ">Interest</button>
|
|
<x-component id="middleJ">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="innerJ">
|
|
<x-component id="innerJ">
|
|
<template shadowRootMode="open" shadowRootReferenceTarget="targetDiv">
|
|
<div id="targetDiv"></div>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
</template>
|
|
</x-component>
|
|
<button id="otherFocusTarget">Other</button>
|
|
|
|
<script>
|
|
function waitForRender() {
|
|
return new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
}
|
|
async function focusOn(element) {
|
|
element.focus();
|
|
await waitForRender();
|
|
}
|
|
|
|
promise_test(async (t) => {
|
|
const outerJ = document.getElementById("outerJ");
|
|
const button = outerJ.shadowRoot.getElementById("button");
|
|
const middleJ = outerJ.shadowRoot.getElementById("middleJ");
|
|
const innerJ = middleJ.shadowRoot.getElementById("innerJ");
|
|
const targetDiv = innerJ.shadowRoot.getElementById("targetDiv");
|
|
let elementsReceivingInterestEvent = [];
|
|
|
|
outerJ.addEventListener("interest", (e) => {
|
|
assert_unreached("interest event should not fire on outer component.");
|
|
}, {once: true});
|
|
|
|
innerJ.addEventListener("interest", (e) => {
|
|
elementsReceivingInterestEvent.push(innerJ);
|
|
assert_true(e instanceof InterestEvent);
|
|
assert_equals(e.target, innerJ,
|
|
"target for event listener on innerJ is innerJ");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on innerJ is button");
|
|
}, {once: true});
|
|
|
|
targetDiv.addEventListener("interest", (e) => {
|
|
elementsReceivingInterestEvent.push(targetDiv);
|
|
assert_true(e instanceof InterestEvent);
|
|
assert_true(e.composed, "InterestEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "InterestEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on targetDiv is button");
|
|
}, {once: true});
|
|
|
|
middleJ.addEventListener("interest", (e) => {
|
|
elementsReceivingInterestEvent.push(middleJ);
|
|
assert_true(e instanceof InterestEvent);
|
|
assert_equals(e.target, middleJ,
|
|
"target for event listener on middleJ is middleJ");
|
|
assert_equals(e.source, button,
|
|
"source for event listener on middleJ is button");
|
|
}, {once: true});
|
|
|
|
await focusOn(button);
|
|
assert_array_equals(elementsReceivingInterestEvent, [targetDiv, innerJ, middleJ], "Expected elements to receive the interest event");
|
|
}, "interest event propagates to source's shadow tree only; target in deeper shadow root");
|
|
|
|
promise_test(async (t) => {
|
|
const outerJ = document.getElementById("outerJ");
|
|
const button = outerJ.shadowRoot.getElementById("button");
|
|
const middleJ = outerJ.shadowRoot.getElementById("middleJ");
|
|
const innerJ = middleJ.shadowRoot.getElementById("innerJ");
|
|
const targetDiv = innerJ.shadowRoot.getElementById("targetDiv");
|
|
const otherFocusTarget = document.getElementById("otherFocusTarget");
|
|
let elementsReceivingEvent = [];
|
|
|
|
outerJ.addEventListener("loseinterest", (e) => {
|
|
assert_unreached("loseinterest event should not fire on outer component.");
|
|
}, {once: true});
|
|
|
|
targetDiv.addEventListener("loseinterest", (e) => {
|
|
elementsReceivingEvent.push(targetDiv);
|
|
assert_true(e instanceof InterestEvent);
|
|
assert_true(e.composed, "InterestEvent with source should be composed");
|
|
assert_equals(e.relatedTarget, undefined, "InterestEvent doesn't expose relatedTarget property");
|
|
assert_equals(e.target, targetDiv,
|
|
"target for loseinterest event listener on targetDiv is targetDiv");
|
|
assert_equals(e.source, button,
|
|
"source for loseinterest event listener on targetDiv is button");
|
|
}, {once: true});
|
|
|
|
middleJ.addEventListener("loseinterest", (e) => {
|
|
elementsReceivingEvent.push(middleJ);
|
|
assert_true(e instanceof InterestEvent);
|
|
assert_equals(e.target, middleJ,
|
|
"target for loseinterest event listener on middleJ is middleJ");
|
|
assert_equals(e.source, button,
|
|
"source for loseinterest event listener on middleJ is button");
|
|
}, {once: true});
|
|
|
|
innerJ.addEventListener("loseinterest", (e) => {
|
|
elementsReceivingEvent.push(innerJ);
|
|
assert_true(e instanceof InterestEvent);
|
|
assert_equals(e.target, innerJ,
|
|
"target for loseinterest event listener on innerJ is innerJ");
|
|
assert_equals(e.source, button,
|
|
"source for loseinterest event listener on innerJ is button");
|
|
}, {once: true});
|
|
|
|
await focusOn(button);
|
|
await focusOn(otherFocusTarget);
|
|
assert_array_equals(elementsReceivingEvent, [targetDiv, innerJ, middleJ], "Expected elements to receive the loseinterest event");
|
|
}, "loseinterest event propagates to source's shadow tree only; target in deeper shadow root");
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|