Skip to content

Conversation

@josepharhar
Copy link
Contributor

@josepharhar josepharhar commented May 29, 2025

This change prevents CommandEvent.source from leaking nodes across shadow boundaries. More context here:
#11255 (comment)

Event.currentTarget and Event.target already have retargeting capabilities built into them to prevent leaking nodes across shadow boundaries. By retargeting against those, we can make sure that Event.source does not leak across shadow boundaries.

(See WHATWG Working Mode: Changes for more details.)


/interaction.html ( diff )

This change prevents CommandEvent.source from leaking nodes across
shadow boundaries. More context here:
whatwg#11255 (comment)
Copy link
Member

@domenic domenic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Editorially LGTM, but I'd appreciate someone more familiar with the intent checking the logic.

Copy link
Member

@keithamus keithamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good, it would certainly solve the issue. I'm a little curious if this can be simplified to "if target is null or currentTarget is null, return null, otherwise retarget source against currentTarget". I'm not sure of the performance cost of re-targeting twice.

@josepharhar
Copy link
Contributor Author

I think this looks good, it would certainly solve the issue. I'm a little curious if this can be simplified to "if target is null or currentTarget is null, return null, otherwise retarget source against currentTarget". I'm not sure of the performance cost of re-targeting twice.

I think there are cases where currentTarget is null but target is not null after event dispatch, so we should continue considering target in addition to currentTarget. See the "clearTargets" variable here and how it only removes the target from the event in some situations (this prevents leaking nodes): https://dom.spec.whatwg.org/#concept-event-dispatch

@josepharhar
Copy link
Contributor Author

I just pushed a change to move the retargeting logic into a separate algorithm so it can also be called for the ToggleEvent.source getter

@josepharhar josepharhar changed the title Improve CommandEvent.source retargeting Improve CommandEvent and ToggleEvent source retargeting Jun 4, 2025
source Outdated
data-x="dom-Event-currentTarget">currentTarget</code>.</p></li>

<li><p>If <var>retargetAgainst</var> is null, then set <var>retargetAgainst</var> to
<var>event</var>'s <span data-x="concept-event-target">target</span>.</p></li>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this setup. We want events which have been dispatched in light DOM to have their .source retargeted? What is the use case for that. Events dispatched in shadow DOM would get just null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want events which have been dispatched in light DOM to have their .source retargeted?

If the event has been dispatched in light DOM, then retargeting won't change the resulting source element that we return; we are effectively not retargeting events that are dispatched in light DOM.

Events dispatched in shadow DOM would get just null.

If the event was dispatched in shadow DOM, then source will become null after event dispatch is done, just like target and relatedTarget.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the event has been dispatched in light dom and source is in shadow DOM, retargeting will happen.

But what are we trying to achieve with this rather unusual setup? Why do we need to fall back to 'target'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the event has been dispatched in light dom and source is in shadow DOM, retargeting will happen.

Ah yes, that is true. In this case, I'm assuming that you still don't want source to be leaked somewhere since it is in a shadow root, so we should do retargeting.

But what are we trying to achieve with this rather unusual setup? Why do we need to fall back to 'target'?

Target is better than currentTarget because after event dispatch target is likely still set to something we can retarget against which won't leak any nodes, whereas currentTarget just becomes null.

Now that I think about it, maybe we can just always retarget against target instead of currentTarget

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the usage of currentTarget and now it just uses target. It shouldn't change the behavior.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that also what happens for relatedTarget? Ideally source works the same way as relatedTarget.

source Outdated
return null.</p></li>

<li><p>Return the result of <span data-x="dom-retarget">retargeting</span> <var>node</var>
against <var>retargetAgainst</var>.</p></li>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retargetAgainst isn't defined anywhere

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But what is actually wrong with the current .source handling. I'm still not quite sure what we're trying to fix here. The current spec retargets shadow course to first light DOM host after dispatch. And during dispatch it is retargeted so that .source will be in the same or "higher" subtree than currentTarget/target.

The discussion about having relatedTarget type of behavior is more about tweaking the propagation path if we kept command event as composed, since once relatedTarget and target are in the same subtree, the event won't propagate higher up.
https://dom.spec.whatwg.org/#dispatching-events 6.9.7

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retargetAgainst isn't defined anywhere

Thanks, I fixed that.

But what is actually wrong with the current .source handling.

Since it returns the original source element when currentTarget is not set, after event dispatch you can get the original element without retargeting: #11255 (comment)

The discussion about having relatedTarget type of behavior is more about tweaking the propagation path if we kept command event as composed, since once relatedTarget and target are in the same subtree, the event won't propagate higher up.

