Search code examples
javascripthtmldomclone

Node.cloneNode() inconsistent with DOM Spec


It is well documented (mainly on SO) that using Node.cloneNode(true) on an <input type="text"> returns a new <input> node with the same text contents as the original one, but cloning a <select> (after a user has chosen) returns a new <select> without the user selection. This at first seems to not follow the spec, because according to DOM 3:

cloneNode
Returns a duplicate of this node, i.e., serves as a generic copy constructor for nodes. The duplicate node has no parent (parentNode is null) and no user data. User data associated to the imported node is not carried over.

The answer to why this happens is somewhat spelled out later, but it is more clear in the DOM 2 spec,

cloneNode
Cloning an Element copies all attributes and their values, including those generated by the XML processor to represent defaulted attributes, but this method does not copy any text it contains unless it is a deep clone, since the text is contained in a child Text node.

I'm willing to accept this behavior given this wording, although I'd argue that an <input> doesn't contain a text node, because the childNodes property of an <input> will always be an empty NodeList regardless if it contains text (in the spec's wording, "has a text node").

At the least, I understand that which <option> a user selects is considered user data, so it makes sense that this is not cloned. In this case, this "user data" is contained in the selected property of the <option>.

However, when trying the behavior with radio buttons and checkboxes, the behavior in Chrome 38, FF 33, Safari 6.2, completely defies the above definition. Cloning checked checkboxes or selected radio buttons maintains their checked state. This seems to be contrary to the spec, since this information is "user data," which (like the <select>) is contained in a property (in this instance checked).

Am I correct that this does not align with the specification? If not, is there elsewhere where this behavior is defined? And if it is, is there a use case that explains why cloneNode() is so inconsistent about how it clones the states of user input fields?


Edit: While testing this against my suite of browsers, I discovered that Opera 12.15 performs as one would like with <select> inputs. Namely, it retains user selection on clone.


Solution

  • DOM Level 4 defines cloning steps clearlier:

    Specifications may define cloning steps for all or some nodes. The algorithm is passed copy, node, document, and optionally a clone children flag, as indicated in the clone algorithm.

    To clone a node, optionally with a document and a clone children flag, run these steps:

    1. If document is not given, let document be node's node document.

    2. Let copy be a node that implements the same interfaces as node.

    3. If copy is a document, set its node document and document to copy.
      Otherwise, set copy's node document to document.

    4. Copy the following from node to copy, depending on the type of node:

    5. Run any cloning steps defined for node in other applicable specifications and pass copy, node, document and the clone children flag if set, as parameters.

    6. If the clone children flag is set, clone all the children of node and append them to copy, with document as specified and the clone children flag being set.

    7. Return copy.

    The cloneNode(deep) method must return a clone of the context object, with the clone children flag set if deep is true.

    Both select and input are Elements, so only their namespace, namespace prefix, local name, and its attribute list should be copied in step 4.

    However, in step 5, additional cloning steps may run. Probably, those steps include copying value and selected properties in case of input elements.