Dynamic Greasemonkey Scripts

A friend and I recently did some work for a client creating a small Greasemonkey script.  Greasemonkey is a nifty little tool that lets you run custom scripts on webpages of your choosing.  These scripts are written in JavaScript and are local to your machine, not served up from some remote server.  They let you make changes to web pages on the fly, as if you got to write your own JS for that page.

The GM script had to work for Firefox and Chrome.  Firefox requires an extension, whereas chrome recognizes a GM script automatically.  The task seemed pretty easy.  We needed to dynamically create a GM script to be installed on the user's system.  All a GM script really needs is some JS with a few special comment fields at the top of the script, and you're ready to rock and roll.  No biggie I thought, I'll just hack up a quick PHP script to spit back the custom JS...no dice.  It turns out that the GM plugin in Firefox and Chrome's auto-detection require that the file name end in .user.js.  Well, this posed a problem.  I need a file ending in .user.js to be served up by the web server, but I needed it to have dynamic content.  I can't write the dynamic parts in JS since a GM script doesn't actually get executed by the browser when served, it get's preempted by the plugin/module, and offered for install as a user script.  I couldn't make a PHP script spit back the GM script because the plugins would refuse to install something ending in .php.

The solution we ended up with was that we added a special rule to run the GM script through PHP before serving it.  We developed on nginx, and came up with the following config rule:

location ~* script_name\.user.\js$ {
    if (!-f $request_filename) {
        return 404;
    }

    fastcgi_pass 127.0.0.1:9000;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include /etc/nginx/fastcgi_params;
}

Unfortunately, our client uses apache, so here's the config rule we came up with:

<FilesMatch "script_name\.user\.js">
  AddHandler php5-script .js
  AddType text/javascript .js
</FilesMatch>

We had a second challenge ahead of us.  The default MIME type for a file processed by PHP is text/html, but the browsers expected type text/javascript for GM scripts. Our quick and dirty solution for testing was to user PHP's header function to set the content type:


header('content-type:text/greasemonkey');

The last hurdle we had to face was a browser compatibility issue.  We needed to pass information to the backend via a query string so we could actually make the content dynamic.  That query string gets tacked on to the end of the URL, meaning we now had a URL like http://www.example.com/script_name.user.js?param=foo.  Unfortunately, the Firefox plugin didn't like that very much.  It seems the plugin just looks for a .user.js at the end of the entire URL, query string and all, to determine if it's a GM script.  Time for another hack: just tack the script's file name as the last part of the query:

http://www.example.com/script_name.user.js?param=foo&gm=script_name.user.js

It didn't take too much time for us to figure out this workaround, and it's certainly not the cleanest solution, but it was a good learning experience.  It seems Greasemonkey could really benefit from having it's own MIME type, say "text/greasemonkey".  That would allow the broswer plugin/module to recognize a GM script by it's content type instead of only by some file extension or string at the end of the URL, as well as allowing for easy, dynamic generation of a GM script, using something like PHP's header function.

Comments

Popular posts from this blog

Welcome!

Easier sed than done

Good Preprocessor Usage