October 22, 2009

"Why are you doing this?"

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

  1. 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.
  2. 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.
  3. 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!)
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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 a nsIWeakReference 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.
  12. In writing the XPCShell testcase, I found that DOM elements from the test weren't being deleted (resulting in a nsIWeakReference 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.
  13. 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.

Posted by WeirdAl at 12:07 AM | Comments (0)