[Posted by Joanna Power]
Cozi builds family organization software, including a free online calendar, shopping list and family journal.
In order to provide a richer experience for the users of our web application, over the past year we have been transitioning more and more of our code from
C# running in an ASP.NET application to JavaScript running in the
browser. We are using the jQuery JavaScript library for event
handling, DOM manipulation, effects, styling and AJAX calls. In the process of this
migration, we lost our ability to easily track errors and
exceptions using an error log on the application server. Not
knowing what errors our users might be encountering when they use our product made us nervous, so we set out to regain complete error
tracking capabilities. Our first attempt to solve the problem was to
attach a custom JavaScript handler to window.onerror. This handler
called back to the application server to log an error; an example
handler might look like this:
window.onerror = function(msg, url, lineNo)
{
trackError(msg, url, lineNo);
}
This attempt largely worked, but as we started paying close attention
during development, we noticed that not all JavaScript errors were
successfully reported to the application to be logged. Specifically, we
were missing some Firefox errors. Investigating further, we discovered
that an error thrown once the page was loaded and the browser event loop took over was
reported and
tracked for IE(6/7), but it was not reported and tracked for
Firefox(2/3).
You can see evidence of this in
the example page. This page
throws two errors when it loads: one during the load itself and the
other in the jQuery document.ready handler. A third error
is thrown when the button is clicked. The custom window.onerror
handler shows an alert with the error message and any other details
that are available. Try it in Firefox and again in IE. You'll see only
one alert in Firefox, but you'll see all three in IE.
I googled and googled, but I found nothing that suggested what the
problem might be. It was
either one of those situations where you had to know the answer to
craft the query that would find the answer, or else no one else had
struggled with this problem. Since we use jQuery as the glue in our
event-driven application, I sought help from
the jQuery Development group.
It took some time, but help arrived. (Thanks, dhtmlkitchen!) As it turns out, in Firefox, an error thrown by code
in an event handler attached
using addEventListener does not make it to
the window.onerror handler. There is an
existing tracking issue for this bug at Mozilla: issue #312448.
The jQuery
bind function
uses addEventListener in Mozilla
and attachEvent in IE.
That explained it. All of our application behavior once the page loads
is triggered by user interaction, and we
use jQuery bind to attach handlers to the
appropriate JavaScript DOM events. In short, most of our application's interesting
code, i.e. the code most likely to cause errors, runs
as a result of an event handler being triggered. These errors were
arriving at window.onerror for IE, but due
to the Mozilla bug, not for Firefox.
On then to the fix. It was clear that wrapping code with try/catch
blocks was necessary, but there was no way we were going to
successfully do that in every handler function. It took some fiddling, but overriding
jQuery bind with a version that wraps the
provided handler function in try/catch blocks does the
trick:
// override jQuery.fn.bind to wrap every provided function in try/catch
var jQueryBind = jQuery.fn.bind;
jQuery.fn.bind = function( type, data, fn ) {
if ( !fn && data && typeof data == 'function' )
{
fn = data;
data = null;
}
if ( fn )
{
var origFn = fn;
var wrappedFn = function() {
try
{
origFn.apply( this, arguments );
}
catch ( ex )
{
trackError( ex );
// re-throw ex iff error should propogate
// throw ex;
}
};
fn = wrappedFn;
}
return jQueryBind.call( this, type, data, fn );
};
The bind override is inelegant in that it needs to know too much about
how the original jQuery function shuffles parameters
around, but if and when it breaks we'll just need to fix it in
one place.
Since bind function is used under the covers
by short-cut functions like click, hover,
etc, this one-time fix takes care of tracking almost all application errors.
Note that to be completely thorough, we should also
override the jQuery one function.
To catch errors that are thrown during page initialization, we also
wrap document.ready functionality in try/catch
blocks:
$(document).ready( function() {
try
{
initializeEverything();
}
catch ( ex )
{
trackError( ex );
// re-throw ex iff error should propogate
// throw ex;
}
} );
Try out the fixed version of
the example page. Like
the first example page, this page
throws two errors when it loads: one during the load itself and the
other in the jQuery document.ready handler. A third error
is thrown when the button is clicked. The window.onerror
handler shows an alert with the error message and any other details
that are available. Try it in Firefox and again in IE. Now you'll see
all three alerts in both Firefox and IE.
What About Safari?
Safari does not yet
support window.onerror, which means that without the
steps described above, we wouldn't be able to track any errors from
Safari. The try/catch additions to document.ready
and jQuery bind will allow us to track most, but not all,
errors arising in Safari.
Further Reading
If you want to learn more about the extremely cool jQuery JavaScript library, check
out jquery.com.
There is a good explanation of how to implement JavaScript error tracking using window.onerror
on Matt
Snider's blog, though it does not mention
the attachEventListener bug.
If you want a refresher on the fundamental differences between event
handler attachment in Mozilla and IE (addEventListener
versus attachEvent), there is
a good explanation at QuirksMode.





This was a very insightful post and I've been having similar problems and, just like you, couldn't find much about it on the web besides this post.
Thanks,
Tom
Posted by: tom | August 13, 2008 at 07:28 AM