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);

About Justin Dolske

Mostly harmless.
This entry was posted in PlanetMozilla. Bookmark the permalink.

2 Responses to Fun hack of the week

  1. That’s an interesting approach, thank you! I had to solve a similar problem with Adblock Plus not too long ago and ended up keeping a list of weak references to DOM nodes as well as using some tricks to remove these weak references once the nodes have been garbage collected. Your solution sounds simpler and seems to be more robust – if the performance is ok then I might start using it as well.

Comments are closed.