mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-03 07:51:12 +03:00
fix(role): account for unslotted elements being hidden for aria (#22070)
When element is not assigned to any slot in the shadow root, it is not rendered and is considered hidden for ARIA in all browsers. In Chromium/Firefox we use `Element.checkVisibility` that already handles this, but in WebKit we have to check it manually. Fixes #21487.
This commit is contained in:
parent
0c4eedbabe
commit
548e4a0c0f
@ -232,6 +232,8 @@ function getAriaBoolean(attr: string | null) {
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion, but including "none" and "presentation" roles
|
||||
// Not implemented:
|
||||
// `Any descendants of elements that have the characteristic "Children Presentational: True"`
|
||||
// https://www.w3.org/TR/wai-aria-1.2/#aria-hidden
|
||||
export function isElementHiddenForAria(element: Element, cache: Map<Element, boolean>): boolean {
|
||||
if (['STYLE', 'SCRIPT', 'NOSCRIPT', 'TEMPLATE'].includes(element.tagName))
|
||||
@ -242,17 +244,30 @@ export function isElementHiddenForAria(element: Element, cache: Map<Element, boo
|
||||
const isSlot = element.nodeName === 'SLOT';
|
||||
if (!isOptionInsideSelect && !isSlot && !isElementStyleVisibilityVisible(element))
|
||||
return true;
|
||||
return belongsToDisplayNoneOrAriaHidden(element, cache);
|
||||
return belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element, cache);
|
||||
}
|
||||
|
||||
function belongsToDisplayNoneOrAriaHidden(element: Element, cache: Map<Element, boolean>): boolean {
|
||||
function belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element: Element, cache: Map<Element, boolean>): boolean {
|
||||
if (!cache.has(element)) {
|
||||
const style = getElementComputedStyle(element);
|
||||
let hidden = !style || style.display === 'none' || getAriaBoolean(element.getAttribute('aria-hidden')) === true;
|
||||
let hidden = false;
|
||||
|
||||
// When parent has a shadow root, all light dom children must be assigned to a slot,
|
||||
// otherwise they are not rendered and considered hidden for aria.
|
||||
// Note: we can remove this logic once WebKit supports `Element.checkVisibility`.
|
||||
if (element.parentElement && element.parentElement.shadowRoot && !element.assignedSlot)
|
||||
hidden = true;
|
||||
|
||||
// display:none and aria-hidden=true are considered hidden for aria.
|
||||
if (!hidden) {
|
||||
const style = getElementComputedStyle(element);
|
||||
hidden = !style || style.display === 'none' || getAriaBoolean(element.getAttribute('aria-hidden')) === true;
|
||||
}
|
||||
|
||||
// Check recursively.
|
||||
if (!hidden) {
|
||||
const parent = parentElementOrShadowHost(element);
|
||||
if (parent)
|
||||
hidden = hidden || belongsToDisplayNoneOrAriaHidden(parent, cache);
|
||||
hidden = belongsToDisplayNoneOrAriaHiddenOrNonSlotted(parent, cache);
|
||||
}
|
||||
cache.set(element, hidden);
|
||||
}
|
||||
|
@ -446,3 +446,36 @@ test('errors', async ({ page }) => {
|
||||
const e8 = await page.$('role=treeitem[expanded="none"]').catch(e => e);
|
||||
expect(e8.message).toContain(`"expanded" must be one of true, false`);
|
||||
});
|
||||
|
||||
test('hidden with shadow dom slots', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<div make-hidden>
|
||||
<button>hidden1</button>
|
||||
</div>
|
||||
<div make-hidden>
|
||||
<span><button>hidden2</button></v>
|
||||
</div>
|
||||
<div>
|
||||
<button>visible1</button>
|
||||
</div>
|
||||
<div>
|
||||
<span><button>visible2</button></span>
|
||||
</div>
|
||||
<script>
|
||||
for (const div of document.querySelectorAll('div')) {
|
||||
const hidden = div.hasAttribute('make-hidden');
|
||||
div.attachShadow({ mode: 'open' }).innerHTML = hidden ? 'nothing to see here' : '<slot></slot>';
|
||||
}
|
||||
</script>
|
||||
`);
|
||||
expect(await page.locator(`role=button`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
|
||||
`<button>visible1</button>`,
|
||||
`<button>visible2</button>`,
|
||||
]);
|
||||
expect(await page.locator(`role=button[include-hidden]`).evaluateAll(els => els.map(e => e.outerHTML))).toEqual([
|
||||
`<button>hidden1</button>`,
|
||||
`<button>hidden2</button>`,
|
||||
`<button>visible1</button>`,
|
||||
`<button>visible2</button>`,
|
||||
]);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user