Chapter 6: Gmail and Greasemonkey

Another phenomenon to hit the web at the same time as Gmail was the Firefox browser. Indeed, the growth of this open source application easily rivaled Gmail for shocking explosiveness. Apart from the additional security benefits and tasty user interface advantages that Firefox gives, the browser is also open to a considerable amount of hacking in itself. One of the key hacks for Firefox was Greasemonkey. In this chapter, you learn how Greasemonkey and Firefox can be used to radically improve your Gmail experience, and how the understanding you now have about the workings of Gmail will enable you to build your own Greasemonkey scripts.

Chapter 6: Gmail and Greasemonkey

  Gmail and

What Is Greasemonkey?

Greasemonkey allows the user to assign snippets of JavaScript
code to run automatically whenever a certain page is loaded. The
upshot of this is that you can write JavaScript code that will customize those web pages, modifying layout, adding new features,
or removing extraneous parts of the page. Greasemonkey has
been used to remove advertising, rewrite links, add new links to
other sites, and even add completely new menus to sites. Gmail,
being one huge hunk of burning JavaScript, is beautifully positioned to be taken advantage of by Greasemonkey.
To use Greasemonkey, you have to install it first. Do that by getting the latest version from http://greasemonkey.mozdev.
The snippets of JavaScript used by Greasemonkey are called userscripts. They need to be installed into Firefox for the application to
work. You do that like this: Go to the page with the userscript in
it. It will look really ugly, with lots of JavaScript, and the top 20 or
so lines preceded by double forward-slashes, as in Figure 6-1.
˛ What is
˛ Using userscripts
˛ Customizing the
Gmail experience
in this chapter
11_59611x ch06.qxp 11/28/05 11:06 PM Page 91
92 Part II — Getting Inside Gmail
Click Tools, and then Install User Script. Check that everything looks okay.
(Nothing red and scary? Good, carry on.) That’s it. You’re done.
FIGURE 6-1: Firefox and Greasemonke

The Userscripts

Now that you know how to install userscripts, you can start to use them.
Ordinarily, you wouldn’t have to type the code in, seeing as you just point your
browser to the site and let fly with the installation procedure, as detailed in the
preceding text, but you can learn a lot from looking at the code. For the next few
examples, therefore, you shall take a look. There are techniques to be learned, and
inspiration to be had, here.

Displaying Bloglines Within Gmail

