Search code examples
javascriptnode.jsecmascript-6jsdomes6-proxy

Intercept calls to DOM API functions


I need to intercept calls to some DOM API functions and store their arguments as a side effect. For example, suppose that I'm interested in the functions getElementsByTagNameand getElementById. See example below:

"use strict";
const jsdom = require("jsdom");
let document = jsdom.jsdom("<html><head></head><body><div id='foo'><div></div></div></body></html>");
let cpool = {ids: [], tags: []};
let obj = document.getElementById("foo");
// --> cpool = {ids: ["foo"], tags: []}
obj.getElementsByTagName("div"); 
// --> cpool = {ids: ["foo"], tags: ["div"]}

One important note is that I'm using node.js and document object is implemented by the jsdom library. So far I tried to exploit ES6 Proxies to modify the behaviour of the aforementioned DOM functions.

That is how I tried to proxify document object to trap all method calls. I wonder if and how using this technique or some other one I can get the solution to my problem.

let documentProxy = new Proxy(document, {
    get(target, propKey, receiver) {
        return function (...args) {
            Reflect.apply(target, propKey, args);
            console.log(propKey + JSON.stringify(args));
            return result;
        };
    }
});    
documentProxy.getElementById("foo");
// --> getElementById["foo"]

Solution

  • If you want to intercept only calls to these two functions, you don't need to use Proxy. You can just store a copy of the original function, and override the function to which you want intercept calls with a function that saves the arguments and then calls the original function.

    const cpool = {ids: [], tags: []}
    
    ;(getElementsByTagNameCopy => {
      document.getElementsByTagName = tag => {
        cpool.tags.push(tag)
        return Reflect.apply(getElementsByTagNameCopy, document, [tag])
      }
    })(document.getElementsByTagName)
    
    ;(getElementsByTagNameCopy => {
      Element.prototype.getElementsByTagName = function(tag) {
        cpool.tags.push(tag)
        return Reflect.apply(getElementsByTagNameCopy, this, [tag])
      }
    })(Element.prototype.getElementsByTagName)
    
    ;(getElementByIdCopy => {
      document.getElementById = id => {
        cpool.ids.push(id)
        return Reflect.apply(getElementByIdCopy, document, [id])
      }
    })(document.getElementById)
    
    console.log(document.getElementsByTagName('body'))
    console.log(document.getElementById('whatever'))
    console.log(document.body.getElementsByTagName('div'))
    console.log(cpool)