2

I want to override XMLHttpRequest's send in order to let my userscript know when data is getting updated on a page through such a request. My override code looks like this:

var oldSend = unsafeWindow.XMLHttpRequest.prototype.send;

unsafeWindow.XMLHttpRequest.prototype.send = function(){
    console.log("notified of XHR update");
    oldSend.apply(this, arguments);
}

If I inject this into the page (w/o the unsafeWindow references) it works fine, but I'd like to get this working from userscript scope. unsafeWindow works for this in Firefox, but not in Chrome. So I grabbed Brock Adams' nifty trick to create a working unsafeWindow in Chrome:

var bGreasemonkeyServiceDefined     = false;

try {
    if (typeof Components.interfaces.gmIGreasemonkeyService === "object") {
        bGreasemonkeyServiceDefined = true;
    }
}
catch (err) {
    //Ignore.
}

if ( typeof unsafeWindow === "undefined"  ||  ! bGreasemonkeyServiceDefined) {
    unsafeWindow    = ( function () {
        var dummyElem   = document.createElement('p');
        dummyElem.setAttribute ('onclick', 'return window;');
        return dummyElem.onclick ();
    } ) ();
}

However, when I combine the two nothing happens. It all works pasted into the console, but there's neither error nor output when running this from a userscript. Am I doing something wrong or perhaps this is beyond the capabilities of this trick?

Hmm, just tried something simpler, like: unsafeWindow.document.title = 'testing'; and that doesn't work either, so maybe it's not specific to XMLHttpRequest.

I'm trying to avoid injection into the page if at all possible.

Community
  • 1
  • 1
mix
  • 6,943
  • 15
  • 61
  • 90

1 Answers1

1

This:

/*--- Create a proper unsafeWindow object on browsers where it doesn't exist
    (Chrome, mainly).
    Chrome now defines unsafeWindow, but does not give it the same access to
    a page's javascript that a properly unsafe, unsafeWindow has.
    This code remedies that.
*/
var bGreasemonkeyServiceDefined     = false;

try {
    if (typeof Components.interfaces.gmIGreasemonkeyService === "object") {
        bGreasemonkeyServiceDefined = true;
    }
}
catch (err) {
    //Ignore.
}

if ( typeof unsafeWindow === "undefined"  ||  ! bGreasemonkeyServiceDefined) {
    unsafeWindow    = ( function () {
        var dummyElem   = document.createElement('p');
        dummyElem.setAttribute ('onclick', 'return window;');
        return dummyElem.onclick ();
    } ) ();
}

followed by this:

unsafeWindow.document.title = 'testing';

Works just fine from my test userscripts.

These also work following the unsafeWindow trick:

unsafeWindow.foo = function () {
    console.log ("In foo().");
};

unsafeWindow.alert = function (s) {
    console.log ("Alert: ", s);
};

(On a page where the script has run, entering foo() in the console yields: "In foo().". alert() does not generate a popup but prints to the console.)

I don't know why (yet) that overriding XMLHttpRequest.prototype.send doesn't work like that from a Chrome userscript, but I don't recommend the unsafeWindow approach for that anyway.

Inject the override code. Use postMessage() (which works on Chrome as well) to communicate between the page scope and the script scope, if you don't (or can't) inject the whole script.

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • brock, thx for the `postMessage()` suggestion. i was trying to avoid injecting the code, partially in response to having read your pros/cons list on the subject (http://stackoverflow.com/a/10828021/348496) --- mostly to protect the script from getting shutoff or blocked by the host site. sounds like you recommend injection regardless? i'll look at `postMessage()` and also reconsider whether i should just inject it all. – mix Jun 19 '12 at 06:24
  • 1
    For Chrome, and this kind of thing, injection is best. `unsafeWindow` can actually get blocked just as easily as injected JS can, but you always have the upper hand, in the end, over the site. However, `unsafeWindow` opens a potential channel for malicious sites to gain elevated privileges. Injection does not. ... Personally, I've yet to need any but the most trivial access (and seldom that) to a page's JS. Most practical cases where people want a script to intercept AJAX, are better served just by polling for changed content. That can remain entirely in the safe sandbox. – Brock Adams Jun 19 '12 at 07:14
  • if I could make things work without any injected JS at all, is that still the best option? or is it not worth the extra hassle (debugging, etc.)? – mix Jun 19 '12 at 07:52
  • 1
    Yes, avoiding injection (and unsafeWindow) AMAP, is always best (faster, more robust, more secure). But avoiding that is not always possible. If you ***truly*** need to intercept AJAX, for example, you will have to inject at least some code. – Brock Adams Jun 19 '12 at 08:24