Bloglines — shown in Figure 6-2 — is another great web-based application. It’s an RSS reader — you can use it to keep track of hundreds of sites’ content by subscribing to each of the sites’ feeds. Many users, myself included, keep close to a hundred sites in their Bloglines subscription. Some have many more. Indeed, the regular trawl of unread news items in Bloglines is close to as important as the regular checking of my Inbox. 11_59611x ch06.qxp 11/28/05 11:06 PM Page 92 Chapter 6 — Gmail and Greasemonkey 93 FIGURE 6-2: The Bloglines Greasemonkey extension in action Martin Sersale’s beautiful code, which can be installed from, allows you to combine the two. First, the listing, and then we shall talk about the more interesting sections. The whole thing is listed here, in Listing 6-1, as it’s full of very useful stuff. Listing 6-1: Displaying Bloglines with Gmail // Displays a box in Gmail with your Bloglines feeds // version 0.1 // 2005-05-02 // Copyright (c) 2005, Martin Sarsale - [email protected] // Released under the GPL license // // ----------------------------------------------------------- --------- // ==UserScript== // @name Bloglines // @namespace / // @include* Continued 11_59611x ch06.qxp 11/28/05 11:06 PM Page 93 94 Part II — Getting Inside Gmail Listing 6-1 (continued) // @include* // @include* // @include* // @include // @include* // @exclude // @description Displays a box in Gmail with your Bloglines feeds // ==/UserScript== (function(){ var __items={}; function cache_gotsubs(e){ GM_setValue(‘subs’,e[‘responseText’]); GM_setValue(‘subs_updated’,Date.parse(Date())/1000) //GM_log/gci(‘getting data, subs_updated set to ‘+GM_getValue(‘subs_updated’,0)); gotsubs(e); } function getcachedsubs(){ var v=GM_getValue(‘subs’,null); if (v){ updated=GM_getValue(‘subs_updated’,0); d=Date.parse(Date())/1000; if ((d - updated) > 300){ //GM_log/gci(‘cache expired: ‘+(d - updated)+”(“+d+” - “+updated+”)”); return false; }else{ return v; } } return false; } function getsubs(){ v=getcachedsubs(); if (v){ gotsubs(v); return true; } getsubs(); } function _getsubs(){ GM_xmlhttpRequest({‘method’:’GET’,’url’:”http://rpc.bloglines. com/listsubs”,’onload’:cache_gotsubs}); 11_59611x ch06.qxp 11/28/05 11:06 PM Page 94 Chapter 6 — Gmail and Greasemonkey 95 } function parsesubs(r){ parser=new DOMParser(); dom=parser.parseFromString(r,’text/xml’); outlines=dom.getElementsByTagName(‘outline’); subs=new Array(); for(i=0; i<outlines.length; i++){="" if="" (outlines[i].getattribute(‘type’)="" !="undefined" ){="" d="{" ‘title’:outlines[i].getattribute(‘title’),="" ‘htmlurl’:outlines[i].getattribute(‘htmlurl’),="" ‘type’:outlines[i].getattribute(‘type’),="" ‘xmlurl’:outlines[i].getattribute(‘xmlurl’),="" ‘bloglinessubid’:outlines[i].getattribute(‘bloglinessubid’),="" ‘bloglinesunread’:outlines[i].getattribute(‘bloglinesunread’)="" };="" subs[subs.length]="d;" }="" return="" subs;="" function="" gotsubs(response){="" (typeof(response)="=’object’){" data="response[‘responseText’];" }else{="" r="parsesubs(data);" r.sort(function(a,b){;="" var=""> b[‘BloglinesUnread’]; if(r){return -1}else{return 1} }); addsubhtml_init(); for(i=0; i<r.length; i++){="" addsubhtml(r[i]);="" }="" addsubhtml_end();="" function="" addsubhtml_end(){="" ul="document.getElementById(‘bloglines_subs’);" if="" (ul){="" gm_setvalue(‘subs_cached_html’,ul.innerhtml);="" createbutton(str){="" a="document.createElement(‘div’);" a.appendchild(document.createtextnode(str))="""’#dddddd’;""’outset’;""’#eeeeee’;" continued="" 11_59611x="" ch06.qxp="" 11="" 28="" 05="" 11:06="" pm="" page="" 95="" 96="" part="" ii="" —="" getting="" inside="" gmail="" listing="" 6-1="" (continued)="""’2px’;""’10px’;""’10px’;""’10px’;""’middle’;""’center’;""’x-small’;""’bold’;""’absolute’;""’0px’;""’0px’;" return="" a;="" addsubhtml_init(){="" ul.innerhtml="’’;" (!document.getelementbyid(‘bloglines_reload’)){="" a.addeventlistener(‘click’,_getsubs,false);="""’bloglines_reload’;" ul.parentnode.appendchild(a);="" addsubhtml(d){="" li="document.createElement(‘li’);" li.classname="’nl’;""’0px’;""’0px’;""’100%’;""’hidden’;" a.href="’’+d[‘Blog" linessubid’]+’&site="0’;""’_blank’;" txt="d[‘title’]" (d[‘bloglinesunread’]="">0){’bold’; txt=txt+” (“+d[‘BloglinesUnread’]+”)”; 11_59611x ch06.qxp 11/28/05 11:06 PM Page 96 Chapter 6 — Gmail and Greasemonkey 97 } a.appendChild(document.createTextNode(txt)); li.appendChild(a); ul.appendChild(li); } function getsub(e){; GM_xmlhttpRequest({‘method’:’GET’,’url’:”http://rpc.bloglines. com/getitems?n=0&s=”+id,’onload’:gotsub}); } function gotsub(r){ var d=parsesub(r[‘responseText’]); for(var i=0; i<d.length; i++){="" item="d[i];" items[gettext(item.getelementsbytagname(‘guid’)[0])]="item;" }="" for(i="0;" i<d.length;="" displaysubhtml(item);="" function="" displaysubhtml(item){="" li="document.createElement(‘li’);" b="document.getElementById(‘items’);" a="document.createElement(‘a’);""getText(item.getElementsByTagName(‘guid’)[0]);" a.addeventlistener(‘click’,displayitem,false);="" a.appendchild(document.createtextnode(gettext(item.getelements="" bytagname(‘title’)[0])));="" li.appendchild(a);="" b.appendchild(li);="" displayitem(e){="" id=";" var="" displayitemhtml(item);="" displayitemhtml(item){="" i="document.getElementById(‘item’);" i.innerhtml="getText(item.getElementsByTagName(‘description’)[0" ]);="" continued="" 11_59611x="" ch06.qxp="" 11="" 28="" 05="" 11:06="" pm="" page="" 97="" 98="" part="" ii="" —="" getting="" inside="" gmail="" listing="" 6-1="" (continued)="" gettext(e){="" nodes="e.childNodes;" for="" (var="" i<nodes.length;="" if="" (nodes[i].nodevalue="" !="null){" return="" nodes[i].nodevalue;="" parsesub(r){="" parser="new" domparser();="" dom="parser.parseFromString(r,’text/xml’);" r="dom.getElementsByTagName(‘item’);" r;="" checkifpresenthtml(){="" d="document.getElementById(‘nt_9’);" (!d){="" inithtml();="" getsubs();="" switch_labels(){="" i<window.labels_readed.length;="" label="window.labels_readed[i];" ("""’none’;" }else{="" inithtml(){="" bar="document.getElementById(‘nav’);" (bar){="" document.stylesheets[0].insertrule(‘ul#bloglines_subs="">li>a{tex t-decoration:none}’,document.styleSheets[0].length); v=getcachedsubs(); if (v){ data=GM_getValue(‘subs_cached_html’,’’); }else{ data=’’; 11_59611x ch06.qxp 11/28/05 11:06 PM Page 98 Chapter 6 — Gmail and Greasemonkey 99 } invite=document.getElementById(‘nb_1’); if (invite){’none’; } document.getElementById(‘ds_spam’)’n one’; document.getElementById(‘ds_all’)’no ne’; document.getElementById(‘ds_trash’)’ none’; document.getElementById(‘comp’)’none ’; div=document.createElement(‘div’);’0px’;’nb_9’; html=”


