Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!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>