Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 10 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /shadow-dom/reference-target/tentative/event-path.html - WPT Dashboard Interop Dashboard
<!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 targetDivCommandEventCount = 0;
let middleACommandEventCount = 0;
outerA.addEventListener("command", (e) => {
assert_unreached("Command event should not fire on outer component.");
});
innerA.addEventListener("command", (e) => {
assert_unreached("Command event should not fire on inner component.");
});
targetDiv.addEventListener("command", (e) => {
targetDivCommandEventCount++;
assert_true(e instanceof CommandEvent);
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, middleA, outerA.shadowRoot],
"Event path should include only tree of target and tree of source");
});
middleA.addEventListener("command", (e) => {
middleACommandEventCount++;
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");
});
button.click();
assert_equals(targetDivCommandEventCount, 1, "Command handler on targetDiv should have been called");
assert_equals(middleACommandEventCount, 1, "Command handler on middleA should have been called");
}, "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");
assert_true(button instanceof HTMLButtonElement);
button.commandForElement = targetDiv;
let targetDivCommandEventCount = 0;
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) => {
targetDivCommandEventCount++;
assert_true(e instanceof CommandEvent);
assert_equals(e.target, targetDiv,
"target for event listener on targetDiv is targetDiv");
assert_equals(e.source, middleB,
"source for event listener on targetDiv is 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_equals(targetDivCommandEventCount, 1, "Command handler on targetDiv should have been called");
}, "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 outerMiddleCCommandEventCount = 0;
let targetDivCommandEventCount = 0;
outerC.addEventListener("command", (e) => {
assert_unreached("Command event should not fire on outer component.");
});
outerMiddleC.addEventListener("command", (e) => {
outerMiddleCCommandEventCount++;
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) => {
assert_unreached("Command event should not fire on inner component.");
});
targetDiv.addEventListener("command", (e) => {
targetDivCommandEventCount++;
assert_true(e instanceof CommandEvent);
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_equals(outerMiddleCCommandEventCount, 1, "Command handler on outerMiddleC should have been called");
assert_equals(targetDivCommandEventCount, 1, "Command handler on targetDiv should have been called");
}, "Event path includes all shadow hosts and shadow roots up to the root of the source, but non-bubbling event fires only 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 outerMiddleDCommandEventCount = 0;
let targetDivCommandEventCount = 0;
outerD.addEventListener("command", (e) => {
assert_unreached("Command event should not fire on outer component.");
});
outerMiddleD.addEventListener("command", (e) => {
outerMiddleDCommandEventCount++;
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) => {
assert_unreached("Command event should not fire on inner middle component.");
});
innerD.addEventListener("command", (e) => {
assert_unreached("Command event should not fire on innerD component.");
});
targetDiv.addEventListener("command", (e) => {
targetDivCommandEventCount++;
assert_true(e instanceof CommandEvent);
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, outerMiddleD, outerD.shadowRoot],
"Event path skips root containing neither source nor target");
});
button.click()
assert_equals(outerMiddleDCommandEventCount, 1, "Command handler on outerMiddleD should have been called");
assert_equals(targetDivCommandEventCount, 1, "Command handler on targetDiv should have been called");
}, "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 innerECommandEventCount = 0;
let targetDivCommandEventCount = 0;
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) => {
innerECommandEventCount++;
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) => {
targetDivCommandEventCount++;
assert_true(e instanceof CommandEvent);
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_equals(innerECommandEventCount, 1, "Command handler on innerE should have been called");
assert_equals(targetDivCommandEventCount, 1, "Command handler on targetDiv should have been called");
}, "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 targetDivCommandEventCount = 0;
let targetDivFirstChildCommandEventCount = 0;
let middleFCommandEventCount = 0;
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) => {
targetDivCommandEventCount++;
assert_true(e instanceof CommandEvent);
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, middleF, outerF.shadowRoot],
"Event path should include only tree of target and tree of source");
});
targetDiv.firstChild.addEventListener("command", (e) => {
targetDivFirstChildCommandEventCount++;
assert_true(e instanceof CommandEvent);
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, middleF, outerF.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) => {
middleFCommandEventCount++;
assert_true(e instanceof CommandEvent);
assert_equals(e.target, middleF,
"target for event listener on middleF is middleF");
assert_equals(e.source, button,
"source for event listener on middleF is button");
});
const commandEvent = new CommandEvent("command", {source: button});
targetDiv.dispatchEvent(commandEvent);
assert_equals(targetDivCommandEventCount, 1, "Command handler on targetDiv should have been called");
assert_equals(targetDivFirstChildCommandEventCount, 0, "Command handler on targetDiv's first child should not have been called");
assert_equals(middleFCommandEventCount, 1, "Command handler on middleF should have been called");
const commandEvent2 = new CommandEvent("command", {source: button});
targetDiv.firstChild.dispatchEvent(commandEvent2);
assert_equals(targetDivCommandEventCount, 1, "Non-bubbling event doesn't bubble to parent when is dispatched on text node");
assert_equals(targetDivFirstChildCommandEventCount, 1, "Command handler on targetDiv's first child should have been called when event is dispatched on text node");
assert_equals(middleFCommandEventCount, 2, "Command handler on middleF should have been called when event is dispatched on text node");
}, "Event propagates to source tree when source is given in synthetic event, even without reference target");
</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 targetDivToggleEventCount = 0;
let middleGToggleEventCount = 0;
outerG.addEventListener("beforetoggle", (e) => {
assert_unreached("beforetoggle event should not fire on outer component.");
}, {once: true});
innerG.addEventListener("beforetoggle", (e) => {
assert_unreached("beforetoggle event should not fire on inner component.");
}, {once: true});
targetDiv.addEventListener("beforetoggle", (e) => {
targetDivToggleEventCount++;
assert_true(e instanceof ToggleEvent);
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) => {
middleGToggleEventCount++;
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_equals(targetDivToggleEventCount, 1, "beforetoggle handler on targetDiv should have been called");
assert_equals(middleGToggleEventCount, 1, "beforetoggle handler on middleG should have been called");
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="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");
let targetDivToggleEventCount = 0;
let middleHToggleEventCount = 0;
const targetDivToggleFired = new Promise((resolve) => {
targetDiv.addEventListener("toggle", (e) => {
targetDivToggleEventCount++;
assert_true(e instanceof ToggleEvent);
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) => {
middleHToggleEventCount++;
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});
});
outerH.addEventListener("toggle", (e) => {
assert_unreached("toggle event should not fire on outer component.");
}, {once: true});
innerH.addEventListener("toggle", (e) => {
assert_unreached("toggle event should not fire on inner component.");
}, {once: true});
button.click();
await Promise.all([targetDivToggleFired, 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 targetFormSubmitEventCount = 0;
let middleISubmitEventCount = 0;
outerI.addEventListener("submit", (e) => {
assert_unreached("submit event should not fire on outer component.");
}, {once: true});
innerI.addEventListener("submit", (e) => {
assert_unreached("submit event should not fire on inner component.");
}, {once: true});
targetForm.addEventListener("submit", (e) => {
e.preventDefault();
targetFormSubmitEventCount++;
assert_true(e instanceof SubmitEvent);
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) => {
middleISubmitEventCount++;
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_equals(targetFormSubmitEventCount, 1, "submit handler on targetForm should have been called");
assert_equals(middleISubmitEventCount, 1, "submit handler on middleI should have been called");
}, "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 targetDivInterestEventCount = 0;
let middleJInterestEventCount = 0;
outerJ.addEventListener("interest", (e) => {
assert_unreached("interest event should not fire on outer component.");
}, {once: true});
innerJ.addEventListener("interest", (e) => {
assert_unreached("interest event should not fire on inner component.");
}, {once: true});
targetDiv.addEventListener("interest", (e) => {
targetDivInterestEventCount++;
assert_true(e instanceof InterestEvent);
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) => {
middleJInterestEventCount++;
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_equals(targetDivInterestEventCount, 1, "interest handler on targetDiv should have been called");
assert_equals(middleJInterestEventCount, 1, "interest handler on middleJ should have been called");
}, "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 targetDivLoseInterestEventCount = 0;
let middleJLoseInterestEventCount = 0;
outerJ.addEventListener("loseinterest", (e) => {
assert_unreached("loseinterest event should not fire on outer component.");
}, {once: true});
innerJ.addEventListener("loseinterest", (e) => {
assert_unreached("loseinterest event should not fire on inner component.");
}, {once: true});
targetDiv.addEventListener("loseinterest", (e) => {
targetDivLoseInterestEventCount++;
assert_true(e instanceof InterestEvent);
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) => {
middleJLoseInterestEventCount++;
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});
await focusOn(button);
await focusOn(otherFocusTarget);
assert_equals(targetDivLoseInterestEventCount, 1, "loseinterest handler on targetDiv should have been called");
assert_equals(middleJLoseInterestEventCount, 1, "loseinterest handler on middleJ should have been called");
}, "loseinterest event propagates to source's shadow tree only; target in deeper shadow root");
</script>
</body>
</html>