”; div.innerHTML=html; bar.appendChild(div); return true; } return false; } function init(){ return inithtml(); Continued 11_59611x ch06.qxp 11/28/05 11:06 PM Page 99 100 Part II — Getting Inside Gmail Listing 6-1 (continued) } if (window.location.href==’ l=en’ || window.location.href.substr(0,57) == ‘’ ){ //GM_log/gci(‘logout’); GM_setValue(‘subs’,null); GM_setValue(‘subs_update’,null); GM_setValue(‘subs_cached_html’,null); }else{ if(init()){ getsubs(); setInterval(checkifpresenthtml,1000); } } })() </d.length;></r.length;></outlines.length;>

How It Works

Have a read through the preceding code. From the knowledge you have from the
chapters on skinning CSS and how the JavaScript within Gmail works, you
should be able to glean a little inkling into how it works. For the sake of brevity, I
won’t repeat all of the functions here, but to walk through, the first interesting
things are the _getsubs (note the plural and underscore) and parsesubs functions. _getsubs uses the same xmlhttprequest system that Gmail does. _getsubs
requests your list of subscriptions from Bloglines.
Once the subs have been got by _getsubs, the script goes through a series of
functions to cache them. That is all at the top of the script, and causes the subscriptions list to be collected only once an hour. (At the bottom of the script, the
very last function, is code to check if the page Greasemonkey can see is the one
you get only if the user has logged out of Gmail. If that page is hit, the cache is
emptied as well.)
A freshly retrieved list of subs is then passed through the parsesubs function.
This parses the XML of the subscription list into an array.
Note here that this is, so far, very useful stuff. Many sites provide information
feeds in XML, and all you have here really is a script that pulls in a feed (after
checking it’s not in a cache) and parses it. You can reuse that structure to pull in
data from just about anywhere. Indeed, if an ordinary website has no feed, but is
well-formed XHTML, you can even use this same technique to screenscrape
something and display that information within a page.
11_59611x ch06.qxp 11/28/05 11:06 PM Page 100
Chapter 6 — Gmail and Greasemonkey 101
Even better, the script then has to go use the data in the subs list, which is placed
inside an array. In the getsub function (note the singular, and lack of underscore),
the script retrieves the XML of the feed. Once you have that, use the functions
displaysubhtml and inithtml to convert the XML of the feed into HTML
and display it on the page. From Chapter 4, even if you know no JavaScript, you
should be able decipher the meaning of lines such as this:
They prevent the browser from displaying that particular div, making space for the
HTML it then adds onto the screen.
To go more deeply into this script would require another book, on JavaScript and
Greasemonkey at the very least, but I hope by reading through it you can see how
it works. It’s very hackable — have a go at converting it to displaying information
from other XML-providing sources. The weather forecasts available at http:// are a good starting point. For extra inspiration, consider displaying the weather at the location of a new mail’s sender. Tricky one, that

Add a Delete Button

Not content with grabbing data from other sources and chucking it all over the site like some crazed mash-up DJ, you can also use Greasemonkey to add additional user interface elements. Anthony Lieuallen’s script at www.arantius. com/article/arantius/gmail+delete+button/ adds a Delete button to the menu, as shown in Figure 6-3. FIGURE 6-3: The added Delete button Without such a button, as you know, you have to move the message to trash. Not much of a change, admittedly, but a nice UI improvement. Listing 6-2 shows the code. 11_59611x ch06.qxp 11/28/05 11:06 PM Page 101 102 Part II — Getting Inside Gmail Listing 6-2: Adding the Delete Button // ==UserScript== // @name Gmail Delete Button // @namespace // @description Add a “Delete” button to Gmail’s interface. // @include http*://**mail*?* // @version 2.9.1 // ==/UserScript== // // Version 2.91: // - Japanese and Hungarian translation // Version 2.9: // - Compatibility upgrade, works in GM 0.6.2 in Firefox 1.5 Beta 1 // Version 2.8.3: // - Polish translation // Version 2.8.2: // - Russian translation // Version 2.8.1: // - Bulgarian translation // Version 2.8: // - Cleaned up bits of the code. No more global scope objects. // - Deer Park compatible. // Version 2.7.2: // - Better i81n, file encoded as unicode, to be compatible with newer // versions of greasemonkey. // Version 2.7: // - Internationalization. If you speak a language other than english, // please check the existing text (if there) and/or suggest the right // word to mean ‘Delete’ in your language. // - A change to the default include path. // Version 2.6: // - Add button into starred and sent mail section as per user request. // - Rework logic to use events (mouse click and key press) instead of // timers to further ameliorate lockouts. I’ve recieved at least one // report that it was fixed by 2.3, and others that it was not at 2.5. 11_59611x ch06.qxp 11/28/05 11:06 PM Page 102 Chapter 6 — Gmail and Greasemonkey 103 // Perhaps it was fixed and the timing of reports was off, but this // should make things more certain. I always welcome constructive // bug reports, I have never had a problem so I need information from // those who have to change anything. // Version 2.5: // - Change default include pattern to match a change in Gmail’s code. // Version 2.4: // - Remove red text. You may restore the red color by uncommenting // the proper line in _gd_make_dom_button. // - Do not show for a message in the spam folder. // - Minor tweaks. // Version 2.3: // - Add/change code to track down/eliminate error conditions. // - Display error when there are no selected messages to delete. // - Include delete button in all labels and ‘All Mail’ section. // Version 2.2: // - Patched to work with GreaseMonkey 0.3.3 // // ----------------------------------------------------------- --------- // Originally written by Anthony Lieuallen of // Licensed for unlimited modification and redistribution as long as // this notice is kept intact. // ----------------------------------------------------------- --------- // // If possible, please contact me regarding new features, bugfixes // or changes that I could integrate into the existing code instead of // creating a different script. Thank you // (function(){ function _gd_dumpErr(e) { var s=’Error in Gmail Delete Button:\n’; s+=’ Line: ‘+e.lineNumber+’\n’; Continued 11_59611x ch06.qxp 11/28/05 11:06 PM Page 103 104 Part II — Getting Inside Gmail Listing 6-2 (continued) s+=’ ‘’: ‘+e.message+’\n’; dump(s); } function _gd_element(id) { try { var el=window.document.getElementById(id); } catch (e) { gd_dumpErr(e); return false; } if (el) return el; return false; } function _gd_gmail_delete(e) { dump(‘Called _gd_gmail_delete()...\n’); //find the command box var; var command_box=delete_button.parentNode.getElementsByTagName(‘sel ect’)[0]; command_box.onfocus(); //find the command index for ‘move to trash’ var delete_index=-1; for (var i=0; i<command_box.options.length; i++)="" {="" if="" (‘tr’="=command_box.options[i].value" &&="" !command_box.options[i].disabled="" )="" delete_index="i;" break;="" }="" don’t="" try="" to="" continue="" we="" can’t="" move="" trash="" now="" (-1="=delete_index)" var="" box="_gd_element(‘nt1’);" (box)="" find="" the="" put="" an="" error="" message="" in="" it="""’visible’;" box.getelementsbytagname(‘td’)[1].innerhtml="’Could" not="" delete.="" make="" sure="" at="" least="" one="" conversation="" is="" selected.’;="" 11_59611x="" ch06.qxp="" 11="" 28="" 05="" 11:06="" pm="" page="" 104="" chapter="" 6="" —="" gmail="" and="" greasemonkey="" 105="" catch="" (e)="" gd_dumperr(e);="" return;="" set="" command="" index="" fire="" change="" event="" command_box.selectedindex="delete_index;" command_box.onchange();="" command_box.dispatchevent(‘click’);="" evt="createEvent();" function="" _gd_make_dom_button(id)="" delete_button="window.document.createElement(‘button’);" delete_button.setattribute(‘class’,="" ‘ab’);="" delete_button.setattribute(‘id’,="" ‘_gd_delete_button’+id);="" delete_button.addeventlistener(‘click’,="" _gd_gmail_delete,="" false);="" uncomment="" (remove="" two="" leading="" slashes)="" from="" next="" line="" for="" red="" text="""’#EE3311’;" this="" a="" little="" hack-y,="" but="" can="" code="" language="" here="" lang="’’;" urltotest="‘frame’)[1]" .src;="" m="urlToTest.match(/html\/([^\/]*)\/loading.html$/);" (null!="m)" check="" that="" language,="" right="" word!="" buttontext="’Delete’;" default="" button,="" overriden="" switch="" below="" know="" word="" (lang)="" case="" ‘it’:="" ‘es’:="" ‘fr’:="" ‘pt-br’:="" continued="" 106="" part="" ii="" getting="" inside="" listing="" 6-2="" (continued)="" was="" suggested="" by="" user="" ‘apaga’="" more="" proper="" ‘de’:="" ‘bg’:="" ‘ru’:="" ;="" ‘pl’:="" ‘ja’:="" ‘hu’:="" delete_button.innerhtml="’<b">’+buttonText+’’; return delete_button; } function _gd_insert_button(insert_container, id) { if (!insert_container) return false; if (_gd_element(‘_gd_delete_button’+id)) { return false; } //get the elements var spacer, delete_button; delete_button=_gd_make_dom_button(id); spacer=insert_container.firstChild.nextSibling.cloneNode(false ); //pick the right place to put them var insert_point=insert_container.firstChild; //this is default if (2==id || 3==id) { // 2 and 3 are inside the message and go at a different place insert_point=insert_point.nextSibling.nextSibling; } if ( { //inside the search page we go yet different places with different spacers 11_59611x ch06.qxp 11/28/05 11:06 PM Page 106 Chapter 6 — Gmail and Greasemonkey 107 if (0==id) { spacer=insert_container.firstChild.nextSibling.nextSibling.clo neNode(false); insert_point=insert_container.firstChild.nextSibling.nextSibli ng.nextSibling; } if (1==id) spacer=window.document.createElement(‘span’); //no space really needed here } else if ( { //inside the sent page we go yet different places with different spacers if (0==id) { //spacer=insert_container.firstChild.nextSibling.nextSibling.c loneNode(false); //insert_point=insert_container.firstChild.nextSibling.nextSib ling.nextSibling; spacer=window.document.createTextNode(‘ ‘); insert_point=insert_container.firstChild.nextSibling.nextSibli ng; } if (1==id) spacer=window.document.createElement(‘span’); //no space really needed here } //put them in insert_container.insertBefore(spacer, insert_point); insert_container.insertBefore(delete_button, spacer); } function _gd_place_delete_buttons() { if (!window || !window.document || !window.document.body) return; var top_menu=_gd_element(‘tamu’); if (top_menu) _gd_insert_button(top_menu.parentNode, 0); var bot_menu=_gd_element(‘bamu’); if (bot_menu) _gd_insert_button(bot_menu.parentNode, 1); var mtp_menu=_gd_element(‘ctamu’); if (mtp_menu) _gd_insert_button(mtp_menu.parentNode, 2); var mbt_menu=_gd_element(‘cbamu’); if (mbt_menu) _gd_insert_button(mbt_menu.parentNode, 3); Continued 11_59611x ch06.qxp 11/28/05 11:06 PM Page 107 108 Part II — Getting Inside Gmail Listing 6-2 (continued) } function _gd_button_event() { try{ setTimeout(_gd_place_delete_buttons, 333); gd_place_delete_buttons(); } catch(e) { gd_dumpErr(e); } } var; dump(‘Load gmail page: ‘+s+’\n’); if (s.match(/\bsearch=(inbox|query|cat|all|starred|sent)\b/) || ( s.match(/view=cv/) && !s.match(/search=(trash|spam)/) ) ) { dump(‘==== Apply Gmail Delete Button to: ====\n’+s+’\n’); //put the main button in try{_gd_place_delete_buttons();}catch(e){dump(e.message);} //set events to try adding buttons when the user does things //because gmail might create new places to need buttons. window.addEventListener(‘mouseup’, _gd_button_event, false); window.addEventListener(‘keyup’, _gd_button_event, false); } })(); Again, without going into JavaScript too deeply, there are two things to note here. The first is how it draws a new button into the page. The second is that the script checks the language the interface is being displayed in and labels the button accordingly. Very pleasingly done</command_box.options.length;>


Mark Pilgrim’s userscript, GmailSecure, found at
scripts/show/1404 and in Listing 6-3, has a simple function: to force Gmail to
use HTTPS instead of HTTP.
11_59611x ch06.qxp 11/28/05 11:06 PM Page 108
Chapter 6 — Gmail and Greasemonkey 109
It is ridiculously simple, consisting simply of only one line of actual code (the rest, to
the chagrin of those of us who print on dead trees, is simply the license under which
the code is released, which has to be included). Here’s the line. Brace yourself:
location.href = location.href.replace(/^http:/, ‘https:’);
Because Gmail works via either HTTP or HTTPS, all the userscript needs to do
is make sure that every time a hyperlink starts with http: that part of the URL is
replaced with https:.
Greasemonkey does this by invoking the location.href.replace function.
Listing 6-3: The Ludicrously Simple GmailSecure
// GMailSecure
// version 0.3 BETA!
// 2005-06-28
// Copyright (c) 2005, Mark Pilgrim
// Released under the GPL license
// -----------------------------------------------------------
// This is a Greasemonkey user script.
// To install, you need Greasemonkey:
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to “Install User
// Accept the default configuration and install.
// To uninstall, go to Tools/Manage User Scripts,
// select “GMailSecure”, and click Uninstall.
// -----------------------------------------------------------
// ==UserScript==
// @name GMailSecure
// @namespace
// @description force GMail to use secure connection
// @include*
// ==/UserScript==
11_59611x ch06.qxp 11/28/05 11:06 PM Page 109
110 Part II — Getting Inside Gmail
Listing 6-3 (continued)
Copyright (C) 2005 Mark Pilgrim
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You can download a copy of the GNU General Public License at
or get a free printed copy by writing to the Free Software
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
location.href = location.href.replace(/^http:/, ‘https:’);
// ChangeLog
// 2005-07-08 - 0.3 - MAP - added license block
// 2005-06-28 - 0.2 - MAP - changed GMail URL
This idea, rewriting URLs, can be very powerfully used. With Mark Pilgrim’s
technique of using location.href.replace, you can do this by brute force.
With the next example, “Mailto Compose in Gmail,” you will see the more radical
version of this.


Perhaps the biggest issue that hits Gmail users, if they start to use the application
as their primary e-mail tool, is that mailto: links found within e-mails do not
trigger Gmail, but rather cause your operating system to load up what it thinks is
the default e-mail application. One moment of thoughtless clicking, and Outlook
Express starts appearing all over the screen. Nausea and discomfort result.
11_59611x ch06.qxp 11/28/05 11:06 PM Page 110
Chapter 6 — Gmail and Greasemonkey 111
Julien Couvreur’s MailtoComposeInGmail userscript solves this issue. It applies
itself to every site apart from Gmail, rewriting the mailto: links it finds into a
link that opens the Gmail compose page, with the to: and subject: lines
already filled in.
Listing 6-4 elucidates the userscript. Afterwards, you will see how it works.
Listing 6-4: MailtoComposeInGmail
// MailtoComposeInGMail
// version 0.1
// 2005-03-28
// Copyright (c) 2005, Julien Couvreur
// Released under the GPL license
// -----------------------------------------------------------
// This is a Greasemonkey user script.
// To install, you need Greasemonkey:
// Then restart Firefox and revisit this script.
// Under Tools, there will be a new menu item to “Install User
// Accept the default configuration and install.
// To uninstall, go to Tools/Manage User Scripts,
// select “Mailto Compose In GMail”, and click Uninstall.
// Aaron Boodman also has a similar script, at:
// In his approach, the links are re-written at the time that
you click 
// on them. One benefit is that the link still looks like
// when you hover over it.
// -----------------------------------------------------------
// After the page is loaded, look for “mailto:” links and
hooks their onclick
// event to go to GMail’s compose page, passing all the usual
// (to, cc, subject, body,...).
11_59611x ch06.qxp 11/28/05 11:06 PM Page 111
112 Part II — Getting Inside Gmail
Listing 6-4 (continued)
// -----------------------------------------------------------
// ==UserScript==
// @name Mailto Compose In GMail
// @namespace
// @description Rewrites “mailto:” links to GMail compose
// @include *
// @exclude
// ==/UserScript==
(function() {
var processMailtoLinks = function() {
var xpath = “//a[starts-with(@href,’mailto:’)]”;
var res = document.evaluate(xpath, document, null,
var linkIndex, mailtoLink;
for (linkIndex = 0; linkIndex < res.snapshotLength;
linkIndex++) { 
mailtoLink = res.snapshotItem(linkIndex);
var m = mailtoLink.href;
var matches =
var emailTo, params, emailCC, emailSubject,
emailTo = matches[1];
//alert(“Found to=” + emailTo);
params = matches[3];
if (params) {
var splitQS = params.split(‘&’);
var paramIndex, param;
for (paramIndex = 0; paramIndex <
splitQS.length; paramIndex++) {
param = splitQS[paramIndex];
nameValue = param.match(/([^=]+)=(.*)/);
11_59611x ch06.qxp 11/28/05 11:06 PM Page 112
Chapter 6 — Gmail and Greasemonkey 113
if (nameValue && nameValue.length == 3) { 
// depending on name, store value in a
pre-defined location
switch(nameValue[1]) {
case “to”:
emailTo = emailTo + “%2C%20” +
case “cc”:
emailCC = nameValue[2];
//alert(“Found CC=” +
case “subject”:
emailSubject = nameValue[2];
//alert(“Found subject=” +
case “body”:
emailBody = nameValue[2];
//alert(“Found body=” +
mailtoLink.href =
“” + 
(emailTo ? (“&to=” + emailTo) : “”) + 
(emailCC ? (“&cc=” + emailCC) : “”) +
(emailSubject ? (“&su=” + emailSubject) : “”)
(emailBody ? (“&body=” + emailBody) : “”);
// mailtoLink.onclick = function() { location.href
= newUrl; return false; };
window.addEventListener(“load”, processMailtoLinks,
Instead of rewriting the mailto: links directly, as Mark Pilgrim’s script does to
make HTTP links into HTTPS, this script adds a JavaScript onclick function
11_59611x ch06.qxp 11/28/05 11:06 PM Page 113
114 Part II — Getting Inside Gmail
to the link instead. When you click such a link, Firefox fires off the JavaScript
function instead of following the link. The onclick function, in turn, opens the
page in Gmail that allows a mail to be composed. Because mailto: links can contain the recipients, message subject, and body text, the userscript has to retrieve
these and add them to the Gmail compose page. You already know that the compose mail URL can be built up in this way, so it’s pretty easy to do that. Here’s the
code that does it:
mailtoLink.href = “”

(emailTo ? (“&to=” + emailTo) : “”) + 
(emailCC ? (“&cc=” + emailCC) : “”) +
(emailSubject ? (“&su=” + emailSubject) : “”)
(emailBody ? (“&body=” + emailBody) : “”);
// mailtoLink.onclick = function() { location.href
= newUrl; return false; };
When you run on a link that points to mailto:[email protected],
this will produce the URL
0?&[email protected].
Perfect. Using this code, you can compose other messages. Perhaps you might like
to use it to produce an “e-mail this to me” userscript, populating the message body
with the contents of the page.

Other Userscripts

Greasemonkey continues to recruit happy developers, and the number of userscripts is ever increasing. Here are some more scripts that provide additional functionality to Gmail. More still can be found at
As ever, of course, you must remember that Gmail’s interface is an ever-changing
mélange of weirdness, and these userscripts may well fade in and out of functionality. If one stops w

Mark Read Button

11_59611x ch06.qxp 11/28/05 11:06 PM Page 114
Chapter 6 — Gmail and Greasemonkey 115
Jim Lawton’s userscript creates a button that, when mails are selected, allows them
to be marked as read, en masse. Very useful in itself, it also provides the core code for
acting on a large number of mails in one go: handy for your own scripts, perhaps.

Multiple Signatures

This is a very smart script indeed. Using the ability to change the reply-to:
address within Gmail, it allows the user to change both their e-mail signature,
their reply-to: address, and — brilliantly — Gmail’s color scheme at the same
time. This allows you to use Gmail for multiple mail accounts without getting
them mixed up in the heat and fury of a working day. Very clever

Hide Invites

A very simple use of Greasemonkey. This userscript simply hides the box that
holds the facility to send Gmail invitations to your friends. As you have already
looked at the way Gmail is constructed, you can modify this userscript yourself to
stop the display of any section of the interface.

Random Signatures

Robson Braga Araujo’s userscript adds a random tagline to the bottom of your
Gmail signature and also creates an option in the Settings menu to edit the
taglines and control how the userscript operates.

And Now . . .

In this chapter, you saw that Gmail’s interface and workings are even more customizable than you might have first thought. By using Greasemonkey, you can
seriously improve the Gmail experience. And by looking at the way the scripts
work, you can learn how to write your own.

What's Your Reaction?