How to use GM_xmlhttpRequest in Injected Code?
GM_
functions will not work in injected code because injected code runs in the target page's scope. If they did work there, then unscrupulous web-sites could also use the GM_
functions -- to do unspeakable evil.
The solutions, most preferable first:
-
Don't inject code. Much of the time, it really isn't necessary, and it always complicates things. Only inject code if you absolutely, positively need to use some of the javascript loaded by the target page.
For libraries like jQuery, you will get better performance using the
@require
directive (Firefox), or pasting-in the library code or using a custom manifest.json file to include it (Chrome).By not injecting code, you:
- Keep the ability to easily use
GM_
functions - Avoid or reduce the dependency on outside servers to deliver libraries.
- Avoid potential side effects and dependencies with/on the page's JS. (You could even use something like NoScript to completely disable the page's JS, while your script still runs.)
- Prevent malicious web sites from exploiting your script to gain access to the
GM_
functions.
- Keep the ability to easily use
Use the Tampermonkey extension (Chrome). This allows you to avoid script injection by providing better Greasemonkey emulation. You can use the
@require
directive and a more powerful/risky version ofunsafeWindow
than Chrome natively provides.Split your userscript code into injected parts -- which cannot use
GM_
functions -- and non-injected parts. Use messaging, polling, and/or a specific DOM node to communicate between the scopes.
If you really must use injected code, here's a sample script that shows how to do it:
// ==UserScript==
// @name _Fire GM_ function from injected code
// @include https://stackoverflow.com/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
/* Warning: Using @match versus @include can kill the Cross-domain ability of
GM_xmlhttpRequest! Bug?
*/
function InjectDemoCode ($) {
$("body").prepend ('<button id="gmCommDemo">Open the console and then click me.</button>');
$("#gmCommDemo").click ( function () {
//--- This next value could be from the page's or the injected-code's JS.
var fetchURL = "http://www.google.com/";
//--- Tag the message, in case there's more than one type flying about...
var messageTxt = JSON.stringify (["fetchURL", fetchURL])
window.postMessage (messageTxt, "*");
console.log ("Posting message");
} );
}
withPages_jQuery (InjectDemoCode);
//--- This code listens for the right kind of message and calls GM_xmlhttpRequest.
window.addEventListener ("message", receiveMessage, false);
function receiveMessage (event) {
var messageJSON;
try {
messageJSON = JSON.parse (event.data);
}
catch (zError) {
// Do nothing
}
console.log ("messageJSON:", messageJSON);
if ( ! messageJSON) return; //-- Message is not for us.
if (messageJSON[0] == "fetchURL") {
var fetchURL = messageJSON[1];
GM_xmlhttpRequest ( {
method: 'GET',
url: fetchURL,
onload: function (responseDetails) {
// DO ALL RESPONSE PROCESSING HERE...
console.log (
"GM_xmlhttpRequest() response is:\n",
responseDetails.responseText.substring (0, 80) + '...'
);
}
} );
}
}
function withPages_jQuery (NAMED_FunctionToRun) {
//--- Use named functions for clarity and debugging...
var funcText = NAMED_FunctionToRun.toString ();
var funcName = funcText.replace (/^function\s+(\w+)\s*\((.|\n|\r)+$/, "$1");
var script = document.createElement ("script");
script.textContent = funcText + "\n\n";
script.textContent += 'jQuery(document).ready( function () {' + funcName + '(jQuery);} );';
document.body.appendChild (script);
};