问题
I need synchronous notifications of DOM changes a la MutationEvents for an extension capability. MutationEvents, however, are deprecated. MutationObserver is limited in usefulness because of the way it aggregates changes and delivers them after the changes have been made.
So, simple question. Is synchronous notification of Element style changes possible in current (2019) browser extensions?
回答1:
There's no API other than those you've mentioned. The only additional approach is to hook Node.prototype.appendChild
, and a bunch of other methods to alter DOM in page context. Naturally you'll have to hook things like innerHTML/outerHTML setters as well.
Redefining prototype methods may break some sites that do similar low-level things.
Theoretically, at least, so be warned.
Here's a simplified content script that intercepts a few common methods:
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);
});
Obviously you'll need to hook all the other methods like removeChild, insertBefore, and so on.
DOM elements cannot be transferred via messaging from the page context to the content script. Only trivial types like strings, numbers, boolean, null, and arrays/objects that consist of such types are transferable. There's a trick though for an existing DOM element: you can transfer its index [...document.getElementsByTagName('*')].indexOf(element)
and then use it immediately as document.getElementsByTagName('*')[index]
. For ShadowDOM you'll have to make a recursive indexer.
来源:https://stackoverflow.com/questions/58635927/alternative-to-mutationobserver-for-synchronous-notifications