Skip to content

Commit df97b80

Browse files
authored
fix: Handle self-referencing in aria-labelledby (#902)
* fix: Handle self-referencing in `aria-labelledby` * Update sources/accessible-name-and-description.ts
1 parent 71337d6 commit df97b80

File tree

3 files changed

+55
-6
lines changed

3 files changed

+55
-6
lines changed

.changeset/heavy-falcons-melt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"dom-accessibility-api": patch
3+
---
4+
5+
Fix a case of maximum call stack size exceeded when a node referenced itself in `aria-labelledby`.

sources/__tests__/accessible-name.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,39 @@ test.each([
407407
"a logo",
408408
],
409409
[` <input type="radio" data-test title="crazy"/>`, "crazy"],
410+
[
411+
`
412+
<h2 id="lorem-heading">
413+
Lorem ipsum
414+
<a data-test aria-labelledby="lorem-heading" href="#lorem-heading" tabindex="-1">
415+
<svg></svg>
416+
</a>
417+
</h2>
418+
`,
419+
"Lorem ipsum",
420+
],
421+
[
422+
`
423+
<label for="toggle-button" id="label" value="full">
424+
<button
425+
data-test
426+
aria-expanded="false"
427+
aria-haspopup="listbox"
428+
aria-labelledby="label toggle-button"
429+
id="toggle-button"
430+
type="button"
431+
>
432+
<span>
433+
Full Refund
434+
</span>
435+
</button>
436+
<div>
437+
Refund Type
438+
</div>
439+
</label>
440+
`,
441+
"Full Refund Refund Type",
442+
],
410443
])(`misc #%#`, (markup, expectedAccessibleName) => {
411444
expect(markup).toRenderIntoDocumentAccessibleName(expectedAccessibleName);
412445
});

sources/accessible-name-and-description.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -575,23 +575,34 @@ export function computeTextAlternative(
575575
}
576576

577577
// 2B
578-
const labelElements = queryIdRefs(current, "aria-labelledby");
578+
const labelAttributeNode = isElement(current)
579+
? current.getAttributeNode("aria-labelledby")
580+
: null;
581+
// TODO: Do we generally need to block query IdRefs of attributes we have already consulted?
582+
const labelElements =
583+
labelAttributeNode !== null && !consultedNodes.has(labelAttributeNode)
584+
? queryIdRefs(current, "aria-labelledby")
585+
: [];
579586
if (
580587
compute === "name" &&
581588
!context.isReferenced &&
582589
labelElements.length > 0
583590
) {
591+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Can't be null here otherwise labelElements would be empty
592+
consultedNodes.add(labelAttributeNode!);
593+
584594
return labelElements
585-
.map((element) =>
586-
computeTextAlternative(element, {
595+
.map((element) => {
596+
// TODO: Chrome will consider repeated values i.e. use a node multiple times while we'll bail out in computeTextAlternative.
597+
return computeTextAlternative(element, {
587598
isEmbeddedInLabel: context.isEmbeddedInLabel,
588599
isReferenced: true,
589-
// thais isn't recursion as specified, otherwise we would skip
600+
// this isn't recursion as specified, otherwise we would skip
590601
// `aria-label` in
591602
// <input id="myself" aria-label="foo" aria-labelledby="myself"
592603
recursion: false,
593-
})
594-
)
604+
});
605+
})
595606
.join(" ");
596607
}
597608

0 commit comments

Comments
 (0)