feat(dom/getResolvedRefs): new function to get the resolved virtual nodes of idrefs - #5151
Conversation
chutchins25
left a comment
There was a problem hiding this comment.
Nice work — clean addition, the shadow-DOM scoping tests are a good touch, and the content instanceof Node → shadowContent instanceof Node typo fix is a nice catch along the way. Two suggestions below, both bugs with simple fixes: one cross-browser correctness issue in the lib, one in the new queryShadowFixture object-param path.
Co-authored-by: Chris Hutchins <[email protected]>
WilcoFiers
left a comment
There was a problem hiding this comment.
Some comments. Think we need to at least consider deduplication
|
|
||
| const attrValue = vNode.attr(attr); | ||
| if (attrValue !== null) { | ||
| return attrValue; | ||
| } |
There was a problem hiding this comment.
Should this be in an else block? Is there ever a case where we want to run this if we checked the prop?
| const attrValue = vNode.attr(attr); | |
| if (attrValue !== null) { | |
| return attrValue; | |
| } | |
| else { | |
| const attrValue = vNode.attr(attr); | |
| if (attrValue !== null) { | |
| return attrValue; | |
| } | |
| } |
There was a problem hiding this comment.
Maybe? With the current code, if we check the prop and it's null we fallback to the attribute, which we would assume would also be null. Is there ever a chance that it wouldn't be null if the prop is?
There was a problem hiding this comment.
I just remembered that we still support IE11 so we have to fallback to the attribute case as the prop won't exist.
|
|
||
| it('should find referenced nodes by ID', () => { | ||
| const vNode = queryFixture(html` | ||
| <div aria-cats="target1 target2" id="target"></div> |
There was a problem hiding this comment.
If ARIA WG ever decides aria-cats needs to be an ARIA property we have a problem 🤣
| testUtils.queryShadowFixture = ( | ||
| content, | ||
| shadowContent, | ||
| targetSelector = '#target' | ||
| ) => { |
There was a problem hiding this comment.
If we're going to tweak this I prefer we start using declarative shadow DOM. I did that in advanced rules. It requires a bit of setup, but is much easier to read than what we've done here with queryShadowFixture.
Not a blocker.
Co-authored-by: Wilco Fiers <[email protected]>
chutchins25
left a comment
There was a problem hiding this comment.
Re-review — the earlier queryShadowFixture order-of-operations and instanceof issues look fixed, and the !== undefined guard is correct (needed for IE11). Two things left: a question on the self-reference filtering (replied on that thread), and the null-vs-undefined return inconsistency flagged inline.
chutchins25
left a comment
There was a problem hiding this comment.
Everything seems resolved and taken care of now. Thanks for that. Looks good to me. Leaving Security Review for Wilco when he applies his review.
…als (#5182) ## Summary Form-associated custom elements (`static formAssociated = true` + `attachInternals()`) can be labeled with native `<label>` elements, which the browser exposes through `ElementInternals.labels`. axe-core's accessible-name computation dropped these labels entirely, because: 1. `nativeTextAlternative` builds its naming methods from the element spec, and `getElementSpec()` returns `{}` for custom element names — so `labelText` was never invoked for a custom element. 2. Label resolution in `labelText` only looked at the DOM by `id` (`label[for]`) / ancestor wrapping. This wires up the [proposal's final step](https://github.com/dequelabs/ocarina-team/blob/main/proposals/axe-core/element-internals.md#supporting-formassociated-elements-with-labels). ## Changes - **`native-text-alternative.js`** — `findTextMethods` detects a form-associated custom element via `ElementInternals` and appends `labelText`. - **`label-text.js`** — `labelText` reads `internals.labels` directly when present (the authoritative, complete set of explicit + implicit labels), falling back to the existing DOM resolution otherwise. `ElementInternals.labels` throws `NotSupportedError` on a custom element that isn't form-associated, so both reads are guarded with try/catch. Both paths are gated by the existing `elementInternals` run option — with the flag off, `vNode.elementInternals` is `undefined` and behavior is unchanged. ### Why `internals.labels` (not the DOM lookup)? It's the browser-authoritative association and is the *complete* set — it already includes the ancestor-wrapping label. Routing it through the existing explicit + implicit merge would count a wrapping label twice, so `labelText` uses the internals list exclusively when present. ## Out of scope - **`checks/label` (`explicit`/`implicit`)** — no change needed. They resolve labels via `label[for=id]` / `closest('label')`, which already cover custom elements, and the `label` rule selector is `input, textarea` anyway. Matches the proposal ("rules that look at implicit/explicit labels will not need to be updated") and #5170 leaving those checks untouched. - **Non-labelable custom elements with a stray `<label for>`** — labelable-ness/role territory (#5039), pre-existing. - `getResolvedRefs` (#5151) — native `<label>`/`for` association isn't an ARIA idref, so it doesn't apply here. ## Testing - `test/testutils.js` — new shared `testutils-form-element` (form-associated). - `label-text.js` — explicit, implicit, multiple, implicit+explicit dedup, global-map protocol, non-form-associated fall-through, Shadow DOM. - `native-text-alternative.js` — explicit/implicit pickup, gating (non-form-associated gets no label), flag-off no-op. No integration test added — this is a `commons/text` change with no rule/check edits; the nearest rule impact (`aria-input-field-name` on a role-bearing custom element) depends on role-via-internals selection (#5039), which isn't wired yet. Closes #5045
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. ## [4.13.0](v4.12.1...v4.13.0) (2026-08-05) ### Features - **aria-actions:** add aria-actions to allowed ARIA attributes ([#5200](#5200)) ([029655d](029655d)), closes [#4584](#4584) [#5199](#5199), references [#5215](#5215) [#5215](#5215) - **aria-allowed-attr:** flag deprecated ARIA attributes as needs-review ([#5246](#5246)) ([518f3cc](518f3cc)), closes [#3341](#3341) - **aria-prohibited-attr:** allow many elements to be named and disallow label and body from being named ([#5259](#5259)) ([d8b1ea5](d8b1ea5)) - **aria-roles:** add sectionheader and sectionfooter roles ([#5238](#5238)) ([c36c109](c36c109)), closes [#4734](#4734), references [#4734](#4734) - **aria/get-aria-value:** new function to get aria values of a node ([#5109](#5109)) ([a7d8f3e](a7d8f3e)), references [#5042](#5042) - **aria/has-attr-value:** new function to check if node has aria value ([#5136](#5136)) ([61f2624](61f2624)), references [#5109](#5109) - **aria:** support role=image as equivalent to role=img ([#5248](#5248)) ([5aa8aaf](5aa8aaf)), closes [#4656](#4656), references [#5272](#5272) - **checks/aria:** support ARIA element internals properties ([#5172](#5172)) ([9b7f754](9b7f754)) - **checks/label:** support ARIA element internals properties ([#5170](#5170)) ([21c5f8b](21c5f8b)) - **checks/navigation:** support ARIA element internals properties ([#5167](#5167)) ([2c3a98f](2c3a98f)) - **commons/aria:** support ARIA element internals properties ([#5171](#5171)) ([31f09e7](31f09e7)) - **commons/dom:** support ARIA element internals properties ([#5163](#5163)) ([f0a12cf](f0a12cf)) - **commons/forms:** support ARIA element internals properties ([#5165](#5165)) ([27a4686](27a4686)) - **commons/matches/fromPrimative:** deprecate in favor of correct spelling ([#5270](#5270)) ([31cfb2e](31cfb2e)) - **commons/text:** support ARIA element internals properties ([#5169](#5169)) ([e841a33](e841a33)) - **commons/text:** support form-associated labels via element internals ([#5182](#5182)) ([57cfe0a](57cfe0a)), closes [#5045](#5045), references [#5170](#5170) [#5039](#5039) [#5151](#5151) [#5039](#5039) - **dom/getResolvedRefs:** new function to get the resolved virtual nodes of idrefs ([#5151](#5151)) ([489cdea](489cdea)), references [#5109](#5109) - **element-internals:** enable ElementInternals by default ([#5284](#5284)) ([2740d42](2740d42)), closes [#5277](#5277) - **i18n:** Add Swedish locale ([#5190](#5190)) ([dcd13f2](dcd13f2)), references [#5189](#5189) - **matches:** add inSectioningContent, hasChild, and isSummaryForDetails matches ([#5262](#5262)) ([c47cdcd](c47cdcd)) - **rules:** support ARIA element internals properties ([#5168](#5168)) ([065baf7](065baf7)) - **standards/ariaAttrs:** add caseInsensitive property for attributes ([#5224](#5224)) ([bcd791c](bcd791c)) ### Bug Fixes - **aria-allowed-role:** allow roles on a non-details summary ([#5242](#5242)) ([3bd9875](3bd9875)), closes [#3911](#3911), references [#3443](#3443) [#3911](#3911) - **aria-allowed-role:** restrict figure roles with child figcaption ([#5240](#5240)) ([178a635](178a635)), closes [#3443](#3443) - **aria-prohibited-attr:** visible aria-labelledby requires review only ([#5285](#5285)) ([fd6fa9f](fd6fa9f)) - **axe.d.ts:** make enabled property of RuleMetadata optional ([#5129](#5129)) ([90fce18](90fce18)) - **color-contrast:** fix various stacking context bugs ([#5214](#5214)) ([d5e5b04](d5e5b04)), references [#8](#8) [#5213](#5213) - **gather-internals:** handle non-HTMLElement nodes ([#5161](#5161)) ([06e84c3](06e84c3)) - **get-selector:** escape control characters in attribute selectors ([#5273](#5273)) ([4b60ac5](4b60ac5)), closes [#5204](#5204) [#5204](#5204) - **image-alt:** allow whitespace alt on presentational images ([#5218](#5218)) ([c5dd0ef](c5dd0ef)), closes [#5216](#5216) - **landmark-unique:** exclude section/form with non-landmark roles from landmark match ([#5085](#5085)) ([c5fd013](c5fd013)), closes [#4722](#4722) [#5064](#5064) - name the image role in role-img-alt and svg-img-alt metadata ([#5279](#5279)) ([995a269](995a269)), closes [#5272](#5272), references [#5248](#5248) [#5248](#5248) - **standards:** update aria-errormessage and aria-details to be idrefs ([#5157](#5157)) ([fb94f8a](fb94f8a)) This PR was opened by a robot 🤖 🎉
A new function that is very similar to
commons.dom.idrefsfunction, but understands handling ARIA idrefs properties and element internals properties and additionally returns virtual nodes rather than DOM nodes. This was the leftover work from #5109 to handle idrefs being a string (attribute) or array of nodes (property).I also update the
queryShadowFixturefunction to allow both#shadowand#targetids to exist in the light DOM. I looked at all uses ofqueryShadowFixtureand almost all of them use the#shadowin the light DOM and#targetin the shadow DOM, though a few rely on#targetin the light DOM (but no#shadow). The changes prioritize the order of find things so that#shadowis used first as the container, then#target(instead of the other way around) and the target selector is found in the shadow DOM first, then in the light DOM after (instead of the other way around). Lastly to support changing either selector I allowed the 3rd parameter to be an object that could define them (no current uses ofqueryShadowFixturepass a 3rd parameter, but I left the option to just pass the target selector as a string).