3

So my goal was to hijack all XMLHttpRequest responses and do something with them in my script's scope. But since I @grant something I had to use unsafeWindow couldn't do it easy way. (I don't know why, but I couldn't change unsafeWindow.XMLHttpRequest.prototype.open. There was no error, but XMLHttpRequest didn't work at all.) So I ended up with this piece of code:

// ==UserScript==
// @name        a
// @include     *
// @version     1
// @grant       GM_xmlhttpRequest
// ==/UserScript==

realProcess = function(xhr) {
    // do something
}

function hijackAjax(process) {
    if(typeof process != "function") {
        process = function(e){ console.log(e); };
    }
    window.addEventListener("hijack_ajax", function(event) {
        process(event.detail);
    }, false);
    function injection() {
        var open = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function() {
            this.addEventListener("load", function() {
                window.dispatchEvent(new CustomEvent("hijack_ajax", {detail: this}));
            }, false);
            open.apply(this, arguments);
        };
    }
    window.setTimeout("(" + injection.toString() + ")()", 0);
}
hijackAjax(realProcess);

My question is: can it be done better/faster/more elegant?

EDIT: Updated code according to hints.

Tithen-Firion
  • 567
  • 5
  • 17

1 Answers1

3

The solution depends on how you want to do interact. You could inject all the logic into the site's scope, however, there is a barrier between userscript's scope and site's scope if you want to interact. It is not trivial to exchange data between both scopes. unsafeWindow is evil, avoid usage. Inject the logic as string to be evaluatet inside a setTimeout(String), so that it is executed in site's scope. If you need to interact with the userscript in privileged scope, setup a messaging system with window.dispatchEvent(new CustomEvent('eventname', {'detail': scalarData}));. You won't be able to transfer complex datatype declared in a scope with other privileges.

Pinke Helga
  • 6,378
  • 2
  • 22
  • 42
  • Wonderful! You can send whole xhr object in `CustomEvent`, unlike in `window.postMessage`. Is there any advantage of injecting code with `setTimeout(String)` instead of appending ` – Tithen-Firion Apr 05 '16 at 23:25
  • I'm not 100% sure how the browser handles a script appended from GM's context, when privileged APIs are granted. Execution of a stringified code by `setTimeout` is one way to declare something in site's context. You can test it by running a page that tries to execute your injected code. If it works I can't see any disadvandage, except that it is harder for a page to find some manipulation when you execute code by `setTimeout` within `(function(){ ... })()`, e.g. a game site that disallows userscripts. – Pinke Helga Apr 07 '16 at 16:14
  • Check if your manipulations on the xhr object from GM are still present in the site's context when `dispatchEvent` returns. Otherwise you might have to transfer scalar message data that tells the function running in site's context what to do, e.g. a JSON string so the object can be rebuilt in site's context. The site is not allowed to access data or execute code that was declared in GM's context when the GM script was granted any privilege. – Pinke Helga Apr 07 '16 at 16:31
  • I'll stick to `setTimeout` then. :) After modifying my script I was able to set request headers, read response headers, text, status, etc. and even abort xhr. Should I post it as new answer or just edit question? – Tithen-Firion Apr 08 '16 at 01:50