绕过现有的 MutationObserver 使用 Tampermonkey 插入 DOM 元素 [英] Working around existing MutationObserver to insert DOM elements with Tampermonkey

查看:32
本文介绍了绕过现有的 MutationObserver 使用 Tampermonkey 插入 DOM 元素的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个 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 可作为构造函数调用.然后,由于实例化的对象可以调用方法(如 observedisconnect),你可能会返回一个 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