Re: [WICG/webcomponents] Reference Target: How to handle events fired on the reference target by related elements? (Issue #1098)

dandclark left a comment (WICG/webcomponents#1098)

Thanks @alice for the deep investigation on this and @jakearchibald for raising the issue.

I was working on a few more test cases for this alongside landing changes for this from a draft @alice started to the [Chromium prototype](https://chromium-review.googlesource.com/c/chromium/src/+/7722304). One interesting aspect I didn't see mentioned in the above discussion is that since events like `command` don't bubble, not all elements in the event path actually get the event.

Consider this test case:

```html
<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>
```
With the algorithm discussed in this thread, the event path for the `command` event ends up being:
`[targetDiv, innerMiddleCSlot, innerCSlot, innerC.shadowRoot, innerC, innerMiddleC.shadowRoot, innerMiddleC, outerMiddleC.shadowRoot, outerMiddleC, outerC.shadowRoot]`

Of note here is that `innerC` and `innerMiddleC` are in the event path, because of the step `"if shadow root is not the root of target, and event does not have a source or shadow root is not the root of event's source, return shadow root's host;"` when running `get the parent` on `innerC.shadowRoot` and `innerMiddleC.shadowRoot`.

*But*, we don't fire `command` on `innerC` or `innerMiddleC`. We *do* fire it on `outerMiddleC`.

To understand why this happens, we first reference step 6.14 of [dispatch an event](https://dom.spec.whatwg.org/#concept-event-dispatch), which will only invoke a non-bubbling event if the `shadow-adjusted target` is non-null:

> 14. [For each](https://infra.spec.whatwg.org/#list-iterate) struct of event’s [path](https://dom.spec.whatwg.org/#event-path):
>     1. If struct’s [shadow-adjusted target](https://dom.spec.whatwg.org/#event-path-shadow-adjusted-target) is non-null, then set event’s [eventPhase](https://dom.spec.whatwg.org/#dom-event-eventphase) attribute to [AT_TARGET](https://dom.spec.whatwg.org/#dom-event-at_target).
>     1. Otherwise:
>         1. If event’s [bubbles](https://dom.spec.whatwg.org/#dom-event-bubbles) attribute is false, then [continue](https://infra.spec.whatwg.org/#iteration-continue).
>         2. Set event’s [eventPhase](https://dom.spec.whatwg.org/#dom-event-eventphase) attribute to [BUBBLING_PHASE](https://dom.spec.whatwg.org/#dom-event-bubbling_phase).
>     3. [Invoke](https://dom.spec.whatwg.org/#concept-event-listener-invoke) with struct, event, "bubbling", and legacyOutputDidListenersThrowFlag if given.

So, when is `shadow-adjusted target` non-null? This is set up earlier in [dispatch an event](https://dom.spec.whatwg.org/#concept-event-dispatch) when the event path is being built, in steps 6.9.6 and 6.9.8.

> 6. If parent is a [Window](https://html.spec.whatwg.org/multipage/nav-history-apis.html#window) object, or parent is a [node](https://dom.spec.whatwg.org/#concept-node) and target’s [root](https://dom.spec.whatwg.org/#concept-tree-root) is a [shadow-including inclusive ancestor](https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor) of parent:
>     1. If isActivationEvent is true, event’s [bubbles](https://dom.spec.whatwg.org/#dom-event-bubbles) attribute is true, activationTarget is null, and parent has [activation behavior](https://dom.spec.whatwg.org/#eventtarget-activation-behavior), then set activationTarget to parent.
>     2. [Append to an event path](https://dom.spec.whatwg.org/#concept-event-path-append) with event, parent, null, relatedTarget, touchTargets, and slot-in-closed-tree.
> 7. Otherwise, if parent is relatedTarget, then set parent to null.
> 8. Otherwise:
>     1. Set target to parent.
>     2. If isActivationEvent is true, activationTarget is null, and target has [activation behavior](https://dom.spec.whatwg.org/#eventtarget-activation-behavior), then set activationTarget to target.
>     3. [Append to an event path](https://dom.spec.whatwg.org/#concept-event-path-append) with event, parent, target, relatedTarget, touchTargets, and slot-in-closed-tree.

When `parent` is `innerC` or `innerMiddleC`, we enter the case in step 6 since `targetDiv`'s `root` (`outerMiddleC.shadowRoot`) is a shadow-including inclusive ancestor, so `shadow-adjusted target` (the third argument to [`append to an event path`](https://dom.spec.whatwg.org/#concept-event-path-append)) is set to null. So the event won't fire on these elements.

When `parent` is `outerMiddleC`, we do not enter the case in step 6 since `targetDiv`'s `root` (`outerMiddleC.shadowRoot`) is **not** a shadow-including inclusive ancestor. So we fall through to the `8. Otherwise` step, setting `shadow-adjusted target` (the third argument to [`append to an event path`](https://dom.spec.whatwg.org/#concept-event-path-append)) to `target` which was set to `parent`. So the event will fire on `outerMiddleC`.

This seems like the behavior we want, where the event fires on the `targetDiv` and on `outerMiddleC` which is the shadow host in the same tree as the source button, and not on the shadow hosts in-between. But the way we got that result for this double-slotting case was not obvious to me so I thought it was worth it to write it out.

-- 
Reply to this email directly or view it on GitHub:
https://github.com/WICG/webcomponents/issues/1098#issuecomment-4178949684
You are receiving this because you are subscribed to this thread.

Message ID: <WICG/webcomponents/issues/1098/4178949684@github.com>

Received on Thursday, 2 April 2026 16:14:32 UTC