绕过现有的 MutationObserver 使用 Tampermonkey 插入 DOM 元素 [英] Working around existing MutationObserver to insert DOM elements with Tampermonkey
问题描述
我编写了一个 Tampermonkey 脚本,将一些 DOM 元素注入到我用于工作的站点中,从而使导航更容易.它不再有效,因为供应商引入了 MutationObserver 大概是为了阻止像我这样的人这样做.为什么,我不知道.我没有做任何邪恶的事情.
我知道这就是正在发生的事情,因为我可以看到我的 DOM 元素短暂地出现然后消失.它们不再在 DOM 树中,站点的 JS 包含 MutationObservers 的代码:
//这是一个大对象的属性值:函数(){var e = 这个如果(this._options.shouldContainFocus){this._observer = new MutationObserver(this.handleDOMMutation)for (var t = this._contextElement; t && 1 === t.nodeType && "BODY" !== t.tagName;) {var n = t.parentElement如果 (n) {this._parents.push(n)this.muteNode(n)Array.prototype.slice.call(n.childNodes).forEach(function(t) {e.hideNode(t)})}t = t.parentNode}}}//这就是 MutationObserver 所调用的this.handleDOMMutation = function(e) {e.forEach(function(e) {Array.from(e. addedNodes).forEach(function(e) {n.hideNode(e)})e.removedNodes.forEach(function(e) {var t = n._nodes.indexOf(e)t >= 0 &&n._nodes.splice(t, 1)})})}
所以我的问题是......如果有的话,我该如何解决这个问题?我所能想到的就是访问一些 MutationObservers 的全局列表,但从我可以阅读的内容来看,这似乎并不存在(直到今天我才听说过这个 API).观察者变量不是我可以通过 unsafeWindow
访问的变量,因此我无法调用 disconnect
方法.我已经阅读了 MutationObservers 上的文档,但找不到任何帮助我的东西.有什么建议吗?
正如评论中提到的,这里有一种在文档加载之前覆盖 window.MutationObserver
的可能方法.因为 MutationObserver
被作为构造函数调用,为了使站点的脚本不抛出错误,请确保让您的monkeypatched MutationObserver
可作为构造函数调用.然后,由于实例化的对象可以调用方法(如 observe
和 disconnect
),你可能会返回一个 Proxy
,当一个属性被访问(如 observer
),返回一个可调用的函数,但不做任何事情.例如:
const div = document.querySelector('div');window.MutationObserver = function() {return new Proxy({}, { get: () => () => null })};new MutationObserver(() => {console.log('看到一个突变');}).observe(div, { childList: true });div.appendChild(document.createElement('span')).textContent = 'bar';
foo
如果 window.MutationObserver
的初始重新分配发生在站点脚本运行之前,则对 new MutationObserver
的调用将返回不执行任何操作的观察者.(正如你在上面的代码中看到的,如果你删除 window.MutationObserver
的重新分配,你会看到 'saw a mutation'
记录,而在猴子补丁之后,什么都没有已记录.)
因此,将其转换为用户脚本,粘贴 window.MutationObserver
重新分配,并确保用户脚本在 document-start
处运行,以便脚本运行在站点的内置脚本运行之前.如果您还希望在自己的代码中使用 MutationObserver
,请在重新分配之前保存对 window.MutationObserver
的引用:
//==用户脚本==//@name 新用户脚本//@match https://somewhere//@run-at 文档开始//==/用户脚本==const oldMutationObserver = window.MutationObserver;window.MutationObserver = function() {return new Proxy({}, { get: () => () => null })};
如果您所在的站点依赖于 MutationObserver
属性和函数来返回合理的值(如 takeRecords()
),那么而不是返回一个 Proxy
什么都不做,你可能会返回一个代理,它可以访问 MutationObserver
的实际实例,当 observe
被调用时,观察一个 不同的元素(一个永远不会变异的):
//用户脚本代码:const oldMutationObserver = window.MutationObserver;const elementThatIsNeverMutated = document.createElement('div');window.MutationObserver = function(callback) {//永远不应该调用回调const 观察者 = 新的 oldMutationObserver(回调);返回新代理(观察者,{ 获取:(对象,道具)=> {if (prop === 'observe') return (targetNode, config) =>{obj.observe.call(obj, elementThatIsNeverMutated, config);};const val = obj[prop];返回 typeof val === '函数' ?val.bind(obj) : val;}})};//网站内置代码示例:const div = document.querySelector('div');const 观察者 = 新 MutationObserver(() => {console.log('看到一个突变');});观察者观察(div,{childList:true});div.appendChild(document.createElement('span')).textContent = 'bar';const arr = 观察者.takeRecords();arr.forEach(() => console.log('数组中的某个项目'));
foo
I wrote a Tampermonkey script to inject some DOM elements into a site I use for work, to make navigation easier. It no longer works because the vendor has introduced a MutationObserver presumably to stop people like me from doing this. Why, I don't know. I'm not doing anything nefarious.
I know this is what's happening because I can see my DOM elements briefly appearing and then disappearing. They aren't in the DOM tree any more and the site's JS contains code for MutationObservers:
// this is a property of a large object
value: function() {
var e = this
if (this._options.shouldContainFocus) {
this._observer = new MutationObserver(this.handleDOMMutation)
for (var t = this._contextElement; t && 1 === t.nodeType && "BODY" !== t.tagName;) {
var n = t.parentElement
if (n) {
this._parents.push(n)
this.muteNode(n)
Array.prototype.slice.call(n.childNodes).forEach(function(t) {
e.hideNode(t)
})
}
t = t.parentNode
}
}
}
// this is what is called by the MutationObserver
this.handleDOMMutation = function(e) {
e.forEach(function(e) {
Array.from(e.addedNodes).forEach(function(e) {
n.hideNode(e)
})
e.removedNodes.forEach(function(e) {
var t = n._nodes.indexOf(e)
t >= 0 && n._nodes.splice(t, 1)
})
})
}
So my question is... how can I get around this, if at all? All I can think of is to access some global list of MutationObservers but that doesn't appear to exist from what I can read (I hadn't heard of this API until today). The observer variable is not one I can access via unsafeWindow
so I can't call the disconnect
method. I've read the documentation on MutationObservers but can't find anything to help me. Any suggestions?
As mentioned in comments, here's a possible way to overwrite window.MutationObserver
before the document loads. Because MutationObserver
gets called as a constructor, in order for the site's scripts not to throw errors, make sure to let your monkeypatched MutationObserver
be callable as a constructor. Then, since the instantiated object can have methods called on it (like observe
and disconnect
), you might return a Proxy
which, when a property is accessed (like observer
), returns a function that is callable, but doesn't do anything. For example:
const div = document.querySelector('div');
window.MutationObserver = function() {
return new Proxy({}, { get: () => () => null })
};
new MutationObserver(() => {
console.log('saw a mutation');
}).observe(div, { childList: true });
div.appendChild(document.createElement('span')).textContent = 'bar';
<div>foo</div>
If the initial reassignment of window.MutationObserver
occurs before the site's scripts run, then calls to new MutationObserver
will return observers that don't do anything. (As you can see in the above code, if you remove the reassignment of window.MutationObserver
, you will see 'saw a mutation'
logged, whereas after the monkeypatching, nothing is logged.)
So, translating this to a userscript, paste in the window.MutationObserver
reassignment, and make sure that the userscript runs at document-start
, so that the script runs before the site's built-in scripts run. If you also wish to use MutationObserver
in your own code, save a reference to window.MutationObserver
before you reassign it:
// ==UserScript==
// @name New Userscript
// @match https://somewhere
// @run-at document-start
// ==/UserScript==
const oldMutationObserver = window.MutationObserver;
window.MutationObserver = function() {
return new Proxy({}, { get: () => () => null })
};
If the site you're on depends on MutationObserver
properties and functions to return sensible values (like takeRecords()
), then rather than returning a Proxy
that does nothing, you might return a Proxy that gives access to an actual instantiation of a MutationObserver
that, when observe
is called on it, observes a different element (one which is never mutated):
// Userscript code:
const oldMutationObserver = window.MutationObserver;
const elementThatIsNeverMutated = document.createElement('div');
window.MutationObserver = function(callback) { // callback should never be called
const observer = new oldMutationObserver(callback);
return new Proxy(observer, { get: (obj, prop) => {
if (prop === 'observe') return (targetNode, config) => {
obj.observe.call(obj, elementThatIsNeverMutated, config);
};
const val = obj[prop];
return typeof val === 'function' ? val.bind(obj) : val;
}})
};
// Example of site's built-in code:
const div = document.querySelector('div');
const observer = new MutationObserver(() => {
console.log('saw a mutation');
});
observer.observe(div, { childList: true });
div.appendChild(document.createElement('span')).textContent = 'bar';
const arr = observer.takeRecords();
arr.forEach(() => console.log('some item in array'));
<div>foo</div>
这篇关于绕过现有的 MutationObserver 使用 Tampermonkey 插入 DOM 元素的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!