Injecting Js Functions into the Page from a Greasemonkey Script on Chrome

Injecting JS functions into the page from a Greasemonkey script on Chrome

The only way to communicate with the code running on the page in Chrome is through the DOM, so you'll have to use a hack like inserting a <script> tag with your code. Note that this may prove buggy if your script needs to run before everything else on the page.

EDIT: Here's how the Nice Alert extension does this:

function main () {
// ...
window.alert = function() {/* ... */};
// ...
}

var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ main +')();'));
(document.body || document.head || document.documentElement).appendChild(script);

UserScripts & Greasemonkey: calling a website's JavaScript functions

Background

Doesn't Greasemonkey inject my extensions JavaScript already? Can someone clarify this for me please.

Greasemonkey executes your scripts in a sandbox, which is a restricted environment without direct access to the JavaScript in the page. Earlier versions of Greasemonkey injected scripts directly into the page, but this introduced serious security vulnerabilities. In the old model, scripts ran with the elevated rights of the browser chrome, which allowed remote pages to access Greasemonkey's built-in functions using some clever JavaScript. This was bad:

Greasemonkey scripts contained their own GM_xmlhttprequest object which, unlike a normal xmlttprequest object, could access any local files one one's computer or make arbitrary requests to arbitrary sites without regard for the same origin policy that typically applies to xmlhttprequest. (source)

When you access the window object from a Greasemonkey script today, what you get is a wrapper object that indirectly references the actual window's properties. This wrapper object can be modified safely, but has important limitations. Access to the actual window object is provided by unsafeWindow (shorthand for window.wrappedJSObject). Use of unsafeWindow re-opens all of Greasemonkey's original security problems and isn't available in Chrome. It should be avoided wherever possible.

The good news: there are at least two ways to work with Greasemonkey's new security model in a safe way.

Script Injection

Now that Greasemonkey scripts can safely access the DOM, it's trivial to inject a <script> tag into the <head> of the target document. Create a function like this:

function exec(fn) {
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.textContent = '(' + fn + ')();';
document.body.appendChild(script); // run the script
document.body.removeChild(script); // clean up
}

It's simple to use:

exec(function() {
return Grooveshark.playNextSong();
});

Location Hack

Script injection may be overkill in some cases, especially when all you need is to modify the value of a variable in the page or execute a single function. The Location Hack leverages javascript: URLs to access code in the document's content. It's a lot like running a bookmarklet from within a Greasemonkey script.

location.assign("javascript:Grooveshark.playNextSong();void(0)");

Bonus Script

Here's a complete Greasemonkey script that demonstrates the examples above. You can run it on this page.

// ==UserScript==
// @name Content Function Test
// @namespace lwburk
// @include http://stackoverflow.com/questions/5006460/userscripts-greasemonkey-calling-a-websites-javascript-functions
// ==/UserScript==

function exec(fn) {
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.textContent = '(' + fn + ')();';
document.body.appendChild(script); // run the script
document.body.removeChild(script); // clean up
}

window.addEventListener("load", function() {
// script injection
exec(function() {
// alerts true if you're registered with Stack Overflow
alert('registered? ' + isRegistered);
});
// location hack
location.assign("javascript:alert('registered? ' + isRegistered);void(0)");
}, false);

Replacing page functions via userscript in Chrome?

In the end, the only technique I found which worked in Chrome was to create a <script> node using DOM methods. Fortunately, this seems to work equally well in Firefox and Opera and is made fairly painless by <func>.toString():

function embed() {
var oldFunc = window.func;

window.func = function() {
oldFunc();

// other stuff
};
}

var inject = document.createElement("script");

inject.setAttribute("type", "text/javascript");
inject.appendChild(document.createTextNode("(" + embed + ")()"));

document.body.appendChild(inject);

Final script: http://userscripts.org/scripts/review/84394

How can I get my cross-domain Greasemonkey script to work in Chrome, even though it requires jQuery and Greasemonkey API functions?

Using Tampermonkey instead of opening it as a extension works

Accessing Variables from Greasemonkey to Page & vice versa

  • Greasemonkey scripts operate in a separate scope and may also operate in a sandbox, depending on the @grant settings.

  • Additionally, the question code isolates greasy in a function scope (as gladoscc said).

  • Finally, by default, test.js will fire before the Greasemonkey script does, so it won't see any set variables, anyway. Use @run-at document-start to address that.


So, given this test.js, run right before </body>:

