Writing Greasemonkey User Scripts for Firefox and Chrome

Greasemonkey was initially only a Firefox extention but due to its popularity it’s quickly adopted by major browsers. Since Opera takes a very small market share, here on the topic of cross browser user scripts, I’m only going to talk about Chrome and Firefox. As for Internet Explorer, I just don’t like it.

Now Chrome doesn’t need an extension to enable user scripts. It had the functionality built in. But the way it handles the scripts is quite different from Firefox’s. At first when I wanted to test if my Greasemonkey script worked in Chrome, I tried to figure out where it stored the script. That’s the way I always do in Firefox – just right click on a user script and edit, save and refresh to see it in effect right away.

Actually each time you install a user script in Chrome, it converts it into an extension, so it appears in the extension management page along with other “real” extensions. There must be a way to simplify the testing process but I don’t have any Chrome extension development experience yet.

Quote from Chrome’s official doc:

  • Chromium does not support @require, @resource, unsafeWindow, GM_registerMenuCommand, GM_setValue, or GM_getValue.
  • GM_xmlhttpRequest is same-origin only.

The doc must be a little outdated. Now GM_xmlhttpRequest is supposedly working cross domain, according to this recently fixed issue.

It’s bad Chrome doesn’t support @require, but here’s a cross browser solution example of injecting JavaScript libraries into the page. Taking it a bit further, and we get the following simple pattern for a cross browser user script that injects multiple third party scripts (extracted from “Google Reader Readability” script):

Cross Browser “@require”

// ==UserScript==
// @name           ???
// @namespace      http://your.tld/
// ==/UserScript==

var scripts = [
  '//cdnjs.cloudflare.com/ajax/libs/jquery/1.7/jquery.min.js',
  '//www.readability.com/embed.js'
];

var numScripts = scripts.length, loadedScripts = 0;

GM_addStyle('CSS styles goes here');

function main() {
  jQuery.noConflict(); // if window.$ has been used by other libs
  // ...
}

var i, protocol = document.location.protocol;
for (i = 0; i < numScripts; i++) {
  var script = document.createElement("script");
  script.setAttribute("src", protocol + scripts[i]);
  script.addEventListener('load', function() {
      loadedScripts += 1;
      if (loadedScripts < numScripts) {
        return;
      }
      var script = document.createElement("script");
      script.textContent = "(" + main.toString() + ")();";
      document.body.appendChild(script);
    }, false);
  document.body.appendChild(script);
  console.log(script);
}

With this pattern we put all our logic into the main function. `main` won't be run in the extension scope but it'll actually be run as a piece of inline code in the page. So its code can't make use of any of the "global" variables outside of `main`. Then if making Ajax requests, it can only rely on the ordinary XMLHttpRequest object and won't be able to request cross domain resources. That could be a road blocker sometimes.

iframes

With Greasemonkey add-on for Firefox, if you specify an include pattern in the header, this script will be run no matter it is a normal page or only an iframe. But Chrome doesn't run it for the iframes!

But surely there are workarounds. A simple example of getting Gmail's menu bar which resides in an iframe:

window.frames['canvas_frame'].contentDocument.getElementById('gbz')

Note that we use contentDocument. contentWindow.document won't work in Chrome.

Other issues

Another trivial issue I encountered was that you can't set User-Agent header when using GM_xmlhttpRequest. Chrome would complain:

Refused to set unsafe header "User-agent"


For more information please check out these two scripts:

They're short and simple, but work at least in both Firefox and Chrome.

One Reply to “Writing Greasemonkey User Scripts for Firefox and Chrome”

  1. Pingback: Quora

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove your intelligence before hitting * Time limit is exhausted. Please reload CAPTCHA.