Javascript: Overriding XMLHttpRequest.open()

You are not modifying the open method inherited by XMLHttpRequest objects but just adding a method to the XMLHttpRequest constructor which is actually never used.

I tried this code in facebook and I was able to catch the requests:

(function() {
    var proxied = window.XMLHttpRequest.prototype.open;
    window.XMLHttpRequest.prototype.open = function() {
        console.log( arguments );
        return proxied.apply(this, [].slice.call(arguments));
    };
})();

/*
    ["POST", "/ajax/chat/buddy_list.php?__a=1", true]
    ["POST", "/ajax/apps/usage_update.php?__a=1", true]
    ["POST", "/ajax/chat/buddy_list.php?__a=1", true]
    ["POST", "/ajax/canvas_ticker.php?__a=1", true]
    ["POST", "/ajax/canvas_ticker.php?__a=1", true]
    ["POST", "/ajax/chat/buddy_list.php?__a=1", true]
*/

So yeah the open method needs to be added to XMLHttpRequest prototype (window.XMLHttpRequest.prototype) not XMLHttpRequest constructor (window.XMLHttpRequest)


Use XMLHttpRequest.prototype.open instead.


Here is the approach I like to take; mind you, mastering the dark arts of XHR monkey patch is a bit of an art form.

Wrap the whole kit and caboodle in an IIFE. So start off with something like the following:

(function(open, send) {
    //...overrides of the XHR open and send methods are now encapsulated within a closure
})(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send)

Any methods can be overridden using this general approach but the above scaffolding sets you up with a way to override (a.k.a monkey patch) both the open and send methods of XMLHttpRequest; in one neat utility function. Notice how the "base" methods (from the API's prototype object) are being fed into the IIFE and assigned to the var's "open" and "send", and safely scoped to the function block.

Now for the guts and what is key to persisting your monkey patch. Now again, this is how I do it and it works.

The general pattern (all within the confines of the IIFE) is to:

1) replicate the method and its arguments, (the signature, in its entirety, per spec/prototype),

2) slip in your mod's, and

3) apply your mod's to the XHR prototype property to ensure all XHR requests pass through your code.

So for example, "open" would look like:

XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
      xhrOpenRequestUrl = url;     // update request url, closure variable
      open.apply(this, arguments); // reset/reapply original open method
};

Don't get hung up on the xhrOpenRequestUrl = url; line, this code is copied from an example where I needed the url for later processing. The key takeaway is "open.apply", it cements your tweaks into the XHR open method, if you're not familiar with the "apply" method or the "arguments" object, then now is a good time to learn what they do.

And similarly for the "send" method...

XMLHttpRequest.prototype.send = function(data) {
  //...what ever code you need, i.e. capture response, etc.
  if (this.readyState == 4 && this.status >= 200 && this.status < 300) {
    xhrSendResponseUrl = this.responseURL;
    responseData = this.data;  // now you have the data, JSON or whatever, hehehe!
  }
  send.apply(this, arguments); // reset/reapply original send method
}

Again, the "apply" is critical and it must be done after all of your overrides. So putting it all together now...

(function(open, send) {

   // Closure/state var's
   var xhrOpenRequestUrl;  // captured in open override/monkey patch
   var xhrSendResponseUrl; // captured in send override/monkey patch
   var responseData;       // captured in send override/monkey patch

   //...overrides of the XHR open and send methods are now encapsulated within a closure

   XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
      xhrOpenRequestUrl = url;     // update request url, closure variable
      open.apply(this, arguments); // reset/reapply original open method
   };

   XMLHttpRequest.prototype.send = function(data) {

      //...what ever code you need, i.e. capture response, etc.
      if (this.readyState == 4 && this.status >= 200 && this.status < 300) {
         xhrSendResponseUrl = this.responseURL;
         responseData = this.data;  // now you have the data, JSON or whatever, hehehe!
      }
      send.apply(this, arguments); // reset/reapply original send method
   }

})(XMLHttpRequest.prototype.open, XMLHttpRequest.prototype.send)

Oh and one last thing, your monkey patch can, in turn, be monkey patched! To minimize this possibility the IIFE code should come after all of the other JS in the page. At least all JS that may be monkeying with XHR, but before any AJAX calls that you may be targeting. Also, and similarly, an XHR monkey patch can be injected via Chrome or Web Extension, and also override your override! HA!

Hope that helps!


I would give the xmlhttprequest project at google code a look. It's a pretty good example of properly overriding the XMLHttpRequest object. The source can be seen here.