Search code examples
javascriptdebuggingbrowsergetter-setterhost-object

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


Here is a use case:

Assume we have a web page that has an issue, which results in the page to be scrolled up on a mobile device at some point after DOMContentLoaded has fired.

We can legitimately assume there is something, which operates on document.documentElement.scrollTop (e.g. assigns it a value of 0).

Suppose we also know there are hundreds of places, where this can happen.

To debug the issue we can think of the following strategies:

  • check each event handler, which can set a value of 0 scrollTop, one by one

  • try to use debug function available in Chrome DevTools

  • Override a native scrollTop as:

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);

The issue with the second approach is that it doesn't work with native getters/setters.

The issue with the third approach is that even though now we can easily track what is assigning a value to the scrollTop property, we monkey patch a native getters/setters and risk to cause unnecessary side effects.

Hence the question: is there a more elegant solution to debug native getters and setters for web browser host objects (e.g. document, window, location, etc)?


Solution

  • Turns out it is possible to use debug function for set method on a scrollTop property descriptor.

    The code is as follows:

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

    After that, we'll automatically stop in any function that tries to set a value to scrollTop. If you need to automatically stop only on those functions which assign a value within a certain threshold (for example, between 0 and 500), you can easily do that too since debug function accepts a second argument (condition) where you can specify your condition logic.

    For example:

    // 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');

    Pros:

    • easy to use and doesn't require a lot of boilerplate code

    Cons:

    • you'll have to evaluate that js snippet above each time when you refresh your page

    Many thanks to Aleksey Kozyatinskiy (former Googler on DevTools team) for the detailed explanation.