I agree, I made this more simple assuming that we are going to make the command event not composed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it returns the original source element when currentTarget is not set

Well, only if the original source element is in light DOM.

@josepharhar josepharhar added the agenda+ To be discussed at a triage meeting label Jul 15, 2025
<li><p>Return the result of <span data-x="dom-retarget">retargeting</span> <var>node</var>
against <var>event</var>'s <span data-x="concept-event-target">target</span>.</p></li>
</ol>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand what we're trying to fix here. The current setup doesn't leak shadow DOM .source, or I don't understand how it does that. Could you explain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would only leak if you create a synthetic event with source set to something in a shadow tree. Or if a new specification comes along that does something similar and reuses this event class. The question has become if we should put in protections for these cases as well. I kinda think we should.

I still don't understand why this is not the same as relatedTarget though. It seems weird to have a completely different mechanism.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it leak even with synthetic events?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't retarget wouldn't it just return the node it was set to? Which could be from a shadow tree?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It actually looks like neither Chrome nor Firefox follow the existing spec, as is written today. The spec today says:

The source getter steps are to return the result of retargeting source against this's currentTarget.

But both implementations do more like:

The source getter steps are to return the result of retargeting source against this's currentTarget if currentTarget is not null, otherwise return source.

This can be seen in the Chrome implementation of command_event.cc L46-50:

  if (current) {
    return &current->ToNode()->GetTreeScope().Retarget(*source);
  }
  DCHECK_EQ(eventPhase(), Event::PhaseType::kNone);
  return source;

And in the Firefox implementation CommandEvent.cpp L83-93:

  if (currentTarget) {
    nsINode* currentTargetNode = currentTarget->GetAsNode();
    if (!currentTargetNode) {
      return nullptr;
    }
    nsINode* retargeted = nsContentUtils::Retarget(
        static_cast<nsINode*>(mSource), currentTargetNode);
    return retargeted ? retargeted->AsElement() : nullptr;
  }
  MOZ_ASSERT(!mEvent->mFlags.mIsBeingDispatched);
  return mSource;

So the spec is right and two impls are wrong. If the implementations followed the spec this would be a non-issue, and this PR is perhaps redundant.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't retarget wouldn't it just return the node it was set to? Which could be from a shadow tree?

there is already the existing retarget in the spec.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, your concern is with the rewrite in particular, not with retargeting in general. Glad we're in agreement on retargeting in general. @keithamus or @josepharhar will have to answer this then.

Copy link
Member

@keithamus keithamus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at this a lot more closely with @smaug---- I don't think this is necessary any more, I think implementations have misinterpreted the spec to cause a bug that we're now trying to fix with more spec but actually simply need to fix with implementation changes.

@annevk
Copy link
Member

annevk commented Aug 14, 2025

Can we capture the bug in a WPT?

@keithamus
Copy link
Member

@annevk
Copy link
Member

annevk commented Aug 15, 2025

So is the proposed resolution here that we rename that away from tentative and close this PR?

@keithamus
Copy link
Member

So is the proposed resolution here that we rename that away from tentative and close this PR?

I think so. If I change the implementation to unconditionally retarget it passes those tests. Here's the Firefox patch: https://phabricator.services.mozilla.com/D261248. I believe Chrome's could be similar, without the need for these spec changes.

@josepharhar
Copy link
Contributor Author

Sounds good to me

@annevk
Copy link
Member

annevk commented Aug 16, 2025

Let's close it once there's a PR to rename the test.

@annevk annevk reopened this Aug 16, 2025
@josepharhar
Copy link
Contributor Author

@keithamus I made the change to retarget against currentTarget and otherwise return null, which still sounds like a good implementation to me, but it required making these WPT changes: https://chromium-review.googlesource.com/c/chromium/src/+/6853807

Do those changes look good to you? Is firefox passing those tests without the changes I'm proposing?

@keithamus
Copy link
Member

