如何在浏览器中跟踪主机对象的本地 getters/setters 的调用者?

How to track a caller for native getters / setters of host objects in a browser?

这是一个用例:

假设我们有一个网页有问题,导致页面在 DOMContentLoaded 触发后的某个时刻在移动设备上向上滚动。

我们可以合理地假设有一些东西在 document.documentElement.scrollTop 上运行(例如,给它赋值 0) .

假设我们也知道有数百个地方可能会发生这种情况。

为了调试问题,我们可以想到以下策略:

var scrollTopOwner = document.documentElement.__proto__.__proto__.__proto__;
var oldDescr = Object.getOwnPropertyDescriptor(scrollTopOwner, 'scrollTop');
Object.defineProperty(scrollTopOwner, '_oldScrollTop_', oldDescr);
Object.defineProperty(scrollTopOwner, 'scrollTop', {
  get:function(){
    return this._oldScrollTop_;
  },
  set: function(v) {
    debugger;
    this._oldScrollTop_ = v;
  }
});

function someMethodCausingAPageToScrollUp() {
  document.scrollingElement.scrollTop = 1e3;
}

setTimeout(someMethodCausingAPageToScrollUp, 1000);

第二种方法的问题是它不适用于本机 getters/setters。

第三种方法的问题在于,即使现在我们可以轻松跟踪为 scrollTop 属性 赋值的内容,我们还是猴子修补了原生 getters/setters 并有引起不必要的副作用的风险。

因此问题来了:是否有更优雅的解决方案来调试 Web 浏览器主机对象(例如文档、window、位置等)的本机 getter 和 setter?

const collector = [];
const originalScrollTop = Object.getOwnPropertyDescriptor(Element.prototype, 'scrollTop');
Object.defineProperty(Element.prototype, 'scrollTop', {
    get: () => originalScrollTop.get.call(document.scrollingElement),
    set: function(v) {
        collector.push((new Error()).stack)
        originalScrollTop.set.call(document.scrollingElement, v)
    }
})

您可以收集堆栈跟踪并稍后检查它们。我们不会立即记录它,以免造成 IO 延迟。 在 Chrome.

工作

事实证明,可以在 scrollTop 属性 描述符上为 set 方法使用 debug 函数。

代码如下:

debug(Object.getOwnPropertyDescriptor(Element.prototype, 'scrollTop').set);

之后,我们将自动停止任何试图将值设置为 scrollTop 的函数。 如果您只需要自动停止在某个阈值内(例如,0 到 500 之间)赋值的那些函数,您也可以轻松地做到这一点,因为 debug 函数接受第二个参数(条件),您可以在其中可以指定你的条件逻辑。

例如:

// In that case, we'll automatically stop only in those functions which assign scrollTop a value within a range of [1, 499] inclusively
debug(Object.getOwnPropertyDescriptor(Element.prototype, 'scrollTop').set, 'arguments[0] > 0 && arguments[0] < 500');

优点:

  • 易于使用,不需要大量样板代码

缺点:

  • 您每次刷新页面时都必须评估上面的 js 代码段

非常感谢 Aleksey Kozyatinskiy(DevTools 团队的前 Google 员工)的详细解释。