如何将用户脚本领域的DOM节点对象映射到页面领域的对应对象?
如何在用户脚本中获取页面领域的对应DOM节点对象
用户脚本默认在独立沙箱里运行,脚本里的DOM节点对象和页面原生环境的DOM节点对象不是同一个。这种隔离多数时候是好事——能避免页面修改DOM属性或原型后影响甚至搞崩用户脚本。
但有些场景下,脚本得突破沙箱限制,拿到页面环境里的DOM节点,因为脚本默认拿到的节点没有页面给原生节点加的额外属性。
举个例子:
页面HTML代码
<p id="node">Hello!</p> <script> // 页面给原生DOM节点加了自定义属性test document.getElementById('node').test = 42; </script>
用户脚本代码
// ==UserScript== // […] // ==/UserScript== // 脚本沙箱里的节点拿不到test属性 console.log(document.getElementById('node').test); // 输出 undefined // 直接访问页面环境的document拿到的节点就能获取test属性 console.log(unsafeWindow.document.getElementById('node').test); // 输出 42
如果已经持有用户脚本沙箱里的DOM节点对象,怎么获取页面环境里的对应对象?
方法1:通过节点唯一标识从页面环境重新获取
如果节点有id、class或者其他唯一标识,直接用页面环境的unsafeWindow.document来查询是最直接的:
function getPageNodeFromScriptNode(scriptNode) { const pageDoc = unsafeWindow.document; // 优先用id查询,最快 if (scriptNode.id) { return pageDoc.getElementById(scriptNode.id); } // 没有id的话,用XPath定位(适合任意节点) const xpath = getElementXPath(scriptNode); return pageDoc.evaluate( xpath, pageDoc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; } // 生成节点的XPath辅助函数 function getElementXPath(element) { if (element.id) return `//*[@id="${element.id}"]`; if (element === document.body) return '/html/body'; let index = 0; const siblings = element.parentNode.childNodes; for (let i = 0; i < siblings.length; i++) { const sibling = siblings[i]; if (sibling === element) { return `${getElementXPath(element.parentNode)}/${element.tagName.toLowerCase()}[${index + 1}]`; } if (sibling.nodeType === 1 && sibling.tagName === element.tagName) { index++; } } }
方法2:借助页面环境的函数转换
把沙箱里的节点传给页面环境的函数,页面环境里拿到的就是原生节点:
// 先在页面环境定义一个接收节点并返回的函数 unsafeWindow.getNativeNode = function(node) { return node; }; // 在脚本里调用这个函数,传入沙箱节点,得到页面原生节点 const pageNode = unsafeWindow.getNativeNode(scriptNode); console.log(pageNode.test); // 输出 42
或者更简洁的方式,直接用unsafeWindow.eval生成转换函数:
const convertToPageNode = unsafeWindow.eval(`(node) => node`); const pageNode = convertToPageNode(scriptNode);
方法3:利用DOM方法的跨领域识别
页面环境的DOM方法(比如contains、compareDocumentPosition)可以识别沙箱里的节点,我们可以用这个特性来匹配:
function getPageNode(scriptNode) { const pageBody = unsafeWindow.document.body; // 遍历页面所有元素,找到和沙箱节点匹配的那个 // 注意:这种方法适合节点不多的场景,大量节点时性能差 const allElements = pageBody.querySelectorAll('*'); for (const el of allElements) { if (el.contains(scriptNode)) { // 如果页面元素包含沙箱节点,说明是同一个原生节点 return el; } } return null; }
内容的提问来源于stack exchange,提问作者dumbass
相关产品推荐
相关产品推荐

