Quantcast
Channel: Silk Engineering Blog
Viewing all articles
Browse latest Browse all 32

Using DOM Mutation Events for node stack traces

$
0
0

The Silk client is a single-page application with lots of moving parts. Being new to the codebase, I often find myself wondering when and where a certain page component is created. Basically: which function inserted this particular DOM element? There’s a simple trick to find out.

Remember DOM Mutation Events? Widely mocked for being “verbose, slow, and crashy,” they’re marked for deprecation in the DOM spec—but they still work, at least in Chrome and Firefox. And for this use case, their flaws are actually what make them useful: they let us hook into DOM node insertion with the call stack intact.

This behavior is problematic for many use cases, and it incurs a performance penalty. So with the newer API, Mutation Observers, handling is postponed and batched into entire subtree insertions, which is more efficient. But it also means useful info is lost; in particular, there’s no way to find out anything about the execution context of the code that inserted each node. With good old mutation events, it’s trivial!

So the basic idea is just to attach a mutation event handler for DOM node insertion that captures the current stack trace and saves it for debugging purposes. I chose to save the stack trace as a data attribute on the node itself; this way I can see them easily in the browser’s DOM inspector. Here is the simplest way to do this (pardon the jQuery):

$(document).bind('DOMNodeInserted', function (e) {
  $(e.target).attr('data-trace', (new Error).stack);
});

With this handler in place, every dynamically inserted DOM element gets a property with a stack trace from its point of insertion.

With many such elements, the DOM inspector becomes cluttered with big stack traces. To clean it up a bit, we can do some substitutions on the stack trace. This is messy, since the format of the (non-standardized) Error#stack property differs between JavaScript engines. Here’s a quick hack that works in Chrome:

$(document).bind('DOMNodeInserted', function (e) {
  $(e.target).attr('data-trace',
    (new Error).stack
      .split("\n").splice(6).join("\n")
      .replace(/( +at )|\([^()]+\)/g,"")
      .replace(/\n/g,"/ "));
});

This results in a one-line stack trace without source locations, skipping a few uninteresting lines related to the event handler itself. It’s useful if you only need a bit of context.

To make this useful across browsers, one could use the Stacktrace.js library, which has a whole arsenal of clever regular expressions to get uniform stack traces. This is left as an exercise for the reader…

Another fun trick is to also add some styling to the dynamically-inserted nodes; jQuery UI’s highlight effect, perhaps? A simple border is nice, too.

Finally, this little debugging snippet (minus the jQuery) is also suitable as a bookmarklet, in case you want to debug a site without changing its source. This is the bookmarklet I use for Chrome. Try it on your favorite site with dynamic content!

Since DOM mutation events are thoroughly deprecated, this trick has a tragic destiny. It would be a nice feature to have built into the browser debugging tools. But until that day, let us celebrate the sunset of this star-crossed API.

Silk is hiring! Check out jobs.silk.co to see if that might be something for you or someone you know


Viewing all articles
Browse latest Browse all 32

Trending Articles