用户脚本如何获取已有 DOM 节点在页面 realm 中的对应对象?
结论
当用户脚本运行在 Firefox 的 Xray 隔离环境中,且脚本管理器暴露了相应接口时,可以通过已有节点的 wrappedJSObject 访问页面侧对象:
const pageNode = node.wrappedJSObject;
console.log(pageNode.test);
node 是用户脚本已经持有的节点,无需给它设置 id,也无需重新查找。wrappedJSObject 是 Firefox 提供的机制,并非标准 DOM API,不能作为所有浏览器和用户脚本管理器通用的转换方法。MDN:访问页面脚本对象
为什么同一个节点会读出不同的属性?
即使指向 DOM 树中的同一个节点,跨隔离边界访问时,JavaScript 看到的对象视图也可能不同。
在 Firefox 的 Xray 机制下,用户脚本可以访问节点原有的 DOM 接口,但页面额外添加的属性,也称为 expando 属性,会被过滤。因此,即使页面设置了 test = 42,用户脚本通过受保护的视图读取 test 时仍可能得到 undefined。
wrappedJSObject 会解除该对象的 Xray 过滤,使页面添加的属性可见。这是特定隔离机制的行为,不能仅凭“处于不同 realm”就推断所有环境都会如此。Firefox:Xray Vision
如何用已有节点读取页面属性?
假设页面已经执行了问题中的赋值代码,在支持 wrappedJSObject 的环境中可以这样处理:
const node = document.getElementById('node');
if (node === null) {
throw new Error('Target node was not found');
}
const pageNode = node.wrappedJSObject;
if (pageNode === undefined) {
throw new Error('wrappedJSObject is unavailable in this environment');
}
console.log(pageNode.test); // 页面已为该节点设置 test = 42 时,预期为 42
代码中的查找只是为了演示如何取得初始引用。如果已经通过事件、遍历或其他方式获得 node,直接使用该引用即可。
无需复制节点。cloneNode() 创建的是另一个节点,无法解决访问原节点页面侧属性的问题。
不支持 wrappedJSObject 时怎么办?
原问题没有说明浏览器、脚本管理器及沙箱配置,因此无法确定另一种对所有环境都成立的直接转换写法。
如果当前环境确实支持示例中的 unsafeWindow 访问方式,而且目标元素具有唯一 id,可以通过页面侧的 document 获取它:
const pageNode = unsafeWindow.document.getElementById('node');
if (pageNode !== null) {
console.log(pageNode.test);
}
Tampermonkey 将 unsafeWindow 定义为访问页面 window 的入口,但这不等于提供了任意节点的通用解包接口。Tampermonkey:unsafeWindow
按 id 重新查询也有局限:如果页面已经替换了元素,查询结果可能不是先前持有的节点。对于没有稳定定位方式的节点,需要根据管理器支持的页面执行模式或跨上下文桥接机制设计方案,不能直接套用 Firefox 的接口。
使用时注意什么?
读取前要确认页面已经完成属性赋值;解除隔离不会让尚未设置的属性自动出现。如果页面把旧节点替换成了新节点,还需要确认赋值和读取针对的是同一个节点。
解除 Xray 过滤后,页面定义的 getter、方法和原型修改也可能变得可见。访问应尽量限定在所需属性上,避免把用户脚本的特权对象暴露给页面。
备注:内容仅供参考。
