Skip to content

Conversation

dandclark
Copy link
Contributor

@dandclark dandclark commented Feb 5, 2025

Reference Target allows authors to specify an element inside a shadow root to be the target of any ID references referring to the host element. This would enable IDREF attributes such as for and aria-labelledby to refer to elements inside a component's shadow DOM while maintaining encapsulation of the internal details of the shadow DOM.

See the reference target explainer.

At a high level, the spec change consists of these parts:

  • Define the concept of "resolving the reference target" on an element, following an IDREF from a host element to its target in the shadow, and potentially recursing.
  • Update the get-the-attr-associated-element algorithm (and its array-of-elements form) to follow reference target.
  • Centralize the definitions of ID reference attributes and list-of-ID-reference attributes as "element reference" and "set of element reference" attributes instead of repeating these definitions throughout the spec.
  • Update uses of ID throughout the spec, where applicable, to use the new reference-target-aware get-the-attr-associated-element.
  • Avoid exposing reference target elements in shadows from element-returning IDL properties by retargeting the result before returning.

See also the corresponding whatwg/dom#1353 which adds the definition of reference target used in this PR.
 

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


/common-dom-interfaces.html ( diff )
/common-microsyntaxes.html ( diff )
/dom.html ( diff )
/form-control-infrastructure.html ( diff )
/form-elements.html ( diff )
/forms.html ( diff )
/iframe-embed-object.html ( diff )
/index.html ( diff )
/indices.html ( diff )
/infrastructure.html ( diff )
/input.html ( diff )
/microdata.html ( diff )
/parsing.html ( diff )
/popover.html ( diff )
/scripting.html ( diff )
/tables.html ( diff )

@dandclark dandclark changed the title Add shadowrootreferencetarget attribute to template Add reference target Feb 6, 2025
alice and others added 5 commits March 14, 2025 16:20
…d make some tweaks in the Reflection section
Collapse most of the 'reset the form owner' checks into one that just watches for changes to the form(attr)-associated-element.
@alice
Copy link
Contributor

alice commented Mar 19, 2025

There's also a WIP PR against ARIA to account for these changes: w3c/aria#2474

There are some open questions being discussed there on exactly how to refer to the HTML concepts, since ARIA needs to be language-independent, but it will certainly refer to these spec concepts in some form.

Copy link
Member

@lukewarlow lukewarlow left a comment

Choose a reason for hiding this comment

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

This should be updated to make use of the new [Reflect] syntax.

@dandclark dandclark requested a review from lukewarlow August 6, 2025 21:37
@dandclark
Copy link
Contributor Author

This should be updated to make use of the new [Reflect] syntax.

Thanks @lukewarlow! Fixed now (it wouldn't let me merge the suggestions directly due to merge conflicts).

@lukewarlow lukewarlow requested review from lukewarlow and removed request for lukewarlow August 6, 2025 21:41
@lukewarlow
Copy link
Member

I'm not sure how GitHub actually lets you dismiss change suggestions but consider this me dropping my change suggestions as they've been resolved :)

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've done an initial review pass, got about half way, but I thought I'd provide my review commentary so far because I think if I continue I may end up repeating themes.

Some meta commentary:

  • I think another formatting pass needs to be done.
  • I am a little worried about the retargeting steps, I wonder if this can be simplified to avoid retargeting and to resolve a reference target where necessary.


<li><p>If no such element exists, return null.</p></li>

<li><p>Return the result of <span data-x="resolve-the-reference-target">resolving the reference
Copy link
Member

Choose a reason for hiding this comment

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

Can you help me understand how this set of steps avoids exposing elements in the shadow root to code calling the IDL bindings?

It seems as though there's nothing in the spec that says "when script calls this, it should not resolve the reference target".

The way I would interpret this is that IDL should return the resolved reference target, which is obviously wrong as it would expose ShadowDOM elements to script.

I'm surprised this has is written this way, I would have imagined we would not change reflection rules, and instead wherever we look at an element reflecting attribute, we would have an additional step to resolve the reference target there.

Copy link
Contributor

Choose a reason for hiding this comment

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

As we discussed elsewhere, the reasoning here is:

  • This logic is just about how to actually make use of the reference target. (Probably this comment was actually meant to go somewhere else.)
  • We always resolve the reference target, even if we're just going to retarget the resulting value, so that we can handle invalid references by returning null.

