Restartless Firefox add-ons, part 1: Giving your add-on the bootstrap

Intro

Firefox 4 will introduce a new type of add-on to users, the restartless add-on! It's true that there are many problems that need to be resolved before all add-ons can be made restartless, which should help illustrate why it has taken as long as it has for Mozilla to introduce the feature. The capability is available now, and although it is more difficult to write a restartless add-on than it is to write a add-on that requires a restart to be installed/uninstalled/enabled/disabled I think most would agree that a restartless add-on is usually superior to one that requires a restart. So I've decided to start a short blog series on how to write restartless add-ons for Firefox in order to help those that are interested along the way as much as I can.

You should note that Mozilla is working on a add-on sdk which makes writing restartless add-ons much easier by providing APIs which allow you to abstract away most of the painful parts, which are the parts that I plan to describe in this series. So if you're interested in writing a restartless add-on without the add-on sdk, or if you're planning on writing modules/packages to be used by add-ons made with the add-on sdk, then you may find this series useful.

The Bootstrap

The core of a restartless add-on is it's bootstrap.js file, because it's this file which Firefox loads at startup, and it's this file that Firefox will notify about install/uninstall/enable/disable/etc events which are key to making a add-on restartless. In order to notify Firefox that your add-on includes a bootstrap.js file, and that you want it to be used you must include a em:bootstrap="true" in your install.rdf file. If you're unfamiliar with a add-on's install manifest file (aka install.rdf) then you can read about it here (it's basically just a metadata file for add-ons).

The bootstrap.js are a bit like a Web Worker in that it has it's own scope which has a special set of global variables and it is notified of events by defining specially named functions which are meant to receive these events. They are disimilar from Web Workers in that they don't run in their own thread, they can pass objects in and out (not just strings), and they have access to pretty much everything using the Components global object which is made available to them.

For a complete list of the functions bootstrap.js uses, there parameters, etc, then I suggest reading this article on Bootstrapped Extensions. The bottom line is that you need to define functions which will handle install/uninstall/enable/disable/startup type events. For instance the first function that you'll want to define is startup(), because this is the function which would be called after the add-on is installed, after it is enabled, or after the browser starts up. Because a add-on typically needs to know why the startup function was called there is an aReason argument passed in to the function (read the docs on how to use aReason). The second function that you'll likely require is the shutdown() function, this is a very important part of restartless add-ons, it's basically the cleanup function, it has to remove any evidence that your add-on was ever present in a live browser session. In other words if your add-on adds/does X to the browser, then the shutdown() function will remove/undo X, where X can be stuff like adding xul elements and/or attributes, adding javascript variables to other scopes, adding observers/event listeners, creating background operations, and various other things; all of which are extremely easy to forget to remove.

Note: If you'd like to make your add-on restartless and backwards compatible, then read this.

Summary

We now know a restartless add-on needs two files: first a install.rdf file, and second a bootstrap.js file; we also know that the latter will be notified of install/uninstall/enable/disable type events related to the add-on which we are expected to define in order to use.

It may surprise you how many add-ons could be made with these two files alone. Most user chrome scripts or jetpack prototype scripts could be written as a bootstrap.js file and made into a extension simply by adding a install.rdf file and zipping the two together as a .xpi file.

[More]

Scriptish 0.1b4 Can Update Your User Scripts

For a long time now both user script authors and Greasemonkey users have wanted Greasemonkey to be able to update user scripts. In absence of this feature user script authors have resorted to rolling their own code to do the updating, which has lead to a minefield of problems ranging from simple javascript errors to causing long downtimes on userscripts.org. Besides the obvious risks that third-party user script updaters have of not working, over using bandwidth, and harming the uptime of important websites, there is also a variability of design and usefulness which means it's hard for a user to know things that they should know, like:

  1. Which of their installed user scripts are update-able?
  2. Which of their installed user scripts have updates available?
  3. Where will they be notified of updates for there user scripts?
  4. What do they need to do in order to update a user script?
  5. How can they disable updates for a user script?

Hopefully it's now plain to see why a user script engine must update user scripts, and yet none that I know of do. Greasemonkey for Firefox does not. Google Chrome runs user scripts natively, but also does not update them. Those two engines are by far the most popular, but there are a few others, all of which do not update user scripts..

Well late last year I started discussing this feature with Olivier Cornu for his fork of Greasemonkey which he called Webmonkey. Months later I decided to start a Greasemonkey fork of my own, which I call Scriptish. Then, as this year went by I discovered Mozilla's plans for the new Addon manager and the stars seemed to align.

So a couple of days ago I finally got around to implementing user script updating in Scriptish, and released the feature in Scriptish 0.1b4 last night; now I'd like to discuss it a little bit for those that are interested.

User Script Updating in Scriptish

@updateURL

You may specify a URL for Scriptish to check for updates using the @updateURL metadata block key. The @updateURL must be a secure url (ie: starts with "https://"), otherwise it will be ignored.

The optional use of ".meta.js"

To save bandwidth, and improve response times, you may optionally provide a ".meta.js" version of your user script as the @updateURL, which should be the same as the user script, but only containing the user script's metadata block. If you provide a @updateURL that ends with ".meta.js", then Scriptish will check then ".meta.js" file for updates, and download an update from the same url where ".meta.js" is replaced with ".user.js".

The option to use the download URL as the update URL if no @updateURL was provided.

If you go to the options window for Scriptish you will see a preference which allows you to use a user script's download URL as the update URL if there was no @updateURL defined; this option is turned off by default.

Using ".meta.js" by default from userscripts.org

If the update URL that should be used to check for updates is a userscripts.org URL, then the ".meta.js" version of the user script will be used by default.

The GM_updatingEnabled constant

All versions of Scriptish that provide user script updating will include a GM_updatingEnabled constant in the user script sandbox, this means that if you are a user script author that has implemented a updater for your script already, then your code can check if this variable is defined and equal to 'true' before attempting to check for updates.

Conclusion

A user script engine that updates user scripts has been long desired, so I'm proud to introduce the feature for the first time in Scriptish, and I hope you give it a try!

Scriptish Beta! A New Greasemonkey

My favorite browser extension for quite a while has been Greasemonkey (GM), I loved how simple it made customizing the web with JavaScript (JS) which allows all users to both filter out the crap that site owners try to provide us (ie: ads, share links, suggested junk) and add new useful features anywhere you that wish. Greasemonkey allowed us to customize the waves as we surfed the web, as well as making a few modifications to the board, and I loved it for that.

Some problems arose with Greasemonkey though, because it was built for Firefox (FF) 1.5, and even the latest GM version (0.8.6) claimed to support 1.5, which meant that Greasemonkey the GM maintainers did not want to introduce new features that would not be available in FF 1.5, and they did not want to take advantage a of newer version, like say FF 3.0's JavaScript Modules (JSM), which would allow GM to use much less memory and improve performance when a user is using multiple windows, and on startup because less JS would need to be loaded at startup.

