如何针对开放Shadow Root执行XPath表达式求值?
问题描述
现有一个包含开放Shadow Root的DOM树(标记为dom),结构如下:
<div id="parent"> #shadow-root (open) <div id="child1">a</div> <div id="child2">b</div> </div>
需要在Shadow Root内执行XPath(标记为xpath,例如//div[contains(@id, "child1")]),返回Shadow Root内首个匹配的节点(本示例应为div#child1)。
已尝试但失败的方法
document.evaluate(xpath, dom, null, XPathResult.FIRST_ORDERED_NODE_TYPE):返回null,因为元素在Shadow Root的document-fragment中,不在根文档或dom节点内document.evaluate(xpath, dom.shadowRoot, null, XPathResult.FIRST_ORDERED_NODE_TYPE):抛出Uncaught DOMException: Failed to execute 'evaluate' on 'Document': The node provided is '#document-fragment', which is not a valid context node type.,符合预期- 使用
const xe = new XPathEvaluator(); xe.evaluate(...)执行上述两种场景,结果相同
不适用的现有方案
- 在Selenium/Cypress环境中使用内置方法
- 从XPath切换为
querySelector - 将Shadow Root的子元素移至主宿主内再执行XPath
可行解决方案
方法1:以Shadow Root的子节点为上下文,调整XPath为相对路径
Shadow Root本身是document-fragment无法作为上下文,但可以用它的子节点作为上下文,同时修改XPath为相对路径:
const shadowRoot = dom.shadowRoot; // 取Shadow Root的第一个子元素作为上下文节点 const contextNode = shadowRoot.firstElementChild; // 把绝对路径XPath转为相对路径(比如//div改为.//div) const adjustedXpath = xpath.replace(/^\/\//, './/'); const targetNode = document.evaluate( adjustedXpath, contextNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue;
如果Shadow Root可能为空,需要先判断contextNode是否存在,避免报错。
方法2:遍历Shadow Root内所有元素,逐个匹配XPath
手动遍历Shadow Root下的所有元素节点,对每个节点用相对XPath检查是否匹配:
function queryShadowRootXPath(shadowRoot, xpath) { const walker = document.createTreeWalker( shadowRoot, NodeFilter.SHOW_ELEMENT, null, false ); let currentNode; while (currentNode = walker.nextNode()) { // 转换为相对XPath,检查当前节点是否匹配 const matchXpath = xpath.startsWith('.') ? xpath : `.${xpath}`; const result = document.evaluate( matchXpath, currentNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE ).singleNodeValue; if (result === currentNode) { return currentNode; } } return null; } // 使用示例 const targetNode = queryShadowRootXPath(dom.shadowRoot, '//div[contains(@id, "child1")]');
该方法会遍历Shadow Root内所有元素,找到第一个匹配项后立即返回。
方法3:利用Shadow Root所属文档创建XPathEvaluator
通过Shadow Root的ownerDocument创建XPathEvaluator,将上下文设为Shadow Root:
const shadowRoot = dom.shadowRoot; const evaluator = shadowRoot.ownerDocument.createXPathEvaluator(); const targetNode = evaluator.evaluate( xpath, shadowRoot, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
注:部分浏览器对该方式的支持有限,需要实际测试验证。
内容的提问来源于stack exchange,提问作者Gaurang Tandon
相关产品推荐
相关产品推荐