We did explore the possibility in our discussion just now of not handling invalid references that way, and instead just not resolving the reference target for IDL attribute getters (in fact, this is what the original design did before that issue was filed), but my argument for keeping this behaviour was:

  • This is consistent with label.control for <label> wrapped: if no descendant element is labelable, either by being labelable itself or by having a labelable resolved reference target, label.control will return null. If there is a descendant element which has a labelable resolved reference target, that element will be returned.
  • This is also consistent with IDL setter/getters without reference target: if you attempt to set an invalid (e.g. in shadow DOM) element as an IDL attribute, for example lightDomElement.ariaActiveDescendantElement = shadowDomElement, the getter will return null (lightDomElement.ariaActiveDescendantElement === null).
  • I'm not convinced we actually gain that much either in code maintainability or in runtime performance in practice from skipping the resolve the reference target step for IDL attribute getters, since I think calls of these algorithms will be heavily dominated by "internal" callers which must do the resolution.

source Outdated
<span>shadow root</span>'s <span>reference target</span> is null, return <var>element</var>.</p>
</li>

<li><p>Let <var>referenceTarget</var> be the value of <var>element</var>'s <span>shadow
Copy link
Member

Choose a reason for hiding this comment

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

I was a little confused here about what referenceTarget represents. A non-binding suggestion: rename this to perhaps referenceTargetId or just targetId or similar?

Copy link
Contributor

Choose a reason for hiding this comment

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

referenceTargetValue?

Comment on lines 5228 to 5232
<li><p>Let <var>candidate</var> be the first element in <var>element</var>'s <span>shadow
root</span> whose <span data-x="concept-id">ID</span> matches <var>referenceTarget</var>.</p>
</li>

<li><p>If no such element exists, return null.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

These steps being disjoint were confusing to me, as step 4 read in isolation makes no reference to variables or some such. I think they'd be better joined:

Suggested change
<li><p>Let <var>candidate</var> be the first element in <var>element</var>'s <span>shadow
root</span> whose <span data-x="concept-id">ID</span> matches <var>referenceTarget</var>.</p>
</li>
<li><p>If no such element exists, return null.</p></li>
<li><p>Let <var>candidate</var> be the first element in <var>element</var>'s <span>shadow
root</span> whose <span data-x="concept-id">ID</span> matches <var>referenceTarget</var>. If no
such element exists, then return null.</p></li>

Or alternatively:

Suggested change
<li><p>Let <var>candidate</var> be the first element in <var>element</var>'s <span>shadow
root</span> whose <span data-x="concept-id">ID</span> matches <var>referenceTarget</var>.</p>
</li>
<li><p>If no such element exists, return null.</p></li>
<li><p>Let <var>candidate</var> be the first element in <var>element</var>'s <span>shadow
root</span> whose <span data-x="concept-id">ID</span> matches <var>referenceTarget</var>. If no
such element exists, then instead let <var>element</var> be null.</p></li>
<li><p>If <var>element</var> is null, then return null</p></li>

Comment on lines +5234 to +5236
<li><p>Return the result of <span data-x="resolve-the-reference-target">resolving the reference
target</span> on <var>candidate</var>.
</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

Formatting:

Suggested change
<li><p>Return the result of <span data-x="resolve-the-reference-target">resolving the reference
target</span> on <var>candidate</var>.
</p></li>
<li><p>Return the result of <span data-x="resolve-the-reference-target">resolving the reference
target</span> on <var>candidate</var>.</p></li>

source Outdated
Comment on lines 5211 to 5215
<p><dfn data-x="element reference attribute" data-lt="element reference" export>
Element reference attributes</dfn> refer to one or more Elements in the document. When specified
as content attributes, <span
data-x="element reference attribute">element reference attributes</span> refer to other Elements
via their <span data-x="concept-ID">ID</span>s.
Copy link
Member

Choose a reason for hiding this comment

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

Formatting is not right here, I think, but also it's missing a </p>:

Suggested change
<p><dfn data-x="element reference attribute" data-lt="element reference" export>
Element reference attributes</dfn> refer to one or more Elements in the document. When specified
as content attributes, <span
data-x="element reference attribute">element reference attributes</span> refer to other Elements
via their <span data-x="concept-ID">ID</span>s.
<p><dfn data-x="element reference attribute" data-lt="element reference" export>Element reference
attributes</dfn> refer to one or more Elements in the document. When specified as content
attributes, <span data-x="element reference attribute">element reference attributes</span> refer
to other Elements via their <span data-x="concept-ID">ID</span>s.</p>

</ol>
</li>
</ol>

Copy link
Member

Choose a reason for hiding this comment

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

Double line break:

Suggested change

Comment on lines 5381 to 5386
<p class="note">An element will only have
<span data-x="explicitly set attr-elements">explicitly-set <var>attr</var>-elements</span> if it
has an IDL attribute with type <code
data-x="">FrozenArray&lt;<var>T</var>&gt;?</code>, where <var>T</var> is either
<code>Element</code> or an interface that inherits from <code>Element</code>, which
<span data-x="reflect">reflects</span> <var>attr</var>.</p>
Copy link
Member

