Skip to content

Commit fabd26b

Browse files
committed
add support for iframes inside of shadow roots
1 parent f34b8ea commit fabd26b

File tree

1 file changed

+39
-13
lines changed

1 file changed

+39
-13
lines changed

lib/a11y/utils.ts

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -169,11 +169,14 @@ export async function buildBackendIdMaps(
169169

170170
let iframeNode: DOMNode | undefined;
171171
const locate = (n: DOMNode): boolean => {
172-
if (n.backendNodeId === backendNodeId) return (iframeNode = n), true;
173-
return (
174-
(n.children?.some(locate) ?? false) ||
175-
(n.contentDocument ? locate(n.contentDocument) : false)
176-
);
172+
if (n.backendNodeId === backendNodeId) {
173+
iframeNode = n;
174+
return true;
175+
}
176+
if (n.shadowRoots?.some(locate)) return true;
177+
if (n.children?.some(locate)) return true;
178+
if (n.contentDocument && locate(n.contentDocument)) return true;
179+
return false;
177180
};
178181

179182
if (!locate(root) || !iframeNode?.contentDocument) {
@@ -725,20 +728,43 @@ export async function getFrameRootXpath(
725728
const handle = await frame.frameElement();
726729
// Evaluate the element's absolute XPath within the page context
727730
return handle.evaluate((node: Element) => {
728-
const pos = (el: Element) => {
731+
function stepFor(el: Element): string {
732+
const tag = el.tagName.toLowerCase();
729733
let i = 1;
730734
for (
731735
let sib = el.previousElementSibling;
732736
sib;
733737
sib = sib.previousElementSibling
734-
)
735-
if (sib.tagName === el.tagName) i += 1;
736-
return i;
737-
};
738+
) {
739+
if (sib.tagName.toLowerCase() === tag) i++;
740+
}
741+
return `${tag}[${i}]`;
742+
}
743+
738744
const segs: string[] = [];
739-
for (let el: Element | null = node; el; el = el.parentElement)
740-
segs.unshift(`${el.tagName.toLowerCase()}[${pos(el)}]`);
741-
return `/${segs.join("/")}`;
745+
let el: Element | null = node;
746+
747+
while (el) {
748+
segs.unshift(stepFor(el));
749+
if (el.parentElement) {
750+
el = el.parentElement;
751+
continue;
752+
}
753+
754+
// top of this tree: check if we’re inside a shadow root
755+
const root = el.getRootNode(); // Document or ShadowRoot
756+
if ((root as ShadowRoot).host) {
757+
// Insert a shadow hop marker so the final path contains “//”
758+
segs.unshift("");
759+
el = (root as ShadowRoot).host;
760+
continue;
761+
}
762+
763+
break;
764+
}
765+
766+
// Leading '/' + join; empty tokens become “//” between segments
767+
return "/" + segs.join("/");
742768
});
743769
}
744770

0 commit comments

Comments
 (0)