@josepharhar firefox passes those tests without the changes (module the popover toggle-source ones as that's still todo for us).

With the changes in your patch applied button-event-dispatch.html also passes but source-attribute-retargeting.tentative.html fails:

FAIL CommandEvent.source should be retargeted when manually dispatched with composed set to true. - assert_equals: host2: event.source after dispatch. expected null but got Element node <div id="host2">

Looking at your patch I think this might not be correct; AIUI it should be <div id="host2> because the re-targeting can always take place. I think the Chromium code leads you to avoid retargeting in some cases, instead returning nullptr when current_target is nullptr, which is IMO incorrect.

If I read through the spec and assume that A (.source) is an element inside a shadow root and B (current_target) is null, then I am left to understand that we hit step 2 and the host of A (.source) should be returned. The retargeting algorithm should only return null when A (.source) is null.

I think Chrome's implementation of Retarget predicates itself on the existence of B (CurrentTarget()) which takes you down the path of returning nullptr if B is nullptr. It is my opinion that Chromium's retarget algorithm should be refactored such that the provided element is the element you're retargeting against (so B), not A, IOW I think it should be closer to:

Element* Event::Retarget(Element* element) const {
  EventTarget* current_target = currentTarget();
  if (element) {
    return &element->GetTreeScope().Retarget(current_target ? current_target->ToNode() :  nullptr);
  }
  return nullptr;
} 

@josepharhar
Copy link
Contributor Author

Thanks Keith! I think I have an implementation that works now, I'll try getting it shipped and remove tentative from the test and report back here.

@domenic domenic removed the agenda+ To be discussed at a triage meeting label Aug 28, 2025
aarongable pushed a commit to chromium/chromium that referenced this pull request Sep 2, 2025
Apparently the original implementation could leak nodes out of the
shadowroot due to a misreading of the spec:
whatwg/html#11345 (review)

This patch makes the new retargeting logic use currentTarget, and if
currentTarget is null, then null is returned instead of returning the
original node.

Bug: 420639769
Change-Id: Ib5e580f83e68aa58ca5b9e8f024391e0191cb604
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853807
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1509850}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Sep 4, 2025
This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
aarongable pushed a commit to chromium/chromium that referenced this pull request Sep 4, 2025
This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1511147}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Sep 4, 2025
This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1511147}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Sep 4, 2025
This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1511147}
lando-prod-mozilla bot pushed a commit to mozilla-firefox/firefox that referenced this pull request Sep 8, 2025
…ult, a=testonly

Automatic update from web-platform-tests
Enable ImprovedSourceRetargeting by default

This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1511147}

--

wpt-commits: 8b9696d6f3e7846524153410b1e53f3dce4ab630
wpt-pr: 54700
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified that referenced this pull request Sep 9, 2025
…ult, a=testonly

Automatic update from web-platform-tests
Enable ImprovedSourceRetargeting by default

This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <jarharchromium.org>
Reviewed-by: Mason Freed <masonfchromium.org>
Cr-Commit-Position: refs/heads/main{#1511147}

--

wpt-commits: 8b9696d6f3e7846524153410b1e53f3dce4ab630
wpt-pr: 54700

UltraBlame original commit: e34e0c9116212a877fc7ae3c38407c8432e78ba4
gecko-dev-updater pushed a commit to marco-c/gecko-dev-comments-removed that referenced this pull request Sep 9, 2025
…ult, a=testonly

Automatic update from web-platform-tests
Enable ImprovedSourceRetargeting by default

This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <jarharchromium.org>
Reviewed-by: Mason Freed <masonfchromium.org>
Cr-Commit-Position: refs/heads/main{#1511147}

--

wpt-commits: 8b9696d6f3e7846524153410b1e53f3dce4ab630
wpt-pr: 54700

UltraBlame original commit: e34e0c9116212a877fc7ae3c38407c8432e78ba4
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified-and-comments-removed that referenced this pull request Sep 9, 2025
…ult, a=testonly

Automatic update from web-platform-tests
Enable ImprovedSourceRetargeting by default

This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <jarharchromium.org>
Reviewed-by: Mason Freed <masonfchromium.org>
Cr-Commit-Position: refs/heads/main{#1511147}

--

wpt-commits: 8b9696d6f3e7846524153410b1e53f3dce4ab630
wpt-pr: 54700

UltraBlame original commit: e34e0c9116212a877fc7ae3c38407c8432e78ba4
i3roly pushed a commit to i3roly/firefox-dynasty that referenced this pull request Sep 9, 2025
…ult, a=testonly

Automatic update from web-platform-tests
Enable ImprovedSourceRetargeting by default

This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1511147}

--

wpt-commits: 8b9696d6f3e7846524153410b1e53f3dce4ab630
wpt-pr: 54700
mertcanaltin pushed a commit to mertcanaltin/wpt that referenced this pull request Oct 26, 2025
This is already resolved in whatwg, see discussion here:
whatwg/html#11345

Fixed: 420639769
Change-Id: I82a543bc6f93f14b487da4373492462cbdb33867
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6853857
Commit-Queue: Joey Arhar <[email protected]>
Reviewed-by: Mason Freed <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1511147}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants