One of the hardest questions for me to answer is, “Why are you doing
this?” It’s also an excellent question, one that checks what I am
working on. I recently found myself asking this question when trying to use
something I’ve never tried to use before: weak references to DOM elements.
What are weak references?
For those of you coming from JavaScript-land, you know that once you hold
a DOM element as a property, it will last as long as the object you attached
that property to. (Depending on the usage, longer.) This is because XPConnect
generates a “strong” reference to the element, which through the magic of
XPCOM reference counting, requires the element not be deleted until all
strong references are gone – including yours. Weak references are different.
They are objects which hold a pointer to your real target, but they don’t
require the real target “stay alive”. In JavaScript pseudo-code, it looks a
little like this:
function getWeakReference(obj) { var weakPtr = { _realPtr: obj, QueryReferent: function(aIID) { if (this._realPtr) return this._realPtr.QueryInterface(aIID); return null; }, QueryInterface: function(aIID) { if (aIID.equals(Components.interfaces.nsIWeakReference) || aIID.equals(Components.interfaces.nsISupports)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; } }; return weakPtr; }
There’s one thing this code can’t show you: Some time in the future,
weakPtr._realPtr
could be destroyed, and set to null. That
doesn’t happen in normal code (thank heaven!). But with weak references, it
can happen at any time.
Why are you doing this?
There’s a long list of sub-projects and sub-sub-projects, etc., each of
which seems to require I look at something I hadn’t tried yet. Here’s the
current list, and how I started thinking (wrongly) I needed weak
references.
(Continued on the next page…)
- Existing XML and HTML editors do a fraction of “what I want to do”, and
they’re not easy to extend to meet my vision. So I’m building my own
extensible editor, Verbosio. That way, others can extend it any way they
want. This is the classic “Scratch an itch” answer,
especially since I think it would be really, really useful to others. - One of these “What I want to do” items is is to take a template and
build XML markup from it – say, a XUL grid from a grid template. So I
need to write a language to define templates. - Crafting a new language isn’t enough – you need a
specification, an implementation, and test
cases. These three verify that the markup templates
language works, and the test cases become real examples of how to use the
language. (Note: This paragraph deserves its own blog entry – you really
do need a spec, implementation and test cases!) - One key test of the template language is the ability to build a
template. No templates in this language exist (it’s a brand new
language), so no one (including me) can know how to write one without
some help. Therefore, I need to write a tool to guide template
construction in a separate XUL wizard window. - In building templates, the user can copy, paste and edit in place the
same basic code. (For example, you could have a template for ordered
lists like this one, and each item would be a copy of basic list
item elements.) It would be a key part of this template to mark
fragments of code as “repeatable”, and provide controls for
copying and pasting these fragments. - A template author also wants to define minimum and maximum
numbers of copies for the repeatable fragments, so my template
construction wizard should support that. - Updating a repeatable fragment’s minimum copy count requires
its own specification, implementation and test cases. I
struggled with this for months, until I thought of XUL’s
broadcast/observer model. - The broadcast/observer model is good, but the implementation isn’t
flexible enough. I need to get and set both JavaScript properties
and XML attributes. So I decided to re-implement the model with
XML attributes. - Observing attributes, setting properties and setting attributes are all
trivial. However, observing properties directly on DOM elements isn’t.
They don’t support Object.prototype.watch, which means I need to find
some other way to detect changes. The simplest I can think of is
timer-based observations of properties of the DOM
elements, comparing previous values to current ones. - This requires the timed observers ask the element for properties, but
we don’t want the observers to keep the element around after the element
would otherwise die. A strong reference keeps the element from dying.
Therefore, timed observers should hold weak references to the
element. - I tried using Components.utils.getWeakReference(), but that quickly led
to a crash. At Boris Zbarsky’s suggestion, I then tried the element’s
GetWeakReference() method, which returns ansIWeakReference
object. Unfortunately, I didn’t see many examples of
nsIWeakReference
in use, so I figured I needed to
write a testcase for nsIWeakReference objects from
elements. - In writing the XPCShell testcase, I found that DOM elements from the
test weren’t being deleted (resulting in ansIWeakReference
object without a real element pointer). Forcing JavaScript garbage
collection didn’t fix that, as this was a XPCOM cycle collector task.
There’s also (currently) no obvious way to force XPCOM cycle collection
from XPCShell, so I realized I needed to add XPCOM cycle
collection to Components.utils. - At which point I realized I couldn’t visualize the chain from “write a
language to define templates” to “writing a testcase for
nsIWeakReference”. It was just too much for me to see the connections. I
asked myself, Why am I doing all this?
Hence why I started writing this blog article, to analyze my own planning
and determine if I really needed to do all the steps. In writing this, I
realized that I’d made an error in my logic. Everything from item 4 onward
takes place in a separate DOM window, in a wizard. That means the template,
the template editing code, the repeated XML fragments, the broadcast/observer
code, all of it, would not live very long. After the user finished
their work with the template, all these pieces of code would be inaccessible
to the user and to other components, and XPCOM cycle collection would
presumably clean them up.
Individually, each step in the list leads logically to the next one. Taken
as a complete picture, step 10 makes an incorrect statement, and nothing
after it matters. Timed observers do NOT need to hold weak references to the
element, since neither the element nor the observer will be around for the
rest of the application’s life.
However, items 11 and 12 would still be useful – perhaps not to me, but to
others. I still think they need doing, but considering the size of this
“requirements stack trace” — and the dozens of other things I have to get
working for Verbosio 0.1 “Proof of concept”, I’m not going to spend any
immediate time on them.
Conclusion
Why am I sharing all this? It’s a classic forest-for-the-trees problem. If
you find yourself writing code for this grand project, and doing something so
far from it you can’t see how you got there… maybe you need to step back
and write it all down. In this case, it helped me realize I was off-track.
However, if everything checked out, I would have gladly continued to work on
weak references, knowing I was still progressing towards my ultimate goal.
(Certainly, items 1 through 9 are still valid statements, and I should
continue working on “timer-based observations of properties”.) Writing out
this “requirements stack trace” can help you see if you went astray, and if
you did, tell you where to get back on track. If you didn’t go astray, it
gives you confidence that you’re doing the right thing.
Ideally, it shouldn’t take you very long to write it all out – say, 15
minutes for a fast typist on a computer. If it does take longer to write out
all the steps, you really do have a stack overflow to worry about, and you
should think about ways you can cut back to #1. Whether you find the path is
correct from start to finish, or has a flaw in it, answering the question,
“Why am I doing this?” is always useful.