Choose a reason for hiding this comment

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

Formatting seems wonky here, maybe this is better:

Suggested change
<p class="note">An element will only have
<span data-x="explicitly set attr-elements">explicitly-set <var>attr</var>-elements</span> if it
has an IDL attribute with type <code
data-x="">FrozenArray&lt;<var>T</var>&gt;?</code>, where <var>T</var> is either
<code>Element</code> or an interface that inherits from <code>Element</code>, which
<span data-x="reflect">reflects</span> <var>attr</var>.</p>
<p class="note">An element will only have
<span data-x="explicitly set attr-elements">explicitly-set <var>attr</var>-elements</span> if it
has an IDL attribute with type <code data-x="">FrozenArray&lt;<var>T</var>&gt;?</code>, where
<var>T</var> is either <code>Element</code> or an interface that inherits from
<code>Element</code>, which <span data-x="reflect">reflects</span> <var>attr</var>.</p>

Comment on lines 5426 to 5441
<li>
<p><span data-x="list iterate">For each</span> <var>candidate</var> in <var>candidates</var>:
</p>

<ol>
<li><p>Let <var>resolvedCandidate</var> be the result of
<span data-x="resolve-the-reference-target">resolving the reference target</span> on
<var>candidate</var>.</p></li>

<li><p>If <var>resolvedCandidate</var> is not null, append <var>resolvedCandidate</var> to
<var>resolvedCandidates</var>.</p></li>
</ol>
</li>

<li><p>Return <var>resolvedCandidates</var>.</p></li>
</ol>
Copy link
Member

Choose a reason for hiding this comment

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

Why do we make a list of candidates to then re-iterate the list and resolve them? Would it not be easier to resolve each candidate during the for each steps so that candidates is a list of resolvedCandidates?

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess because there are two ways to collate candidates, and I didn't want to have each path also do the resolution.

source Outdated
Comment on lines 8764 to 8766
<li><p>Let <var>candidate</var> be the result of running <span>this</span>'s
<span data-x="get the attr-associated element">get the <var>attr</var>-associated
element</span>.</p>
Copy link
Member

Choose a reason for hiding this comment

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

Oh I see. IDL reflections take the attr-resolved reference target and then retarget against element. This seems a little like extra work for no payoff. Are there concrete reasons to do this, rather than having attr-resolved elements return the element, and then in each spec resolve the reference target?

I'm also not sure retargeting against element is always going to consistently give the same result as the old way of handling things. This feels awkward to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

Are there concrete reasons to do this, rather than having attr-resolved elements return the element, and then in each spec resolve the reference target?

I think main reason to have "get the attr-associated element(s)" do the retargeting is that we resolved that reference target should apply to all IDREF-type attributes: WICG/webcomponents#1091

So, when defining an attribute, the spec need only refer to "get the attr-associated element(s)" and it'll automatically be opted in to that behaviour, as well as automatically working with "explicitly-set attr-elements" iff the appropriate IDL attribute is defined.

