mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore(aria): do not generate refs for pointer-events none (#35819)
This commit is contained in:
parent
cb99e260fa
commit
eda5a9efeb
@ -29,6 +29,7 @@ export type AriaNode = AriaProps & {
|
||||
children: (AriaNode | string)[];
|
||||
element: Element;
|
||||
box: Box;
|
||||
receivesPointerEvents: boolean;
|
||||
props: Record<string, string>;
|
||||
};
|
||||
|
||||
@ -43,7 +44,7 @@ export function generateAriaTree(rootElement: Element, generation: number, optio
|
||||
const visited = new Set<Node>();
|
||||
|
||||
const snapshot: AriaSnapshot = {
|
||||
root: { role: 'fragment', name: '', children: [], element: rootElement, props: {}, box: box(rootElement) },
|
||||
root: { role: 'fragment', name: '', children: [], element: rootElement, props: {}, box: box(rootElement), receivesPointerEvents: true },
|
||||
elements: new Map<number, Element>(),
|
||||
generation,
|
||||
ids: new Map<Element, number>(),
|
||||
@ -148,7 +149,7 @@ export function generateAriaTree(rootElement: Element, generation: number, optio
|
||||
|
||||
function toAriaNode(element: Element, options?: { emitGeneric?: boolean }): AriaNode | null {
|
||||
if (element.nodeName === 'IFRAME')
|
||||
return { role: 'iframe', name: '', children: [], props: {}, element, box: box(element) };
|
||||
return { role: 'iframe', name: '', children: [], props: {}, element, box: box(element), receivesPointerEvents: true };
|
||||
|
||||
const defaultRole = options?.emitGeneric ? 'generic' : null;
|
||||
const role = roleUtils.getAriaRole(element) ?? defaultRole;
|
||||
@ -156,7 +157,8 @@ function toAriaNode(element: Element, options?: { emitGeneric?: boolean }): Aria
|
||||
return null;
|
||||
|
||||
const name = normalizeWhiteSpace(roleUtils.getElementAccessibleName(element, false) || '');
|
||||
const result: AriaNode = { role, name, children: [], props: {}, element, box: box(element) };
|
||||
const receivesPointerEvents = roleUtils.receivesPointerEvents(element);
|
||||
const result: AriaNode = { role, name, children: [], props: {}, element, box: box(element), receivesPointerEvents };
|
||||
|
||||
if (roleUtils.kAriaCheckedRoles.includes(role))
|
||||
result.checked = roleUtils.getAriaChecked(element);
|
||||
@ -406,7 +408,7 @@ export function renderAriaTree(ariaSnapshot: AriaSnapshot, options?: { mode?: 'r
|
||||
key += ` [pressed]`;
|
||||
if (ariaNode.selected === true)
|
||||
key += ` [selected]`;
|
||||
if (options?.ref && ariaNode.box.visible) {
|
||||
if (options?.ref && ariaNode.box.visible && ariaNode.receivesPointerEvents) {
|
||||
const id = ariaSnapshot.ids.get(ariaNode.element);
|
||||
if (id)
|
||||
key += ` [ref=s${ariaSnapshot.generation}e${id}]`;
|
||||
|
@ -1051,6 +1051,40 @@ function getAccessibleNameFromAssociatedLabels(labels: Iterable<HTMLLabelElement
|
||||
})).filter(accessibleName => !!accessibleName).join(' ');
|
||||
}
|
||||
|
||||
export function receivesPointerEvents(element: Element): boolean {
|
||||
const cache = cachePointerEvents!;
|
||||
let e: Element | undefined = element;
|
||||
let result: boolean | undefined;
|
||||
const parents: Element[] = [];
|
||||
for (; e; e = parentElementOrShadowHost(e!)) {
|
||||
const cached = cache.get(e);
|
||||
if (cached !== undefined) {
|
||||
result = cached;
|
||||
break;
|
||||
}
|
||||
|
||||
parents.push(e);
|
||||
const style = getElementComputedStyle(e);
|
||||
if (!style) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const value = style.pointerEvents;
|
||||
if (value) {
|
||||
result = value !== 'none';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result === undefined)
|
||||
result = true;
|
||||
|
||||
for (const parent of parents)
|
||||
cache.set(parent, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let cacheAccessibleName: Map<Element, string> | undefined;
|
||||
let cacheAccessibleNameHidden: Map<Element, string> | undefined;
|
||||
let cacheAccessibleDescription: Map<Element, string> | undefined;
|
||||
@ -1059,6 +1093,7 @@ let cacheAccessibleErrorMessage: Map<Element, string> | undefined;
|
||||
let cacheIsHidden: Map<Element, boolean> | undefined;
|
||||
let cachePseudoContentBefore: Map<Element, string> | undefined;
|
||||
let cachePseudoContentAfter: Map<Element, string> | undefined;
|
||||
let cachePointerEvents: Map<Element, boolean> | undefined;
|
||||
let cachesCounter = 0;
|
||||
|
||||
export function beginAriaCaches() {
|
||||
@ -1071,6 +1106,7 @@ export function beginAriaCaches() {
|
||||
cacheIsHidden ??= new Map();
|
||||
cachePseudoContentBefore ??= new Map();
|
||||
cachePseudoContentAfter ??= new Map();
|
||||
cachePointerEvents ??= new Map();
|
||||
}
|
||||
|
||||
export function endAriaCaches() {
|
||||
@ -1083,6 +1119,7 @@ export function endAriaCaches() {
|
||||
cacheIsHidden = undefined;
|
||||
cachePseudoContentBefore = undefined;
|
||||
cachePseudoContentAfter = undefined;
|
||||
cachePointerEvents = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -731,7 +731,7 @@ it('ref mode can be used to stitch all frame snapshots', async ({ page, server }
|
||||
`.trim());
|
||||
});
|
||||
|
||||
it('should not include hidden input elements', async ({ page }) => {
|
||||
it('should not generate refs for hidden elements', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<button>One</button>
|
||||
<button style="width: 0; height: 0; appearance: none; border: 0; padding: 0;">Two</button>
|
||||
@ -744,6 +744,37 @@ it('should not include hidden input elements', async ({ page }) => {
|
||||
- button "Three" [ref=s1e5]`);
|
||||
});
|
||||
|
||||
it('should not generate refs for elements with pointer-events:none', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<button style="pointer-events: none">no-ref</button>
|
||||
<div style="pointer-events: none">
|
||||
<button style="pointer-events: auto">with-ref</button>
|
||||
</div>
|
||||
<div style="pointer-events: none">
|
||||
<div style="pointer-events: initial">
|
||||
<button>with-ref</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="pointer-events: none">
|
||||
<div style="pointer-events: auto">
|
||||
<button>with-ref</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="pointer-events: auto">
|
||||
<div style="pointer-events: none">
|
||||
<button>no-ref</button>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const snapshot = await page.locator('body').ariaSnapshot({ ref: true });
|
||||
expect(snapshot).toContain(`- button "no-ref"
|
||||
- button "with-ref" [ref=s1e5]
|
||||
- button "with-ref" [ref=s1e8]
|
||||
- button "with-ref" [ref=s1e11]
|
||||
- button "no-ref"`);
|
||||
});
|
||||
|
||||
it('emit generic roles for nodes w/o roles', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user