UserScripts & Greasemonkey: calling a website's JavaScript functions

22,689

Solution 1

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);

Solution 2

Functions and variables declared in your GreaseMonkey scripts (and Chrome's user scripts) are kept separate from the ones declared by the web page, for obvious reasons. For GM scripts in Firefox, you can access global variables via unsafeWindow.

The best approach for safety and compatibility is to inject your functions into the page using a script element. I use the following snippet in my user scripts:

function addFunction(func, exec) {
  var script = document.createElement("script");
  script.textContent = "-" + func + (exec ? "()" : "");
  document.body.appendChild(script);
}

The "-" here makes certain the function is parsed as an expression so that exec can be used to immediately execute upon adding it. You call the function like so:

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

// Inject the function and execute it:
addFunction(myFunction, true);
Share:
22,689
sebastian
Author by

sebastian

Updated on April 29, 2020

Comments

  • sebastian
    sebastian about 4 years

    I'm creating a UserScript extension for Firefox & Chrome and I'm trying to use some of the code in the website's JavaScript, e.g.:

    function: myFunction(){
        return  Grooveshark.playNextSong();
    }
    

    The problem is when I test this code, Grooveshark is a null reference.

    I know there are other people who have done it:

    see BetterGrooveshark

    But I don't know why my simple extension can't call Grooveshark's JavaScript functions.

    Do I need to 'append' my script to the document in order for this to work?: document.document.body.appendChild(script);

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

    Thanks.

  • josh.trow
    josh.trow about 13 years
    Yowza! Do your hands hurt now?
  • Brock Adams
    Brock Adams about 13 years
    unsafeWindow is not available in Chrome; occasional use in FF-only scripts is fine.
  • Wayne
    Wayne about 13 years
    I don't know what you mean by "occasional use" being fine? You should only use unsafeWindow if you're positive that the script will execute only on pages you control or in which you have absolute trust. Any page that your expose unsafeWindow to has the potential to elevate its access privileges using the technique described here: groups.google.com/group/greasemonkey-dev/tree/browse_frm/thr‌​ead/…
  • Wayne
    Wayne about 13 years
    With my previous comment in mind, I've increased the warning on unsafeWindow. The text I chose -- "It should be avoided wherever possible." -- is directly from the Greasespot wiki. In fact, they think it's so serious that they put that text in all caps, so I think I've toned it down.
  • Brock Adams
    Brock Adams about 13 years
    Your new verbiage is good. The GM wiki is overly paranoid and lawyer-esque (which is probably for the best). It's like warning that you should never drive because you might die in a car accident. If you know your target page, the risk, of using unsafeWindow, is negligible.
  • PointedEars
    PointedEars over 11 years
    @MathiasBynens When I inspect with Developer Tools (Shift+Ctrl+i) the code of the user script I have just added in Chromium 21, my original code is wrapped into (function (unsafeWindow) { … })(window);. When I set a breakpoint anywhere in that script and inspect unsafeWindow, its value is identical to that of window (unsurprisingly). I have also observed that user scripts can access the document without workarounds. So ISTM that Chrome would not support unsafeWindow is a myth, and that no polyfills are necessary there.
  • Mathias Bynens
    Mathias Bynens over 11 years
    @PointedEars Recent versions of Chrome indeed support unsafeWindow.
  • stil
    stil almost 10 years
    How it come I can read global variables from page in my userscripts on the newest Greasemonkey 2.0? I can't understand how it could be sandboxed then?
  • Wayne
    Wayne almost 10 years
    I haven't investigated, but it's probably related to this: blog.mozilla.org/addons/2014/04/10/…
  • KurzedMetal
    KurzedMetal about 8 years
    Thank you freaking much. You summarized all that Greasemonkey wiki couldn't explain properly in a few paragraphs. I salute you fellow developer.
  • chwzr
    chwzr about 7 years
    the demo script is not working here. did they changed something or whats the error?