cross-browser Greasemonkey scripts

May 18, 2010 1:37 am | 4 Comments

I love customizing web apps and browser behavior. I want my web my way! So bookmarklets, Greasemonkey scripts, and browser add-ons are some of my favorite things to work on. When searching for the right implementation I approach the problem in that same order:

  • bookmarklets - If I can accomplish my goals with a bookmarklet, I stop there. This means it works cross-browser without installing a plugin.
  • Greasemonkey scripts – If I want my custom behavior to happen automatically, I step up to Greasemonkey. Besides automatically launching my script, there are some other extras that come with Greasemonkey such as cross-site XHR and menu manipulation. Checkout the Greasemonkey API for a full list of functions. But API support, and support for Greasemonkey itself, varies by browser (we’ll get to that in a minute).
  • Browser add-ons – Browser add-ons have the most power, access, and UI control. But they only work on a single browser. (At least until someone ports Jetpack to more browsers.) The implementation stack varies by browser (lowers likelihood of reusing code). Installation can be cumbersome for users, and hosting is harder for developers. Updates are more automated, which is nice.

Greasemonkey covers a nice middle ground. Easy to develop and more features. Up until recently, though, I thought of Greasemonkey scripts as being only for Firefox. That’s no longer the case.

Greasemonkey was created by Aaron Boodman for Firefox back in 2005. He works on Chrome now, so it was awesome when Aaron announced that Greasemonkey scripts are supported in Chrome. When I started researching it this week, I was surprised to find out that Opera supports Greasemonkey scripts (aka, “user scripts” and “user JavaScript”) starting back with Opera 8. So Firefox supports Greasemonkey scripts through the original Greasemonkey add-on. Chrome and Opera have built-in support for Greasemonkey scripts.

Safari and Internet Explorer don’t have built-in support for Greasemonkey scripts. And they also don’t have (what I would call) solid add-on support for Greasemonkey scripts. I read that in Safari you can use SIMBL and Greasekit to make Greasemonkey scripts work. In IE, Trixie is suggested. In this post I’m going to focus on Firefox, Chrome, and Opera, but I’d appreciate comments about people’s experiences in Safari and IE with these or other plugins that give Greasemonkey support.

my example: TwitterHistory.user.js

I mostly read Twitter in Firefox on my laptop. One feature I’ve wanted forever is an indicator of the last tweet I’ve read, so I can quickly see how much new stuff there is. Ditto for mentions. So I wrote a Greasemonkey script that implemented these features: twitterhistory.user.js. Now my Twitter looks like this:

Tweets that I’ve already read are grayed out, and there’s a thick gray bar dividing the read from the unread. The key features needed are:

  • remember the newest-viewed-tweet - All tweets have an ascending number in their id, so all I need to do is save the id of the first tweet in the list.
  • gray out the tweets – The next time the user comes to the page, since I know the number of the newest-viewed-tweet I can iterate over all the visible tweets and gray out the ones with a lower number.
  • add a handler to various navigation links – A tricky part is figuring out when to remember the newest-viewed-tweet, and when to gray out the visible tweets. Doing this at page transition is obvious – gray out the already read tweets when the page loads, and save the newest-viewed-tweet when the page unloads. But Twitter is very Ajaxified. For example, clicking the “more” link at the bottom adds more tweets that might need to be grayed out. And clicking the “Home” and “@souders” navigation links causes the list of tweets to change, which means saving the newest tweet number and graying out the new list needs to happen. To achieve this I add an onclick handler to those links.

Getting the Code to Work

Let’s look at how these features get implemented as a Greasemonkey script that works across Firefox, Chrome, and Opera.

Firefox

The Greasemonkey add-on for Firefox provides an API that includes GM_setValue and GM_getValue. These work great to save and retrieve the ID of the newest-viewed-tweet. Graying out tweets was a simple matter of iterating over the items in a list and comparing each id to the newest-viewed-tweet value. Adding the handlers was tricky. In order for these callbacks to persist from the Greasemonkey script to the main page’s event loop, I had to use the unsafeWindow variable from Greasemonkey. Without this, the handlers don’t work, as demonstrated in my Greasemonkey Test Page. This took less than an hour to code up.

Chrome

I had never built a Greasemonkey script for Chrome before, so I had some learning to do. First off, there’s no Greasemonkey API in Chrome. Therefore, unsafeWindow doesn’t exist, but it turns out using window works just fine (as shown in the Greasemonkey Test Page). So I defined a proxy variable that refers to unsafeWindow if it exists, otherwise window. One stumble I had here was trying to detect programmatically if GM_setValue and GM_getValue are defined. It turns out they are defined in Chrome! But they don’t do anything:

function() {
    console.log("%s is not supported.", api);
}

Since GM_setValue and GM_getValue don’t work, I fallback on localStorage (see Greasemonkey API emulation for Chrome). If localStorage doesn’t exist, I use cookies (a la PPK). Two browsers down. One to go.

