Over the weekend I rolled out a build that provided call stacks for certain errors, under certain conditions. You only get a call stack if:
- The error occurs in Internet Explorer,
- The error happens after page load
I must admit, because of these conditions I was unsure how useful the feature will be. But after having it run for some time now, it looks awesome!
If we were able to capture the call stack of an error, it is highlighted in the errors listing.
The call stack is shown in the error details page for the error.
How it works
One interesting behaviour of IE is that when
window.onerroris called, it doesn't actually destroy the call stack — in fact, the
window.onerrorfunction call is placed on the top of the currently executing stack. This is different from how all other browsers behave. We exploit this behaviour in IE.
However, IE doesn't give you a nicely formatted stack trace. In fact, there's no explicit way to get the call stack at all. However, IE does give you a meaningful
arguments.callee.caller, which is used here. We then recursively walk the call stack using
arguments.callee.caller.calleretc. to build individual stack frames of the error. We do this for 10 stack frames and stop there, just in case the call stack is for a recursive function.
.caller, which is a reference to the function that called it. In other words, it's a reference to the function one stack frame below itself.
In the case of
arguments.calleewill refer to the
window.onerrorhandler itself, since it points to itself. Once we have a handle to our own function, we can then access properties of our function, as described above.
There's one caveat though. Only IE retains a meaningful
arguments.callee.caller. In all other browsers, this value is
null. That's because all other browsers destroy the call stack before calling
window.onerror. IE retains the call stack. So, since we have a call stack in IE, we can now recursively call
.callerfor each function in the stack to know the previous function in the stack.
Why after page load?
As I've mentioned several times before, Errorception will always maintain a zero performance cost. I wouldn't use a error reporting system that would cause page load delays for every single user. To ensure that the performance cost is zero, Errorception introduces it's script after page load. This doesn't mean that we don't catch errors from before page load, of course. We do. But we only process them once our script has loaded.
Because of this loading pattern, we won't have access to call stacks from before page load.
arguments.callee.callerwill always point to null since the stack has already unrolled. This is why we cannot generate call stacks before page load, even though we have all other error details.
But that sucks!
Not really, if you think about it. While it would be awesome if we could get call stacks from before page load as well, these errors are really easy to replicate. That's because the error happens in such a small and predictable time interval that it's easy to recreate it locally. The error happens between when the page started loading and when page load was fired - usually within a couple of seconds, maybe before any user interaction has even occurred.
In a lot of cases, the call stack consists of anonymous functions, which isn't very descriptive. I would ask you to rewrite your code using named anonymous functions, but we all know that no one's going to do that. I'm still on the lookout for a decent way to solve this. If you have any suggestions, I'm all ears.