用于同步通知的 MutationObserver 替代方案
Alternative to MutationObserver for synchronous notifications
我需要 DOM 的同步通知更改一个扩展功能的 la MutationEvents。然而,MutationEvents 已被弃用。 MutationObserver 的用途有限,因为它聚合更改并在更改完成后交付更改的方式。
所以,简单的问题。当前(2019)浏览器扩展是否可以同步通知元素样式更改?
除了您提到的那些,没有 API。唯一的附加方法是挂钩 Node.prototype.appendChild
,以及一些其他方法来改变 page context 中的 DOM。当然,您还必须挂钩 innerHTML/outerHTML 设置器之类的东西。
重新定义原型方法可能会破坏一些做类似低级事情的网站。
至少从理论上讲,所以请注意。
下面是截取了几个常用方法的简化内容脚本:
const eventId = chrome.runtime.id + Math.random().toString(36);
const script = document.createElement('script');
script.textContent = `(${eventId => {
let reportingEnabled = true;
// only simple data can be transferred, not DOM elements, not functions, etc.
const sendReport = detail => dispatchEvent(new CustomEvent(eventId, {detail}));
const makeHook = (name, fn) =>
function () {
if (reportingEnabled) sendReport({name, phase: 'pre'});
const res = fn.apply(this, arguments);
if (reportingEnabled) sendReport({name, phase: 'post'});
return res;
};
const {appendChild} = Node.prototype;
Node.prototype.appendChild =
Element.prototype.appendChild = makeHook('appendChild', appendChild);
const {append} = Element.prototype;
Element.prototype.append = makeHook('append', append);
const innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
innerHTML.set = makeHook('innerHTML', innerHTML.set);
Object.defineProperties(Element.prototype, {innerHTML});
}})('${eventId}')`;
document.documentElement.appendChild(script);
script.remove();
window.addEventListener(eventId, e => {
console.log(e.detail);
});
显然,您需要挂钩所有其他方法,如 removeChild、insertBefore 等。
DOM 元素无法通过消息传递从页面上下文传输到内容脚本。只有字符串、数字、布尔值、空值和 arrays/objects 等由此类类型组成的普通类型是可转移的。对于现有的 DOM 元素,有一个技巧:您可以传输其索引 [...document.getElementsByTagName('*')].indexOf(element)
,然后立即将其用作 document.getElementsByTagName('*')[index]
。对于 ShadowDOM 你必须制作一个递归索引器。
我需要 DOM 的同步通知更改一个扩展功能的 la MutationEvents。然而,MutationEvents 已被弃用。 MutationObserver 的用途有限,因为它聚合更改并在更改完成后交付更改的方式。
所以,简单的问题。当前(2019)浏览器扩展是否可以同步通知元素样式更改?
除了您提到的那些,没有 API。唯一的附加方法是挂钩 Node.prototype.appendChild
,以及一些其他方法来改变 page context 中的 DOM。当然,您还必须挂钩 innerHTML/outerHTML 设置器之类的东西。
重新定义原型方法可能会破坏一些做类似低级事情的网站。
至少从理论上讲,所以请注意。
下面是截取了几个常用方法的简化内容脚本:
const eventId = chrome.runtime.id + Math.random().toString(36);
const script = document.createElement('script');
script.textContent = `(${eventId => {
let reportingEnabled = true;
// only simple data can be transferred, not DOM elements, not functions, etc.
const sendReport = detail => dispatchEvent(new CustomEvent(eventId, {detail}));
const makeHook = (name, fn) =>
function () {
if (reportingEnabled) sendReport({name, phase: 'pre'});
const res = fn.apply(this, arguments);
if (reportingEnabled) sendReport({name, phase: 'post'});
return res;
};
const {appendChild} = Node.prototype;
Node.prototype.appendChild =
Element.prototype.appendChild = makeHook('appendChild', appendChild);
const {append} = Element.prototype;
Element.prototype.append = makeHook('append', append);
const innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
innerHTML.set = makeHook('innerHTML', innerHTML.set);
Object.defineProperties(Element.prototype, {innerHTML});
}})('${eventId}')`;
document.documentElement.appendChild(script);
script.remove();
window.addEventListener(eventId, e => {
console.log(e.detail);
});
显然,您需要挂钩所有其他方法,如 removeChild、insertBefore 等。
DOM 元素无法通过消息传递从页面上下文传输到内容脚本。只有字符串、数字、布尔值、空值和 arrays/objects 等由此类类型组成的普通类型是可转移的。对于现有的 DOM 元素,有一个技巧:您可以传输其索引 [...document.getElementsByTagName('*')].indexOf(element)
,然后立即将其用作 document.getElementsByTagName('*')[index]
。对于 ShadowDOM 你必须制作一个递归索引器。