window.targetPages_GlobalVar = 'stovetop';

console.log ("On target page, local global: ", targetPages_GlobalVar);
console.log ("On target page, script global: ", gmScripts_GlobalVar);

Then the following will work:

No sandbox:

// ==UserScript==
// @name _Greasemonkey and target page, variable interaction
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @include http://output.jsbin.com/esikut/*
// @run-at document-start
// @grant none
// ==/UserScript==

//--- For @grant none, could also use window. instead of unsafeWindow.
unsafeWindow.gmScripts_GlobalVar = 'greasy';

console.log ("In GM script, local global: ", unsafeWindow.targetPages_GlobalVar);
console.log ("In GM script, script global: ", gmScripts_GlobalVar);

window.addEventListener ("DOMContentLoaded", function() {
console.log ("In GM script, local global, after ready: ", unsafeWindow.targetPages_GlobalVar);
}, false);


With sandbox, no function scope, unsafeWindow:
==> Important update: Greasemonkey changed unsafeWindow handling with version 2.0, the next sample script will not work with GM 2.0 or later. The other two solutions still work.

// ==UserScript==
// @name _Greasemonkey and target page, variable interaction
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @include http://output.jsbin.com/esikut/*
// @run-at document-start
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/

unsafeWindow.gmScripts_GlobalVar = 'greasy';

console.log ("In GM script, local global: ", unsafeWindow.targetPages_GlobalVar);
console.log ("In GM script, script global: ", unsafeWindow.gmScripts_GlobalVar);

window.addEventListener ("DOMContentLoaded", function() {
console.log ("In GM script, local global, after ready: ", unsafeWindow.targetPages_GlobalVar);
}, false);


With sandbox, no function scope, Script Injection:

// ==UserScript==
// @name _Greasemonkey and target page, variable interaction
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @include http://output.jsbin.com/esikut/*
// @run-at document-start
// @grant GM_addStyle
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/

function GM_main () {
window.gmScripts_GlobalVar = 'greasy';

console.log ("In GM script, local global: ", window.targetPages_GlobalVar);
console.log ("In GM script, script global: ", window.gmScripts_GlobalVar);

window.addEventListener ("DOMContentLoaded", function() {
console.log ("In GM script, local global, after ready: ", window.targetPages_GlobalVar);
}, false);
}

addJS_Node (null, null, GM_main);

function addJS_Node (text, s_URL, funcToRun, runOnLoad) {
var D = document;
var scriptNode = D.createElement ('script');
if (runOnLoad) {
scriptNode.addEventListener ("load", runOnLoad, false);
}
scriptNode.type = "text/javascript";
if (text) scriptNode.textContent = text;
if (s_URL) scriptNode.src = s_URL;
if (funcToRun) scriptNode.textContent = '(' + funcToRun.toString() + ')()';

var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
targ.appendChild (scriptNode);
}

Notes:

  1. You can test these script against this page (output.jsbin.com/esikut/1).
  2. With no sandbox, unsafeWindow and window are the same.
  3. All of these scripts produce the same output on the console:

    In GM script, local global: undefined
    In GM script, script global: greasy
    On target page, local global: stovetop
    On target page, script global: greasy
    In GM script, local global, after ready: stovetop
  4. The Script Injection code will work in a variety of browsers besides Firefox. unsafeWindow currently only works in Firefox+Greasemonkey(or Scriptish) or Chrome+Tampermonkey.

How to control a Tampermonkey script's function from the browser's JS console?

Greasemonkey, and Tampermonkey, operate in separate scopes from the target page, and may use a sandbox as well.

The target page and JS console can not see variables and functions defined in the script scope, but your script can inject stuff into the target page's scope.

So, place your on variable in the target page scope, and you can then control that function from the console. In this case, use unsafeWindowDoc to do that.

A complete Greasemonkey and Tampermonkey script that does that is:

// ==UserScript==
// @name _Allow console control of Tampermonkey function
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @grant unsafeWindow
// ==/UserScript==

unsafeWindow.on = true;

function doSomething () {
if (unsafeWindow.on){
//do stuff
}
}
setInterval (doSomething, 1000);

From the console, omit the unsafeWindow. That is, you would use:

on = false;
// OR
on = true;

to stop and start that script's action.


Note that, in Chrome, the script must be running in Tampermonkey, not Chrome's native userscript emulation.



Related Topics



Leave a reply



Submit