Fun hack of the week

I recently wrote an interesting solution to a problem, and thought I’d share…

The Problem.

I have multiple DOM nodes, scattered across multiple pages and windows, which I may want to modify in the future. Code already runs once per node (so that’s free), but I want to make future updates fast and avoid dealing with document lifetime issues… The first rules out wading through the DOMs of a thousand tabs, and the second rules out simply storing the nodes in some JS array (which risks leaks/bloat by keeping long-lived references to content DOMs).

Specifically, this problem is for bug 550293, where I’m keeping the crashed-plugin UI in sync across multiple tabs when submitting a crash report.

The Solution.

[I’m sure there are multiple solutions, but this one fit in well with the other things the code was doing…]

First, I decided to drive things by notifications and observers. The code that was already running (to initialize the crashed-plugin UI) now adds an observer for each instance. To update them, I just fire a notification when needed.

But I don’t want the observer service causing leaks by keeping the observers and nodes alive, so when adding the observers, they’re added with a weak reference (see nsIObserverService.addObserver()).

Oh, but now there’s a new problem — if nothing is holding a strong reference to the observer, it will be garbage collected at some random point in the future. [This can be a confusing bug; the observer works fine if you trigger it quickly enough, but if you’re slow in testing it will seem to randomly fail!]

So, here’s the clever bit… To keep the observer alive, but not so alive that it outlasts the expected document lifetime, I add a dummy event listener to the document that also holds onto the observer (directly, or via a closure). When it’s time for the page to die a normal death, it clears its event listeners, which clears the strong-reference to the observer, which allows it to be GC’d. [I don’t know why it swallowed the fly, perhaps it’ll die…]

Summary.

let someDOMNode = ...
let observer = {
  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  observe : function(subject, topic, data) {
    // do stuff with someDOMNode...
  },
  handleEvent : function(event) {
    // Not called.
  }
}
observerService.addObserver(observer, "some-interesting-topic", true); // weak reference
someDOMNode.ownerDocument.addEventListener("dummyEventName", observer, false);

Not quite smooth as buttah…

So, I finally got around to watching this video which has been been making the rounds as of late (not a Rickroll, I swear to god! WHY DOES NO ONE TRUST ME?! :-). I fired it up the 1080p version in fullscreen mode, and was slightly disappointed to find that while the video was playing back rather well, it wasn’t quite perfect. As the beer commercials say, “rich, but not smooth.”

So I next loaded it up in Safari, to see how it compared. Previous versions of Firefox have had some jerky-video problems (due to running garbage collection at bad times or doing too much main-thread disk IO for session restore), so I was fully prepared to file a bug on the problem — smooth playback in another browser would be a strong indication that something in our code was causing the poor performance.

However, what I found was that playback in Safari was much much worse that on the trunk Firefox build I was using. While Firefox yielded good playback with an occasional glitch, Safari gave a consistently poor playback of around 3 fps.

There’s one clear, inescapable conclusion here: Flash sucks. 🙂 But seriously, folks, I’m happy to see that trunk builds perform well. I should still file a bug to investigate if we’re responsible for the occasional stutter in playback (or if that’s just Flash), and if results on Windows are similar to OS X or not. But I’m still pleased to see that this arbitrary trunk nightly — not even beta quality — is doing a good job.