Opera

The main trick with Opera was figuring out how to install the scripts. I talk about that more under Installation. Once I figured that out, there was only one issue to resolve and it had nothing to do with Greasemonkey. In the case of Opera, I need to use onunload instead of onbeforeunload.

Installation

Installing Greasemonkey scripts is easiest in Chrome. You just enter the script’s URL (for example, http://stevesouders.com/twitterhistory.user.js) and it works the next time you visit the page. The UI for managing scripts is available under wrench | Extensions or chrome://extensions/.

Firefox is second easiest. You first install the Greasemonkey add-on, then just navigate to the script’s URL. Managing scripts is done via Tools | Greasemonkey | Manage User Scripts.

Opera was slightly harder to figure out. You go to Opera menu | Settings | Preferences | Advanced | Content | JavaScript options and enter the directory where you want the scripts to be saved. Then you manually download the scripts and save them there. It’s straightforward, but kinda clunky and buried. But it works!

Development

I learned some time saving lessons while developing my Greasemonkey script across these browsers.

Create a landing page for your script - The URL of Greasemonkey scripts aren’t kept in location history for Firefox and Chrome, so you’re constantly typing or pasting it. It’s much easier to have a landing page that’s always open where you just click a link to re-install the script, for example my TwitterHistory landing page. This works best if your script isn’t cacheable…

Make your Greasemonkey script UNcacheable – As I make changes to the script I want to re-install it as easily as possible. Having to clear my cache each time is a pain. I can skip that step by adding a “Cache-Control: no-cache, must-revalidate” response header. Now when I click on the link in my landing page to re-install my Greasemonkey script, I get all the updates in Firefox. For Chrome, I still need one more step…

Uninstall in Chrome – Even with a landing page and a Greasemonkey script that’s not cacheable, re-installing doesn’t pick up the changes in Chrome. First you have to go to chrome://extensions/ or Tools | Extensions and click the Uninstall link for your script. So I have two tabs open all the time in Chrome – my landing page and chrome://extensions/. After I save my script changes on my server, I uninstall the script, and then re-install it from my landing page.

JavaScript errors show up in Chrome, but not Firebug – I’m a big Firebug fan, but was disappointed to see that errors in my Greasemonkey script didn’t show up in Firebug’s Console. You do see JavaScript errors in Chrome’s console (accessed via page | Developer | JavaScript console).

Now you can tryout my TwitterHistory Greasemonkey script in Firefox, Chrome, and Opera. Visit Greasespot to read more about Greasemonkey. Add comments below for other tips that you’ve found to make Greasemonkey scripts work across browsers.

4 Responses to cross-browser Greasemonkey scripts

  1. Another nice method for firefox is to edit the usercontent.css (%AppData%\Roaming\Mozilla\Firefox\Profiles\SOMETHING\chrome)

    This overrides all the stylesheets for the websites you are visiting and is done automatically.

  2. Recently i have asked for some assistance in following issue. Could cross-browser Greasemonkey scripts – be the solution?

    What’s the best cross browser method to find unexpected reloads and if the resources are from browser cache or server?

    We have a lot of incidents of reloading “valid cached resources from server” and inconsistencies between browsers.
    What’s the best (cross browser) method to alert (in JS or other means) about those “unexpected reloads” programmatically?
    I’m don’t care to embed some code or special resources on my pages which could assist us in this process.
    Although Firebug extension is Less preferred (because this method cannot be used to figuring out the problem in other browsers), any directions and best practices for firebug implementation would be much appreciated.

    Best regards
    Hertzel

  3. I’ve been a fan of Greasemonkey for many years now. I really appreciate the tips you wrote, so I’ll give some in return:

    - In Forefox you can edit the file locally for debug. In the greasemonkey window, click the “Edit” button. No caches, no reinstal, just save the file and reload.
    - In Firefox you can see the erros in the Javascript Console. If you love firebug, try something like:
    console = unsafeWindow.console || window.console;
    This line lets firebug work and don’t break Chrome’s console. Greasemonkey changed a lot, I don’t remember it you have to wrap you script into a try/catch and pass the error to the console or it’s not needed in the current version.
    - For complex scripts, I start with an external (createElement then appendChild to head) and when the basic functionality is good to go, I install it and start adding unsafeWindow and other compatibility layers.
    - Firefox and Chrome (I guess Opera too) supports localStorage and JSON parse/stringify, so I don’t use GM_setValue anymore:
    // restore the state
    var state = JSON.parse(localStorage.getItem(‘state’)||’{}’);
    // use the state as you wish
    state.lastTweetId = 987654321;
    state.options = {reloadTime:30,playSound:true};
    // save state
    localStorage.setItem(‘state’, JSON.stringify(state) );

    Hope it helps!

  4. You might want to play around in FireBug’s Console options: If you click on the little triangle next to “Console” there are a lot of options that are normally not ticked. For example “Show Chrome errors”. I believe this should help.