Search code examples
xmlhttprequestsandboxinterceptorrestrictiongreasemonkey-4

Intercept XMLHttpRequest from Greasemonkey script fails


I seek to manipulate the XMLHttpRequest done by a website using Greasemonkey (version 4.9 installed). Interception should be simple (How can I intercept XMLHttpRequests from a Greasemonkey script?) but does not work for me. Maybe things changed with newer versions of Greasemonkey?

I obviously tried the examples in the linked question, but they don't have any effect - nothing printed in the console although I have an console.log(...) in my customised open function.

Next, I gave unsafeWindow a try. It should not be needed. My userscript runs with @grant none and documentation (see here) says my script should run in the content scope.

With unsafeWindow I get an effect but it breaks XMLHttpRequest completely

// ==UserScript==
// @name          Test
// @version       1
// @include       *
// @run-at        document-start
// @grant         none
// ==/UserScript==

"use strict";

let realOpen = unsafeWindow.XMLHttpRequest.prototype.open

console.log("Real: " + realOpen)

unsafeWindow.XMLHttpRequest.prototype.open = function() {
  console.log("Called for " + this + " with URL: " + arguments[0])

  //call original
  return realOpen.apply(this, arguments)
};

window.addEventListener ("load", function() {
  console.log ("Page loaded");
});

console.log("Unsafe: ", unsafeWindow.XMLHttpRequest.prototype.open.toString())
console.log("Normal: ", XMLHttpRequest.prototype.open.toString())

This gives following output in console:

Real: function open() {
    [native code]
}

Unsafe:  function() {
    console.log("Called for " + this + " with URL: " + arguments[0])

    //call original
    return realOpen.apply(this, arguments)
  }

Normal:  function open(method, url) {
    // only include method and url parameters so the function length is set properly
    if (arguments.length >= 2) {
      let newUrl = new URL(arguments[1], document.location.href);
      arguments[1] = newUrl.toString();
    }
    return origOpen.apply(this, arguments);
  }

==> Page loaded

As mentioned, function of XMLHttpRequest is broken. When I use the Firefox developer console to have a further look I get this

>> window.XMLHttpRequest.prototype.open
Restricted {  }

Any properties set on window (like window.foobar = "foobar") do not exist in console, but those set on unsafeWindow do. I assume this has to do with Greasemonkey's sandboxing.

Why are there two versions of XMLHttpRequest even when I use @grant none? Why is my custom function restricted? Can I avoid that? Why does it work without problems when I install event listener on window?


Solution

  • Next, I gave unsafeWindow a try. It should not be needed. My userscript runs with @grant none and documentation (see here) says my script should run in the content scope.

    This is wrong for Greasemonkey 4 as stated in its announcement:

    Due to the more limited abilities that the new extension system gives us, we are currently unable to make @grant none scripts work in the same way. Most importantly, they have a different connection to unsafeWindow. For the short term at least, it's a good idea to adopt cloneInto and exportFunction.

    See also this other question Firefox doesn't respect Object.defineProperty() from a Greasemonkey script?

    This change explain the observations, but no idea why adding listener to window work.