Using YUI in Greasemonkey Scripts
January 3, 2007 at 8:04 am by Carlo Zottmann | In Development | 18 Comments
About the Author: Carlo Zottmann is a Market Engineer who works for Yahoo! in Munich, Germany. He spends his days integrating feeds from Yahoo!’s European content partners, helping develop new features for de.yahoo.com and fixing things. He’s usually employing Perl, PHP, Python or Javascript, or whatever the job requires.
Carlo has been blogging since 2001 and he blogs (mostly in English) at http://carlo.zottmann.org/; his blog is called “tail -f carlo.log”.
I love Greasemonkey. I like how much power it gives me when it comes to bending other peoples’ websites to my will, how I can add features to or or ditch them from a website, how I can use Greasemonkey scripts to pull data from all over the net to spice up the very page I am looking at. It makes my daily life as Yahoo! engineer easier.
Also, I love the Yahoo! UI library. YUI contains JavaScript and CSS components that allow anyone to quickly build some pretty amazing things.
Wouldn’t it be great if we could bring Greasemonkey and YUI together? How nice would it be to use YUI components anywhere, to have Greasemonkey dynamically load the libraries when needed and to attach YUI-powered thingamajigs to any page we like? For example to add autocompletion to form fields, or to make use of the advanced event management in your Greasemonkey scripts! The mind boggles.
In this brief article, I’ll share with you my own effort to reach that goal — a Greasemonkey script that adds calls to external JavaScript libraries and CSS files to a given page and, once they are loaded, passes the YAHOO global object to the code inside the Greasemonkey script. (All YUI components reside within this single single global variable, YAHOO — so, for example, you access the YUI Event Utility by referencing YAHOO.util.Event.) I’m sure that this approach is neither the perfect nor the only solution to achieving YUI/Greasemonkey integration, so suggestions and ideas are welcome! Please sound off in the comments and let me know what approaches you’ve taken to this problem in your own projects.
An Example Greasemonkey Script Implementing a YUI Loader and Using YUI Components
What I am interested in sharing with you here, primarily, is the mechanism by which you can include and invoke YUI from within a Greasemonkey script while reusing (and not disturbing) existing YUI components already present in the document. I’ll do that by exploring a simple Greasemonkey sample script that translates selected text on YUIBlog.com, Yahoo! News, or my personal blog using Yahoo! Babelfish; with the script installed, you can highlight any passage of text on one of those sites and, if you hold down the shift key while releasing the mouse, a YUI Panel with a German Babelfish translation will pop up. (If you want to install and test the script, you can download it from http://carlo.zottmann.org/code/yuigm_example_yuiblog_babelfish.user.js; the script is configured to operate only on http://*yuiblog.com/*, http://news.yahoo.com/*, and http://carlo.zottmann.org/* URIs).
In case you’re not using Firefox, here’s a quick example of the script in action. Click the screencapture below to see a 10-second QuickTime movie of the interaction:
Key Objects in the Script
We have four key objects in the script: GM_YUILOADER, GM_YUILOADER_CONFIG, YBFLOOKUP, and of course the YAHOO global object.
GM_YUILOADERholds all the logic to inject the necessary JavaScript and CSS files, makes sure they are loaded and triggers execution of the main part of the script (the “payload”).GM_YUILOADER_CONFIGcontains the configuration parameters for our YUI usage, including the list of YUI JavaScript libraries and/or CSS files we want to load, the maximum time to wait for for said files to complete loading, the frequency with which to check for completion, and information about which callback function to fire once everything is loaded.YBFLOOKUPis the “payload”, containing the code where we use YUI to achieve our goals. In our example this is the Babelfish YUI Panel (hence the name of the object) which will display a German translation for the English text on the page that was marked by the user.YAHOOis what you would expect — theYAHOOglobal object. It is avaible onceGM_YUILOADERhas triggered execution of the main part of the script.
The Loader
The loader is the most critical component of the script, and it’s the part that you are most likely to want to adapt in creating your own YUI-based Greasemonkey implementations. Here is the general workflow of the GM_YUILOADER technique.
- Greasemonkey triggers script execution.
GM_YUILOADER.loader()is called and…- adds a
GM_YUILOADER_DOCproperty to Greasemonkey’sunsafeWindow.documentwhich (among other things) holds a counter, a so-called trigger variable and a function (which increments aformentioned counter; if the counter reaches the number of included<script/>tags, the trigger variable is set to true) - adds new
<script src="..."/>and/or<link rel="stylesheet" type="text/css" href="..."/>tags to the page (unless that YUI component is already included in the page, which is determined using object detection) - adds
onLoadevent handlers to above<script/>tags (which call the
function insideunsafeWindow.document.GM_YUILOADER_DOC)
- adds a
GM_YUILOADER.loaderCheck()is run periodically, checking the status of the trigger variable, until one of two things happens: either the variable is true, in which case the payload logic is invoked (i.e.YBFLOOKUP.run()) after making theYAHOOglobal object available to the Greasemonkey script, or the maximum loading time is reached, which will cause the script to abort.
Let’s take a look at some of the loader-specific code in the sample script. First, you specify the YUI components on which your Greasemonkey script will rely. You do so in an structured object — the assets member of the GM_YUILOADER_CONFIG object:
// Settings used by the loader "engine"
var GM_YUILOADER_CONFIG = {
// List of JS libraries and CSS files to load. obj is used for the object
// detection used in the loader. Basically, if the object already exists,
// the script is not injected in the page.
assets: [
{ type: 'css', url: 'http://developer.yahoo.com/yui/build/container/assets/container.css' },
{ type: 'js', obj: 'YAHOO', url: 'http://us.js2.yimg.com/us.js.yimg.com/lib/common/utils/2/yahoo_2.1.0.js' },
{ type: 'js', obj: 'YAHOO.util.Event', url: 'http://us.js2.yimg.com/us.js.yimg.com/lib/common/utils/2/event_2.1.0.js' },
{ type: 'js', obj: 'YAHOO.util.Dom', url: 'http://us.js2.yimg.com/us.js.yimg.com/lib/common/utils/2/dom_2.1.0.js' },
{ type: 'js', obj: 'YAHOO.util.Anim', url: 'http://us.js2.yimg.com/us.js.yimg.com/lib/common/utils/2/animation_2.1.0.js' },
{ type: 'js', obj: 'YAHOO.widget.Panel', url: 'http://us.js2.yimg.com/us.js.yimg.com/lib/common/widgets/2/container/container_2.1.0.js' }
],
By comparing this list with the YUI objects that may already be present in the YAHOO global object, the script creates a “to-do” list of needed-but-missing components. It can then loop through the needed assets and include them on the page. Here’s the underlying code for that part of the loader:
// Now let's add the extra tags to the page that'll load the libraries and
// CSS files.
var numAssets = GM_YUILOADER_CONFIG.assets.length;
for (var a = 0; a < numAssets; a++) {
var tag;
var asset = GM_YUILOADER_CONFIG.assets[a];
switch (asset.type) {
// CSS file
case 'css':
tag = document.createElement('link');
tag.href = asset.url;
tag.type = 'text/css';
tag.rel = 'stylesheet';
break;
// Javascript library.
case 'js':
var injectScript = true;
// Object detection
try {
injectScript = eval('window.' + asset.obj + ' === undefined');
}
catch (e) {}
if (injectScript) {
tag = document.createElement('script');
tag.src = asset.url;
// The crucial part: triggering document.GM_YUILOADER.countLoaded()
// means keeping track whether all scripts are loaded yet.
tag.setAttribute('onload', 'document.GM_YUILOADER_DOC.countLoaded();');
// How many JS libraries are we dealing with again? Let's keep
// track.
ud.GM_YUILOADER_DOC.numberTotal++;
}
break;
}
document.body.appendChild(tag);
}
There are other details taken care of in the loader portion of the script, but this is the heart of the logic & and the code above captures the essence of this approach to marrying YUI with Greasemonkey.
The Payload
The practical part of the script (a.k.a. the "payload") is pretty straightforward: a simple, invisible YUI Panel is built, a mouseup event handler is attached to the document body. Once triggered, it'll check if text was selected and if the shift key is still pressed; if so, it'll grab the German translation for the text from Babelfish, put it in the body of the Panel and invoke the Panel's show() method.
At the heart of the payload is an event listener listening for the mouseup event on the window object. Here's the beginning of that event handler:
// Event handler for mouseUp events
YBFLOOKUP.subscriberSelect = function(e) {
var selection = window.getSelection();
var selectionText = selection.toString();
// Shift key pressed? Anything selected?
if (!e.shiftKey || selectionText == '') { return; }
YBFLOOKUP.panel.setBody('Loading Babelfish EN-DE translation, just a second...');
YBFLOOKUP.panel.cfg.setProperty('x', e.clientX + 20);
YBFLOOKUP.panel.cfg.setProperty('y', e.pageY + 20);
YBFLOOKUP.panel.show();
From there, the script proceeds to make a call to Greasemonkey's built in facility for loading external pages (GM_xmlhttpRequest), loads the translation from Yahoo! Babelfish, and shows the result in the Panel.
Closing Words
The ability to use YUI in Greasemonkey scripts can be quite beneficial to Greasemonkey developers. I know from personal experience that YUI brings a lot of new options and tools to the Greasemonkey playing field that, without a library, you would have to build on your own. Also I like the idea of playing with new YUI-powered gimmicks on a live site; for instance, it is rather easy now for me to to inject autocompletion into a form field on a live Yahoo! page just to see what it would look and how it would behave — without running the risk of destroying things and without having to set up a dedicated development environment, do exhaustive QA testing, or ask anyone for permission. Greasemonkey captures the essence of hacking and it opens up wonderful creative opportunities.
For me personally, YUI and Greasemonkey are a perfect fit, and I'd like to use this opportunity to thank both the Greasemonkey developers and the YUI crew for their ingenuity and willingness to share the love with us.
Share and extend: Bookmark with Yahoo! My Web | Bookmark with del.icio.us | digg it! | reddit!
18 Comments »
RSS feed for comments on this post. TrackBack URI
Leave a comment

Copyright © 2006-2010 Yahoo! Inc. All rights reserved. Privacy Policy - Terms of Service
Powered by WordPress on Yahoo! Web Hosting.


[...] The aforementioned tech article is available now on YUIblog.com! It’s explaining a way to use the excellent YUI library in Greasemonkey scripts. I’m sure that this approach is neither the perfect nor the only solution to achieving YUI/Greasemonkey integration, but it works for me and I thought sharing can’t hurt. [...]
Pingback by Using YUI In Greasemonkey Scripts | tail -f carlo.log — January 3, 2007 #
Wow! This is really quite an impressive feat, and it opens the doors for a lot of new scripts.
Does the date in your screencast imply you’ve been holding this from us for over seven months?!
Comment by Paul Irish — January 3, 2007 #
Thanks, Paul.
Yes, we’re quite secretive. ;) But no, this screenshot (and the movie) were taken on a dated-back mock-up page.
Comment by Carlo Zottmann — January 3, 2007 #
@Paul: Yes, we’re excited too about putting Carlo’s work to use. As for the screen grab, that was me — and, no, we would never hold out on you for seven months. Just mocked up the piece in an old template, that’s all. Regards, Eric
Comment by Eric Miraglia — January 3, 2007 #
This is brilliant work, Carlo. You’ve put together an elegant mechanism for bringing these two powerful tools together.
Beyond Greasemonkey, it strikes me that this sort of technique might be used to create JavaScript packages that could be easily integrated into a preexisting website. It’s more or less an `include_once` framework for JavaScript.
Comment by Mike West — January 3, 2007 #
Does this mean that you, Yahoo are OK with me and my users using the yui js files via the URLs you posted e.g http://us.js2.yimg.com/us.js.yimg.com/lib/common/utils/2/yahoo_2.1.0.js ?
Is there a doc on these URLs covering updates/versions etc?
Comment by Peter Herrmann — January 3, 2007 #
Peter,
No, the fact that Carlo used Yahoo-hosted versions of the files in his example does not mean that Yahoo is opening those files up for general use. You can certainly use them in Carlo’s script, of course, but in your own projects you should host YUI assets on your own server and use those filepaths as you adapt Carlo’s script.
Regards,
Eric
Comment by Eric Miraglia — January 3, 2007 #
@eric… That kind of goes ‘against the grain’ for greasemonkey scripts where typically a developer writes a script for use against a 3rd party site (hardly ever their own).
You should think hard about hosting YUI like AOL is doing for dojo. The growing cost of doing so would be commensurate with the growing benefit that Yahoo gains from the standardization on YUI as a platform library.
Comment by Peter Herrmann — January 3, 2007 #
Peter,
Thanks for the comment. Opening up hosting of YUI would benefit lots of developers — not just Greasemonkey developers — but it would be especially nice in the GM context, absolutely. I hear you loud and clear.
-Eric
Comment by Eric Miraglia — January 3, 2007 #
[...] You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your ownsite. [...]
Pingback by Internet Vibes » Blog Archive » Using YUI in GreaseMonkey Scripts — January 4, 2007 #
I had a go at this a while back and got distracted apparently. The issue I was grappling with was the use of the unsafeWindow – which as I understand it potentially opens a hole allowing a page author to gain access to the greater privileges Greasemonkey has on your machine.
I went down a rat-hole loading the files via GM_xmlhttprequest and trying to eval them (in the protected userscript scope). But I see now that even if that could work, the need to load CSS into the document puts you back where you (I) started.
I think the power of loading in a library like YUI (or Dojo via AOL’s CDN) is /huge/ and should drive the design of future versions of Greasemonkey if there’s not a safe and elegant way to do it now. I know the topic has been aired in the Greasemonkey mailing list.
Btw. I’m right there with you on prototyping and testing out new ideas on applications and pages. Even though I might in theory have access to those apps’ code, in practice its much easier (and cooler) this way.
Comment by Sam — January 5, 2007 #
For those reading this post Feb 2007, Yahoo does now host the yui files:
http://developer.yahoo.com/yui/articles/hosting/
Comment by miles — March 14, 2007 #
[...] Using YUI in Greasemonkey Scripts (Yahoo! User Interface Blog) Title says it all, interesting! (tags: api code webdev YUI yahoo javascript greasemonkey) [...]
Pingback by links for 2007-04-17 « Joost’s weblog — April 17, 2007 #
Hi,
I wanted to create a more generic version of this script (GM_includeOnce for example) which was able to be more secure (not use unsafeWindow) if required. Also I wanted to remove what I believe is un-necessary polling in this script.
Here is what I came up with (please tear it to bits/make it better):
try {test=GM_includeOnce;}
catch (err) {
GM_log(‘adding GM_includeOnce function’);
function GM_includeOnce(assets, callbackOnComplete, callbackOnTimeout, timeout, context) {
/*
GM_includeOnce(assets[, callbackOnComplete[, callbackOnTimeout[, timeout[, context]]]])
assets = [{url:'', existenceTest:func[, type: typeEnum]}] type is calculated from extension if not included
no callback is made without callbackOnComplete
default timeout is 10000 (10 seconds)
context is the object that the scripts are run on, defaults to this
callbackOnComplete is sent a closure style function as its only parameter which allows access to the variables
created in the scripts
*/
//private functions
var numAssets=assets.length, numJSAssets=0, numJSLoaded=0;
var timeoutTimer=setTimeout(callbackOnTimeout, timeout||10000);
var allScript=”, allCSS=”, importScript=”;
var asset, type, styleLink;
function accessFunc(localVar) {try{return eval(localVar);} catch (err) {}}
function allScriptsLoaded() {
GM_log(‘allScriptsLoaded’);
clearTimeout(timeoutTimer);
//it may be better to add these scripts immediately rather than compiling the big string in some circumstances
GM_log(‘compiling scripts’);
for (var a=0;a
Comment by Annesley Newholm — September 8, 2007 #
Hi,
The code in the last post didn’t come out too well! Here is the address of a GreaseMonkey script that uses the GM_IncludeOnce loader:
http://dharmafly.com/hackhud/hackhud.user.js
Enjoy!
Annesley
Comment by Annesley Newholm — September 13, 2007 #
[...] Dive Into Greasemonkey, which is really his Greasemonkey Hacks book online. Next step: Using YUI in GreaseMonkey Scripts. Then the outside-world website hacking will begin!!Powered by [...]
Pingback by Where Are The Wise Men? » Blog Archive » Mike Dives Into Greasemonkey — October 19, 2007 #
This does not work for me. Has something new in Greasemonkey or YUI broken this? I just get a regular text, but no panel formatting whatsoever.
Thanks.
Comment by Eric — February 15, 2008 #
eric, that might be due to the latest version of the GM extension having tightened its security up. it no longer lets you call the GM_ built in functions like gm_xmlhttprequest from within the unsafewindow scope.
Comment by binky — March 29, 2008 #