Attack of the cloneNodes

So the solution to the bug I had yesterday was fixed with a call to element::cloneNode to avoid aliasing. This introduced, to my great consternation, another bug — some DOM nodes were reverting to their default value. Had I written this down in my (as yet hypothetical) bug journal, it might have become more clear. Instead, I slaved away in Firebug for a few hours without results.

Thinking about it clearly, the problem had to be in cloneNode. I ended up having to write the following recursive fix-up function:

/**
 * List of all DOM event handler names.
 */
var dom_events = 
['onblur', 'onfocus', 'oncontextmenu', 'onload',
'onresize', 'onscroll', 'onunload', 'onclick',
'ondblclick', 'onmousedown', 'onmouseup', 'onmouseenter',
'onmouseleave', 'onmousemove', 'onmouseover',
'onmouseout', 'onchange', 'onreset', 'onselect',
'onsubmit', 'onkeydown', 'onkeyup', 'onkeypress',
'onabort', 'onerror']; // ondasher, onprancer, etc.

/**
 Fixes copy errors introduced by {@link element#cloneNode}, e.g. failure to copy classically-registered event handlers and the value property.

 @param {element} o The original DOM element
 @param {element} copy The result of o.cloneNode()
 @return {element} A modified copy with event handlers maintained
*/
function fix_dom_clone(o, copy) {
    if (!(dom_obj(o) && dom_obj(copy))) { return; }
    
    for (var i = 0;i < dom_events.length;i++) {
        var event = dom_events[i];
        if (event in o) { copy[event] = o[event]; }
    }
    if ('value' in o) { copy.value = o.value; }
    
    // recur
    var o_kids = o.childNodes;
    var c_kids = copy.childNodes;
    for (i = 0;i < o_kids.length;i++) {
        fix_dom_clone(o_kids[i], c_kids[i]);
    }
}

Oof. Unsurprisingly, there are a few efficiency issues.

My bug was weird and unexpected, and the W3C DOM level 2 spec doesn't allude to problems like this, but looking at a Mozilla bug report on the topic, it seems that the W3C DOM level 3 spec says that "[u]ser data associated to the imported node is not carried over". I guess if that's true for event handlers, it's also true for the value property. Oh well. I'd feel better about this irritating API "feature" if they said "associated with".

Another nasty bug — and an idea

I spent about two hours tracking down a DOM node sharing bug — nodes were being put into a new structure outside of the document before the salient data had been read out. While there was no information in these nodes, the lens system insisted that they still be there. (More on that — eventually.)

After finally tracking it down and writing a version of cloneNode that also copies event handlers, everything worked. Between this and the last prototype aliasing bug I had, I got an idea. A programmer could keep a “bug journal”, a list of bugs found and described first by their behavior, then by their solution (and, if those two aren’t descriptive enough, the underlying problem should be described as well). For example, two days ago I ran into my first genuine typing bug in JavaScript — a type checker would have rejected my program, and from the errors generated it wasn’t obvious where the problem was.

This practice could be useful in a few ways. First, the process of writing down the description can help the programmer find the solution. Tedious, but perhaps worthwhile. Surely some bugs would end up being described post facto, since it’s not worth the time when the fix is fairly clear. Second, the solution may add to a ‘bag of tricks’ at the programmer’s/team’s disposal. Third, the bug and solution tease out invariants in the program, and so the bug journal could be gleaned for inter-module and system-level documentation.

Fourth, and dearest to my heart, I think it’s an interesting way to evaluate programming languages. The bug log of a programmer writing an e-mail interface in Java and that of one writing such an interface in JavaScript would provide for some interesting contrasts. To provide more than anecdotal evidence, you’d need to use a much larger sample size of programmers and kinds of programs being written.

I think the bug journal would differ profoundly from examination of bug tracking logs. Only the truly mysterious bugs and the large, architectural shortcomings make it into the tracker. On a daily basis, programmers struggle with making buggy code workable — before it ever hits version control. So bug tracking logs can highlight difficulties in design and with the team, but a bug journal shows exactly what a programmer has to deal with.

More on this

So I showed my confusing problem detailed in this last post to Dave Herman, who after an initial surprise, said that this was probably due to ES4 expansions method binding — that is, o.f sometimes closes over this, but sometimes not.

It doesn’t work in Opera or IE — they return false for both function calls — but it seems that if they implement ES4, the first call will eventually return true. That seems terrible to me — it was so surprising! It also makes it a little difficult to use JavaScript itself as a compiler representation, since you can’t use A-normal form if let-abstraction doesn’t work. Never mind that programmers (read: I) use let-abstraction to break up complicated expressions when debugging, and so this change in behavior will only confuse matters more.

Dave pointed out that in trade-offs between consistency and convenience, the latter sometimes wins, particularly when changes affect thousands and millions of people. But it’s not clear to me how convenient this is; it’s a tiny shortcut for those who know about it, but it’s very fragile. I’d liken it to operator precedences: in only a few cases do people take advantage of the ordering, so arithmetic expressions are generally just written out with parentheses for clarity.

Capture this!

JavaScript woes:

function C() {
    var _this = this;
    this.foo = function () { return _this === this; }; 
    this.bar = function () { return this.foo; };
    return this;
}
var c = new C();

What is the value of c.foo()? What is the value of c.bar()()? Both are true. But let:

var foo = c.bar()

What is the value of foo()?

If you thought it was true, you’re wrong. This doesn’t make much sense at all. Either this is captured by a dot, or it isn’t. Looking at the inner call to c.bar, it’s syntactically obvious that the call to bar has this = c. But why is that preserved when evaluating it within an outer expression, but not when we split the expression into two statements?

I’m sure this has been seen before, but I’ll probably show it to Dave Herman anyway. If I remember correctly, Python gets this right. Between this and a prototype aliasing bug I had yesterday, I’m irritated.

As an aside, I was speaking with Arjun Guha last night, and none of the conventional type theory would have helped me with these problems. In fact, I get almost no type errors in JavaScript — only grotesque object model problems.