The good news for GM is that with the next major release, 0.9 (which contributors including myself have been working on for quite some time now) is that the minimum version of Firefox that Greasemonkey will support will be FF 3.0, which means it can now take advantage of the benefits that FF 3.0 offers, finally. The trouble I found while trying to contribute to GM is that the maintainers don't really want to alter the code base, they're actually seem to be fine with doing the minimum necessary and bug fixing for the rest of the future, at least that is how it seemed to me after writing quite a few patches which were rejected, either outright, or by degradation (meaning I would have to basically redo everything, on crappy architecture, because they've sat on their hands for so long). Patches such as using JSM, reg exp @includes, @icon, and a few more.

So the reason that I decided to bypass Greasemonkey and start working on a fork, which I now call Scriptish, is because I was tired of beating my head against a wall, I wanted to have and use the Greasemonkey that I always wanted asap, so I'm making it now. I've always enjoyed working with others, and I hope that I can convince some GM contributors to start working on Scriptish in the future.

Scriptish

Scriptish is a fork of GM, you can think of it as a superset of Greasemonkey, it can do whatever GM does, and more. Some of it's new features are:

  • @author - displayed to users in the addon manager
  • @contributor - displayed to users in the addon manager
  • @homepage or @homepageURL - displayed to users in the addon manager
  • @icon or @iconURL - Include a icon for your user script which is displayed in the addon manager and for notifications from the script.
  • @screenshot - displayed to users in the addon manager.
  • @match - a include pattern introduced by Google Chrome user scripts.
  • @noframes - a simple way to prevent a user script from running in iframes.
  • GM_worker - use a Worker within user scripts.
  • GM_notification - send a Growl style notification to the user.
  • GM_setClipboard - save data to the clipboard from a user script.
If you'd like to do some reading on these features, then checkout the Scriptish wiki, which should explain them all in detail.

Other changes include major reorganization of the internal code, using JSM, in a way that only loads code into memory that will actually be used, by loading the req'd code the first time that it is going to be used. I was also able to remove a bunch of legacy code, because Scriptish will only support Firefox 4.0 or higher for the moment.

Give it a try!

If you'd like to give Scriptish a try, then you can download Scriptish here. If you want to get involved here are some links for you:

I hope you like it, I'll be working on it for the next few months at least, pretty hard I imagine; I'd like to make it restartless, and implement user script updates & communication asap. If you think you'd like to open it up and hack on it, then please do!!

Webxtend

Something that I don't think many web developers and extension developers seem to be picking up on, but yet some are, is what I'm calling webxtending, which is providing APIs that allow their users to extend their website/webapp or extension with a browser extension. To illustrate this idea take the Gmail-Greasemonkey API, this is a front-end JS API that a user script can easily access to be notified of Gmail's custom events, change state variables, and perform actions with the Gmail interface (afaik the api used to work, and some parts do not work at the moment). This was a wonderful idea because it allowed user script authors to save a lot of development time, also maintenance time, and write more cpu efficient scripts. Furthermore I imagine such a API would be extremely useful to internal development, it would at least reduce the chance of sloppiness.

Webxtending doesn't always mean providing a Javascript API though, it often simply means writing semantic code by using microformats, and structuring the markup in a logical manner, taking into account how others may want to change the UI, and making those changes as easy for them to make as possible. This means using the id attribute even when you don't need it, because others may, but it also means thinking carefully about every facet of the structure. XUL overlays used in Firefox extension development are a good example of this structural extendibility idea.

Webxtend is a gold standard quality requirement, if your website or browser extension is not webxtendable then it's not gold standard quality. =p

The Principle of JavaScript Illumination

Every web developer knows that their site should work when the site's users disables JavaScript, but I constantly find this principal is violated when it is simple thing to obey. When web UI developers violate this principal it shows an extreme laziness and a failure to understand good practices on their part; this can greatly worsen the user experience. So, I'd like to articulate what I'm going to call The Principle of JavaScript Illumination which is an idea I haven't seen articulated yet, but that everyone knows in their gut they should do, and yet I hardly ever see this principal adhered to in practice.

Definition

The Principle of JavaScript Illumination is a simple one, if some user interface element will not work when JavaScript is disabled, then have JavaScript add the element.

Implications

If you have a user interface (UI) element that uses JavaScript, then make sure there is a default functionality that will work when JavaScript is disabled. If the UI element cannot work at all without JavaScript, then you should make JavaScript add the element, this way if JavaScript is disabled then the user will not see UI elements that will not work, causing a bad user experience.

Examples

Facebook Chat

When you log in to Facebook with JavaScript disabled, the chat widget is displayed to you on the bottom right of the screen, but it doesn't do anything, it's completely useless to you and it's just in the way, so why the heck is it displayed to the user?

Kampyle

Kampyle provides some widget code for anyone to put on their site which adds a "Give Feedback" button to the bottom right of the user's screen, which the user can click on to give feedback for the site. The trouble is, when JS is disabled, or when the user uses NoScript to block assets from the kampyle.com domain, then the button doesn't work (see ROIRevolution's site for an example of this), so why the heck is it displayed to the user?

Conclusion

Be a good web citizen, and a smart web developer by obeying The Principle of JavaScript Illumination, because it's the right thing to do and because you're users will thank you for it.

Upcoming Jetpack Presentation @ VanJS

For those that are interested, I will be giving a presentation on Mozilla's Jetpack to the VanJS meetup group in Vancouver on May 19th. Click here for more information.

VanJS: Canvas and Jetpack

When
May 19th

Location
SFU Harbour Centre
515 W. Hastings St
Vancouver, BC V6B 5K3

How to find us
"Room 1600 is left from the main lobby, past the Terasen Theatre we used in March."

Agenda
Mozilla Jetpack extensions: Erik Vold
Canvas graphics: Dave Shea
Beers afterward

Using Web Workers Today

I did a little reading on Web Workers a few months ago, and I recently used them in a little test I was asked to perform, so I thought I would share what I've discovered since I don't see much in Google search results about how to use Web Workers in a cross browser fashion with graceful degradation.

Web Workers

For those of you that do not know what Web Workers are I suggest you read the Web Workers Draft Recommendation, but in brief they provide web developers with the ability to create thread-like operations.

This is obviously useful for long operations. The standard way of dealing with laborious tasks like these in the past was to split up the work with multiple setTimeout() calls.

setTimeout

Before Web Workers, setTimeout() had to be used to split up long operations in to multiple small operations, because you only had a single thread, so this had to be done in order to avoid having an unresponsive UI or triggering a unresponsive script warning.

Current Browser Support For Web Workers

Currently the latest versions of Firefox, Chrome, and Safari all support Web Workers; IE and Opera do not support Web Workers.

So how do you detect if a browser supports Web Workers programmatically? this is how:

if( Worker /*check for support*/ )
// Web Workers are supported
else
// Web Workers are not supported

Minor Note For Safari

While Safari supports Web Workers it requires that strings be used when passing messages, which is something that Firefox and Chrome do not require. Firefox and Chrome both stringify anything not already a string that is crossing the bridge to or from a Web Worker, but Safari does not do this, so you must. This is a simple thing to do, but one that you must remember, always use the JSON object to JSON.stringify when sending a message and JSON.parse when receiving a message (if web workers are supported, then so will the JSON object be).

Supporting Browsers That Do Not Support Web Workers

The biggest hurdle for most to implement Web Workers today is the fact that not all browsers support them, but the fact is that it is simple to degrade gracefully, thus allowing those users that are using browsers that support web workers to start reaping the rewards, and allowing you to improve your site and be prepared for when IE and Opera finally come around.

So what is the best way to handle the browsers that do not support Web Workers? well I see two options:

  1. Maintain a secondary script to be used when Web Workers cannot be, and add it to the page by creating a script element and appending it to the document.body.
  2. Make the Web Worker friendly to being used in a window scope instead of a Web Worker's scope so that it can be simply added to page like I describe in option 1.

Option 2 will be the better approach in most cases I suspect, because it means that two separate files with two slightly different methods of achieving the same task do not need to be maintained. In order to implement option 2 two there are some things to consider:

  • You cannot use functions like postMessage, onmessage, or importScripts while in the window scope.
  • You will need to split the work up with setTimeout (which is available in the Web Worker scope as well) to avoid an unresponsive UI and unresponsive script warnings.
  • You should assume that your Web Worker will be used in the window scope, so do not pollute your global scope.

There are a couple of ways to determine if you are not in a Web Worker's scope, as to avoid using the postMessage, onmessage, and importScripts functions (which are available to Web Workers) when your script is run in the window scope (postMessage has another purpose in the window scope). The first method is this:

if(typeof importScripts == "function") {
// web worker scope
}
else {
// not web worker scope, assume window scope
}

The other method is to check if you are in the window scope:

if(typeof window != "undefined") {
// window scope
}
else {
// not window scope, assume web worker scope
}

Or, combining the two methods above:

if(typeof window == "undefined" && typeof importScripts == "function") {
// web worker scope
}
else {
// another scope
}

These methods will allow us to avoid using postMessage, onmessage, or other methods we use while in the scope of a Web Worker.

To avoid polluting the global space normal tactics apply, I would simply suggest wrapping all of the code in an anonymous automatically executed function, like so:

(function(){
// this is an anonymous automatically executed function
})()

Putting these methods together, we would get web workers like so:

(function(){
var winScope = true;
if(typeof window == "undefined" && typeof importScripts == "function") {
// web worker scope
winScope = false;
}

if(!winScope) {
onmessage = function() {
postMessage("blah");
}
}
else {
alert("blah");
}
})()

Except that real Web Workers would do interesting things, and use setTimeout.

Making All Internal Link Clicks a Conversion

A common use case of Google Website Optimizer (GWO) is to test (in order to optimize) a page's ability to have a user click an internal link, I like to call this the internal pass thru rate of a page. Meaning the click event of any internal link on a test page will count as a conversion for the test. This is not the opposite of the bounce rate which it is sometimes mistaken for, because the pass thru rate is oblivious to the difference between first time visits and other visits. The pass thru rate is not the same as the click thru rate either, because the pass thru rate relates to visits and the click thru rate relates to impressions. This is usually desired either because you have no other conversion to track -- although time on page is always another option, but the results typically take longer to acquire -- or because you wish to optimize the bounce rate, and this is as close as you can get at the moment.

Google doesn't have any documentation on how to track all internal links on a page, but they do provide a page on how to track an individual link, which any novice JavaScript programmer can follow in order to track all of the internal links, by simply repeating the process for every internal link on the test page(s). The main problem here is that Google's process requires that the onclick attribute of every link be set to "return false", albeit this can be done via JavaScript, it is not desirable. Google Website Optimizer's Technical Lead Engineer, Eric Vasilik, explained Google's method in a blog post from August 2009, called "Tracking Outbound Links -- The Right Way", and I wrote a follow up post in December 2009, called "Tracking Outbound Links - The Really Right Way" in which I describe a better method which is slightly harder to implement but does not require any use of the onclick attribute.

If you are at least a novice JavaScript programmer that wants to measure the pass thru rate of a page, then you can probably implement Google's method or mine with relative ease after reading the blog posts I mentioned.

If you are not a novice JavaScript programmer, or simply want to save some time implementing this type of conversion over and over again, then I would suggest you take a look at the JavaScript click track library that I released in early December '09, because with this javascript library you could simply add the following code to your page:

clickTrackingLib.addMatches([{
match: clickTrackingLib.getMatchPreset( "all-internal" ),
trackingFunc: function(e, link){
var gwoTracker=_gat._getTracker("UA-XXXXXXX-X");
gwoTracker._trackPageview("/YYYYYYYYYY/goal");
}
}]);
clickTrackingLib.attachTrackingFunctions(null,99);

The above code will tag all non-rel-external links or links with the internal hostname on the page; for just internal hostname links replace "all-internal" with "internal-hostname". I would recommend that you wrap the above into a function though, to be run on the DOM ready event or page loaded event, with jQuery that would look like:

$(document).ready(function(){
clickTrackingLib.addMatches([{
match: clickTrackingLib.getMatchPreset( "all-internal" ),
trackingFunc: function(e, link){
var gwoTracker=_gat._getTracker("UA-XXXXXXX-X");
gwoTracker._trackPageview("/YYYYYYYYYY/goal");
}
}]);
clickTrackingLib.attachTrackingFunctions(null,99);
});

The first line in the code above will setup a function to be executed when the DOM is ready, and all of the page's links have been added. The second line is the first of the function the be executed on DOM ready, and it is adding an array of match objects to the global clickTrackingLib object provided by the click tracking library that I wrote. The following 5 lines define a single match object for the input array, which consists of a preset match function to match internal links (as I said already this preset can be changed, and you can also define a custom match function), and a simple tracking function. When clickTrackingLib.attachTrackingFunctions(null,99); is executed, links are tested against the match functions in the clickTrackingLib object's match object array, and where there is a match the associated trackingFunc function is made in to a onclick event listener for the matched link. As for the two inputs, the null means try all links, and the 99 means use a 99 millisecond delay.

At this point you might be wondering, what if the page has dynamic content, which is changed via ajax, well in that case you can round up the new link(s) in to an array and run the clickTrackingLib.attachTrackingFunctions(links, delay) function again like so:

clickTrackingLib.attachTrackingFunctions(newLinksArray,99);

clickTrackingLib.attachTrackingFunctions(links, delay) will also accept a single link.

Greasemonkey Optimization: Convert2RegExp

Over the last week I've been spending a great deal of time looking over the Greasemonkey and Webmonkey source code, both because I want to understand both code bases more, but also because I'm interested in Firefox extension internals in general. While looking through these two code bases I saw a common file which could be optimized. This file was the convert2RegExp.js file, which looks like it came from Adblock at some point, at least in part. In fact I saw a number of changes that I could try in order to speed up the function, so I decided to time them all.

Test Factors

Factor I: Check for /\.[^\.ld]*t[^\.td]*l[^\.lt]*d/ in pattern string character loop

The first thing I noticed was that the regular expression that checks the pattern string for a ".tld" string was being run on every pattern input, this seemed obviously bad to me since there was a loop prior to the regular expression which runs through the pattern string's characters, so why didn't that loop at least check for the ".tld" substring first? the check could be as simple as making sure all of the required letters exist in the string first, or make sure that the ".tld" substring, exactly, is in the pattern string, or what I found to be the best way was to make sure the regular expression /\.[^\.ld]*t[^\.td]*l[^\.lt]*d/ matched the pattern string via the character loop through the pattern string. The average cases and best cases will go much faster despite the fact that the latter case would be matching some pattern strings that following tld regular expression (that is already in convert2regexp) would not match, thus just adding work in this case, and making the worst possible case even slower.

Factor II: Cache tldStr and tldRegExp

The convert2RegExp( pattern ) function creates the tldRegExp and tldStr from literals on every execution! that might be a faster operation I thought, but I expected it to be slower than simply looking up the value of a variable when I timed it, and even though I haven't figured out how to test the memory consumption yet, I expect creating a large string from a string literal over and over again would increase the peak memory usage, and thus the garbage collector pause time as well. So, caching the tldStr and tldRegExp seemed like it would be a small win.

Factor III: Use array.push()/array.unshift() and array.join() instead of +=

From what I've read about Firefox 3.6 the += operator in loops is optimized to use a single StringBuffer, but there were a few += outside of the loop, and I figured some people will probably use versions of Firefox < 3.5 for some time to come still, and using an array there would certainly improve/decrease the peak memory usage. I didn't know what the affect on performance would be, but this change is commonly said to be the better approach for JavaScript in the past to present, mainly because of the memory issue. Furthermore, the more memory that is used, the more work there is for the garbage collector to deal with, which means your computer is even slower, and that time is hard to measure. I do know that a new feature to Firefox 3.6 is that the garbage collector frees the memory in a new thread, which means that older versions of Firefox did not, and that means that the chances of pauses were even greater. For more reading on these changes I am mentioning in Firefox 3.6 please read this article.

The Tests

Description

In order to test the factors listed above I knew I needed a number of different versions of the convert2RegExp function, but the other piece I needed was a collection of pattern strings to test. I made the different versions of convert2RegExp easily, but when it came to making the pattern set(s) to test I had to do some more thinking.

Test Pattern Sets

I may not have made the best choice, but I decided to use two pattern sets:

  1. Pattern Set 0: 3/50 patterns use the magic tld expression, 2/50 do not use the tld expression, but match /\.[^\.ld]*t[^\.td]*l[^\.lt]*d/
  2. Pattern Set 1: 2/50 patterns use the magic tld expression
In retrospect I think testing an even worse case might be advantages, although I don't suspect the tld expression is used very often. It's hard to say however, I would like to see an audit of userscripts.org, but even with that it'd hard to know what the average case is for all Greasemonkey users, even when you take the install counts from userscripts.org into account, although all of that perspective would be nice to have.

I am an avid user of Greasemonkey and I write quite a few userscripts, and I find that in practice that there are very few times when I would need to userscript to use the magic tld expression, so my feeling is that the ratio is probably closer to 1 tld pattern per 100 or more.

Test Pages/Versions

I decided to run my tests of the different factors listed above for FF3.5/FF3.6 on WinXP, OSX, and Ubuntu (only FF3.5). The test pages worth pointing out are:

  • Original
  • Alternate 3: test of only factor I
  • Alternate 5: test of only factor II
  • Alternate 6: test of factor II + minor change to return a value asap.
  • Alternate 11: test of factor I + II, uses array.unshift() and array.join(), and returns a value asap
  • Alternate 12: test of factor I + II, uses array.push() and array.join(), and returns a value asap
  • Alternate 13: test of factor I + II, and returns a value asap
  • Alternate 15: test using array.unshift() and array.join() as only change

Test Results

Here are the tests I ran and the results I record (the links are to published google docs spreadsheets, all values are in seconds):

AMD 2.21Ghz, DDR2, WindowsXP

Intel Duo 2.0Ghz, DDR3, Mac OSX

Intel Duo 2.0Ghz, DDR3, Windows XP

Intel 2.0Ghz, DDR, Ubuntu

Each test was using a version of convert2RegExp, on a set of 50 patterns, 2500 times.

Results

  • In all cases alternate 3 was faster than the original.
  • In all cases alternate 5 and 6 were faster than the original.
  • In all cases alternate 6 seemed to be slightly faster than 5.
  • In all cases alternate 15 is much faster than the original for FF3.6, and slightly slower for FF3.5.
  • For FF3.5 alternate 13 > alternate 12 ~= original ~> alternate 11
  • For FF3.6 alternate 11 > alternate 13 > alternate 12 > original.
Although for FF3.5 we know that alternate 13 is a bad choice because it uses the += operator which result in O(N^2) characters copied, so we can't use that in my opinion. The final choice has to be between alternate 11 and alternate 12.

This is where I need your help, I have no idea why using array.unshift() is so much faster in Firefox 3.6, and taken that it is, and that the majority of Greasemonkey users will be using the latest version of Firefox, should we use array.unshift() and not array.push()?

Tweet Me!

Tracking Outbound Links - The Really Right Way

About 5 months ago Eric Vasilik wrote a post on his GWO Tricks blog about Tracking Outbound Links -- The Right Way which was ment to point out a solution for the problem with the technique outlined in the Analytics Help Center article called "How do I manually track clicks on outbound links?", is that it suffers from a Race Condition which may mean the tracking doesn't take place. If you haven't read Eric's article, please do.

Well as readers of my blog would know, I recently wrote a JavaScript click tracking library which can be found at GitHub here. In the blog post I mention that I implemented Eric Vasilik's "Right Way" to track links, but today I started thinking about this some more.

I decided to do some searching on how to cancel a click event, and I found a great old blog post from 2006 by Ryan Campbell pretty quickly which mentions a method implemented by Prototype called Event.stop(). So I decided to dig in to the Prototype code to see what Event.stop() did exactly, so I could extract it, and I found that it does two things, one is to stop the event propagation, and the second is to stop the default behavior for the event. At the end of the post Ryan said that it not work for Safari 2.0.3, which is why it could not be relied on at the time, but today Safari 2.0.3 is dead, and I tested Safari 3 out which works. In fact I decided to do some PPK style testing and try test cases on every browser I could.

After seeing that Event.stop() had two purposes I wanted to find out why this was done, and found this documentation page for Event.stop(), which says this was done "because stopping one of those aspects means, in 99.9% of the cases, preventing the other one as well", but in the case of simply click tracking we don't really want to stop the event propagation, we just want to stop the default action from being triggered eventually. So here is what my update to Eric's example looks like:

<a id="example" href="http://www.example.com">Click me</a>
<script type="text/javascript">
function doGoal(e){
if(!e) var e = window.event;

var targ;
if ( e.target ) targ = e.target;
else if ( e.srcElement ) targ = e.srcElement;
// Safari..
if ( targ.nodeType == 3 ) targ = targ.parentNode;

setTimeout('document.location = "' + targ .href + '"', 100);

try{
var pageTracker=_gat._getTracker("UA-123456-1");
pageTracker._trackPageview("http://www.example.com");
}
catch(err){}

if (e.preventDefault) e.preventDefault(); // w3c
else e.returnValue = false; // for ie
}
if ( document.body.addEventListener ) {
// w3c
document.getElementById('example').addEventListener( "click", doGoal, false );
}
else if ( document.body.attachEvent ) {
// ie
document.getElementById('example').attachEvent( "onclick", doGoal );
}
</script>

To try the code above see the test page here. I have tested this out on IE6+, FF2+, Opera9+, Safari for the iPhone OS 3.0, Google Chrome, and Safari3+ on MacOSX and WindowsXP and they all work, so I am pretty confident that this is going to work for something like ~99% of web users today, and since it would not error even for a user using Safari 2 (which I wasn't able to test), and Safari 2 is so slow, there is a good chance the user would still be tracked anyhow.

If anyone else could try this out on some other browsers at let me know what they find good or bad that would be nice so that I can add them to the list above. The best way to test the test page is to comment out the setTimeout line, and make sure that clicking the link doesn't do anything.

The beautiful part about this method is that it is totally unobtrusive JavaScript code, and will work even if you are using the onclick attribute for other site functionality (even though you shouldn't ever use the onclick attribute).

Merry Clicking!

P.S. The Click Tracking JavaScript Library has been updated to use this really right method, check it out if you haven't already!

More Entries

© Erik Vold 2007-2013. Contact Erik Vold. Top ^