If some attribute really wants to opt out, the spec can instead use the "first element whose ID matches" language (although there's not really a way to opt out and have the IDL attribute work).

I'm also not sure retargeting against element is always going to consistently give the same result as the old way of handling things. This feels awkward to me.

I think retargeting should work, regardless of whether this is in the same tree as candidate or in (relative) shadow DOM.

We did discuss a potential optimisation here: "get the attr-associated element" could take an optional argument, something like "encapsulation preserving" or "for bindings", which would cause the algorithm to return the non-resolved element iff resolving the element didn't return null. I'd be happy to draft a change to see what that would look like.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, the latest change explores moving the "retargeting" step into the "get the attr-associated element(s)" algorithms (and not actually invoking retargeting, but instead just returning the shadow host we already found before resolving the reference target).

Comment on lines 46392 to 46516
readonly attribute <span>HTMLFormElement</span>? <span data-x="dom-label-form">form</span>;
readonly attribute <span>HTMLElement</span>? <span data-x="dom-label-form">form</span>;

This comment was marked as resolved.

Copy link
Member

Choose a reason for hiding this comment

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

Would it be observable? You'd still end up with a form element returned in all existing code right?

Copy link
Member

Choose a reason for hiding this comment

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

Current code expects a form element or null. This change would mean it could get some other thing, which may be surprising.

An example of a potential surprise: if (foo.form) new FormData(foo.form) would now throw an error.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah but you'd only get that if you start using this new referencetarget? So it's not really a compat issue as I understand it?

Copy link
Member

Choose a reason for hiding this comment

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

I guess "web compatible" is the wrong term here. Old sites wouldn't break, but the change here might cause a surprise for actively maintained websites.

I guess I'll mark as resolved.

source Outdated
<ol>
<li>
<p><span data-x="list iterate">For each</span> <var>attrElement</var> in
<var>reflectedTarget</var>'s <span>explicitly set <var>attr</var>-elements</span>:</p>

Choose a reason for hiding this comment

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

What is reflectedTarget here?

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<var>reflectedTarget</var>'s <span>explicitly set <var>attr</var>-elements</span>:</p>
<var>element</var>'s <span>explicitly set <var>attr</var>-elements</span>:</p>

<span>tree</span>.</p>
a valid <span>single element reference</span> attribute
<span data-x="single-element-reference-refers-to">referring to</span> a <code>form</code>
element.</p>

Choose a reason for hiding this comment

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

This isn't perhaps super clear. I think this means that the final element from single element reference should be a form element

Copy link
Contributor

Choose a reason for hiding this comment

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

That's right. I'm not really sure of a clearer way to put it.

source Outdated
<li><p>Let <var>candidates</var> be an empty <span>list</span>.</p></li>

<li>
<p>If <var>element</var>'s has an <span>explicitly set <var>attr</var>-elements</span> which
Copy link
Member

Choose a reason for hiding this comment

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

This isn't grammatically correct. Based on the which I'm guessing the has is correct so drop the 's

Suggested change
<p>If <var>element</var>'s has an <span>explicitly set <var>attr</var>-elements</span> which
<p>If <var>element</var> has an <span>explicitly set <var>attr</var>-elements</span> which

source Outdated
order</span> is a <span data-x="category-label">labelable element</span>, then that element is the
<code>label</code> element's <span>labeled control</span>.</span></p>
attribute is specified, the attribute's value must be the a valid <span>single element
reference</span> attribute.</p>

Choose a reason for hiding this comment

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

But the final result of single element reference doesn't need to be labelable element?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah oops, I had intended to add that restriction and it must have dropped off my radar somewhere. Should be fixed now.

<var>descendant</var>.</p></li>

<li><p>If <var>candidate</var> is a <span data-x="category-label">labelable element</span>,
return <var>candidate</var>.</p></li>

Choose a reason for hiding this comment

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

This handling of descendants feels a bit surprising, but I don't have strong opinions, and the behavior is probably fine.

data-x="attr-popover">popover</code> attribute in the same <span>tree</span> as the <span
data-x="concept-button">button</span> with the <code
be a valid <span>single element reference</span> attribute
<span data-x="single-element-reference-refers-to">referring to</span> an element with the <code

Choose a reason for hiding this comment

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

Is this correct? The old text requires an element with popover attribute.

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh, yeah, something went weird there. Fixed.

[<span>CEReactions</span>, <span data-x="xattr-Reflect">Reflect</span>] attribute boolean <dfn attribute for="HTMLTemplateElement" data-x="dom-template-shadowrootclonable">shadowRootClonable</dfn>;
[<span>CEReactions</span>, <span data-x="xattr-Reflect">Reflect</span>] attribute boolean <dfn attribute for="HTMLTemplateElement" data-x="dom-template-shadowRootSerializable">shadowRootSerializable</dfn>;
[<span>CEReactions</span>, <span data-x="xattr-Reflect">Reflect</span>] attribute DOMString <dfn attribute for="HTMLTemplateElement" data-x="dom-template-shadowRootCustomElementRegistry">shadowRootCustomElementRegistry</dfn>;
[<span>CEReactions</span>, <span data-x="xattr-Reflect">Reflect</span>] attribute DOMString? <dfn attribute for="HTMLTemplateElement" data-x="dom-template-shadowRootReferenceTarget">shadowRootReferenceTarget</dfn>;

Choose a reason for hiding this comment

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

Why is shadowRootReferenceTarget nullable?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is an open issue: WICG/webcomponents#1093

- Rename variable from `referenceTarget` to `referenceTargetValue`
- Specify that label's for attribute must refer to a labelable element
- Fix error with popovertarget: attribute must refer to an element with the popover attribute.
Copy link
Contributor

@alice alice left a comment

Choose a reason for hiding this comment

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

I had to apply all the code suggestions manually, because I foolishly pushed my changes before I'd applied them.

source Outdated
</ol>
</li>

<li>If candidate is null, return null.</li>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<li>If candidate is null, return null.</li>
<li><p>If candidate is null, return null.</p></li>

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