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