If you've ever wondered how to inject rich JavaScript frameworks like Prototype or Scriptaculous into otherwise boring old web pages, I'm happy to report that Greasemonkey can make it happen.
In this article, I aim to demystify the process of injecting JavaScript libraries into web pages via Greasemonkey as a step towards creating more interesting userscripts.
Though my focus will be on Prototype and Scriptaculous, the process laid out below should apply equally well to other popular JavaScript frameworks like dojo, Rico and jQuery.
Greasemonkey is a Firefox Extension designed to do exactly one thing: inject arbitrary JavaScript into web pages. That's it.
There is a great online book and reference guide for the extension called Dive Into Greasemonkey. This book covers most aspects of Greasemonkey from initial installation to troubleshooting complicated scripts.
Once you have Firefox and Greasemonkey installed, feel free to crack open your favorite text-editor and follow along! Or, if you're the kind of person that likes to skip right to the code, you'll find it in jimbojwshakeit
First, let's set up some supporting code:
// ==UserScript== // @name Jimbojw.com Shake-It! // @namespace http://jimbojw.com/ // @description Proof-of-concept injecting Scriptaculous // @include http://jimbojw.com/* // ==/UserScript== // Anonymous function wrapper (function() { })(); // end anonymous function wrapper
The ==UserScript== segment establishes the authorship and purpose of the script. I've named the script "Jimbojw.com Shake-It!", and specified that this script should apply to any page of this site.
The anonymous function wrapper is not technically necessary since GM will encapsulate userscripts inside its own wrapper behind the scenes. It does however act as a namespace of sorts, shielding other script segments that might come before or after it.
The next thing to do is inject the libraries. This should be the first thing inside the function wrapper.
var scripts = [ 'http://script.aculo.us/prototype.js', 'http://script.aculo.us/effects.js', 'http://script.aculo.us/controls.js' ]; for (i in scripts) { var script = document.createElement('script'); script.src = scripts[i]; document.getElementsByTagName('head')[0].appendChild(script); }
The preceding code tells Greasemonkey to create three <script> tags and attach them to the end of the document's <head> element. The src attributes refer sequentially to the list of scripts above. Essentially, this forces the browser to include the required libraries in the DOM window as though they were included in the original page.
Note however, that the classes, objects and methods defined in those scripts will not be immediately available in the Greasemonkey window. This distinction may seem subtle and inconsequential, but understanding it is key to gaining access to the remotely included libraries.
For more information on the differences between Greasemonky's "window" object and the DOM "window" object (aka "unsafeWindow" in Greasemonkey), see Pitfall #8 in the O'Reilly Network's Avoid Common Pitfalls in Greasemonkey.
Now that the libraries are slated to be included by the end of the window load, we can arrange to leverage them at the proper time. Here's the final component of the script, which should appear immediately below the previous code block:
window.addEventListener('load', function(event) { // Grab a reference to the Effect object which was loaded by the scriptaculous library earlier. Effect = unsafeWindow['Effect']; // Add a shake effect to the site title header when clicked. var div = document.getElementById('p-logo'); var h1 = div.getElementsByTagName('h1')[0]; h1.id = 'p-logo-h1'; h1.addEventListener('click', function(event) { Effect.Shake(h1.id); }, 'false'); }, 'false');
This code accomplishes the following:
The anonymous function attached to the onClick event executes a Scriptaculous Effect.Shake operation on the element.
The final resulting code is available in jimbojwshakeit.
After installing this userscript, you can see it in action by clicking the "Jimbojw.com" logo at the top of the page.
Though it's not terribly exciting, this example does illustrate the successful use of an object imported from a remote JavaScript library.
To see a more complex example, check out OpenGrok Autocomplete (install).
Utilizing Ajax calls, this userscript extends OpenGrok by providing autocompletion support. Specifically, as you start typing in a field, a drop-down box lists potential query strings.
Greasemonkey makes it possible to inject rich JavaScript frameworks directly into any web page. By using the <script> tag injection technique, one can effectively import classes, objects and methods defined in remotely hosted javascript libraries.
Oops. Excuse me being a bit lame. I'm pretty new
Of course one would rather use
unsafeWindow['document'].getElementById('logo');
instead to access objects in page's object space.
Great article, by the way. :)
--maxima 08:21, 16 March 2007 (MST)
Thanks maxima,
Yeah - the whole trick is that you have to execute your JavaScript inside the page, not just in "Greasemonkey space" in order to use JavaScript libraries.
It took me a good while to figure this out, which is why I decided to write it up as an article. I'm glad you found it helpful!
Thanks for reading,
--Jimbojw 12:49, 16 March 2007 (MST)
Hopefully this code comes through (one can usually count on markup taking over). If not, oh well.
I notice you use "document.getElementBy", but this annoys me, since one of the coolest things about PType is the $() function. Thus:
$ = unsafeWindow['window'].$; var div = document.getElementById('something-clickable'); div.addEventListener('click', function(event) { x = document.createElement('p'); x.innerHTML = "It is very greasy. You are likely to be eaten by a monkey."; $('content-main').appendChild(x); }, 'false');
Thanks for the article. It "unblocked" me. And for that, I am grateful.
--JRice 16:53, 27 April 2007 (MST)
Thanks JRice,
That's a good point about prototype's $ method - I hadn't considered using it instead of document.getElementById().
For now I think I'll keep the long way. The reason being that I really do intend to use GreaseMonkey's window's document, not the unsafeWindow's document, for the purpose of event attachement.
Thanks for the tip!
--Jimbojw 19:58, 30 April 2007 (MST)
This page is incredible. Thanks a lot. I have 2 additional questions:
--guillaume 02:02, 23 July 2007 (MST)
Hi Guillaume,
Regarding jQuery, it shouldn't be too hard to use in the same manner - just import the '$' and '$$' methods from unsafeWindow in the same manner as the article pulls the Effects object.
Regarding using 'local' files. Sure! That should work just fine. Instead of making <script> tags with src attributes pointing to the scripts, you could inline the entire files as strings and set the SCRIPT tags' innerHTML attributes to contain the contents.
This would make the Greasemonkey script larger of course, but it wouldn't abuse the bandwidth of the site hosting the files.
--Jimbojw 10:27, 23 July 2007 (MST)
So you've shown how to get a reference to an object. How do I get a reference to a class, so that I can create new instances of said class?
--DataSurfer 06:01, 2 December 2007 (MST)
Hello DataSurfer,
In JavaScript, classes are themselves objects, so the process of getting handles to them is the same.
So for example, if you have a class MyClass in the page, and you want to access it from Greasemonkey, you'd do:
var MyClass = unsafeWindow["MyClass"]; var myvar = new MyClass();
Hope this helps!
--Jimbojw 13:54, 3 December 2007 (MST)
I'm having some trouble at using Lightview with Greasemokey. Lightview needs Prototype and Scriptaculous but for some reason I can't use it. If I try to load "/scriptaculous.js?load=effects" I receive the message "Prototype is not defined" but if I try "/effects.js" I receive the message "Lightview requires Scriptaculous >= 1.8.1". I've tried a lot of variations but no success. ps.: Sorry, English is not my first language.
--Wesley 15:35, 15 May 2008 (MST)
I am just getting into greasemonkey, and have no desire to do it without prototype (I never want to write js without prototype again). So trying to get a hang of it, thanks for the article.
All of this aliasing unsafeWindow stuff to local stuff seems to be getting around 'security' in GreaseMonkey that is there for a reason--but I can't quite figure out what that reason is. Should I be concerned?
I _would_ like to alias $ as JRice suggests. Jimbojw, what is your reason for actually wanting to use the Greasemonkey window document for event attachment instead of the real 'unsafe' one? Maybe that would help me understand what the trade-offs are here.
--Jonathan Rochkind 15:23, 23 May 2008 (MST)
Hi Jonathan,
> I am just getting into greasemonkey, and have no desire to do it without prototype (I never want to write js without prototype again).
I completely understand. Coding JavaScript without any framework at all quickly becomes a chore for anything more complex than simple things.
If you haven't tried Mootools and jQuery yet, I'd highly recommend them. Each has its pros and cons compared to Prototype/Scriptaculous, and its worth while learning their differences.
> All of this aliasing unsafeWindow stuff to local stuff seems to be getting around 'security' in GreaseMonkey that is there for a reason--but I can't quite figure out what that reason is. Should I be concerned?
That's a fairly accurate observation. In the early days of Greasemonkey, there was no distinction between window and unsafeWindow. Malicious users discovered that by crafting their JavaScript in a particular way, they could trick Greasemonkey into running their site's script with elevated GM privs - somewhat akin to a process running in user space which figures out how to execute kernel-level commands in an operating system.
Since Greasemonkey gives you the power to perform cross-domain XMLHttpRequests using the GMxmlhttprequest() function, this meant that bad sites could silently steal information, or do things on your behalf if you happened to be logged into another site. For example, say you were logged into PayPal, a malicious site may exploit that to make a payment to themselvs, or they could just as easily steal your GMail address book. Things of that nature.
Later versions of Greasemonkey contained additional restrictions to make it harder for these kinds of sites to break into the GM secure domain, and at the same time made it harder for GM scripts to expose themselves to those kinds of attacks.
Whenever you access unsafeWindow, there's a chance that a malicious script may walk up the callstack and start executing code in the GM security realm. JavaScript is a very flexible language in the hands of an advanced user.
> Jimbojw, what is your reason for actually wanting to use the Greasemonkey window document for event attachment instead of the real 'unsafe' one? Maybe that would help me understand what the trade-offs are here.
As a personal rule, I try to do everything I possibly can from the Greasemonkey 'window' perspective since it's separate from the site-provided script in the 'unsafeWindow' object. That is, if you write a GM script which never touches unsafeWindow, you can be certain that the target site will not be able to walk up into the GM realm.
I only use unsafeWindow when I need to do something clever, like injecting a third party library. Of course, you could write your whole script to use the unsafeWindow object, at the risk of increased exposure to malicious sites crawling up into your execution bubble.
Hope this helps!
--Jimbojw 16:00, 26 May 2008 (MST)
I've been trying to get this working all day with very little luck. Has anyone actually used some prototype code with this technique? When I try to use Enumerable as follows, I get errors like 'iterator.bind' is not a function. How would I go about running code as if it's in a <script> tag on the page?
window.addEventListener('load', function(event) {
$$ = unsafeWindow['window'].$$;
$$('div').each(function(s){alert(s);});
}, 'false');
--Aaron 15:49, 12 June 2008 (MST)
This doesn't seem to work anymore with FF3 and GM. 'Effect is undefined'
--Hedge 13:46, 22 June 2008 (MST)
@Hedge: you have the change the urls of the scripts:
var scripts = [ 'http://script.aculo.us/prototype.js', 'http://script.aculo.us/effects.js', 'http://script.aculo.us/controls.js' ];
--sebastian 09:38, 7 August 2008 (MST)
What about a site that already has scriptaculous scripts included? Like script.aculo.us itself?
Try loading the page with these modifications to your user script:
and have a look at error console messages.
An idea on importing already loaded prototype js object extensions into greasemonkey object space?
--maxima 08:15, 16 March 2007 (MST)