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

2 Comments

  1. Andreas Eibach pointed out that this definition was missing:

    /**
     * Determines whether o is a DOM object.
     *
     * @param o A value to test
     * @param {Boolean} True if o is a DOM object
     */
    function dom_obj(o) {
        return typeof o == 'object' && (o instanceof Node || o.nodeType > 0);
    }
    

Leave a Reply

Your email address will not be published. Required fields are marked *