[whatwg] DOM Range: redefining behavior under DOM mutation

Aryeh Gregor Simetrical+w3c at gmail.com
Tue Mar 29 11:19:34 PDT 2011


On Mon, Mar 28, 2011 at 8:05 PM, Boris Zbarsky <bzbarsky at mit.edu> wrote:
> One problem here is that there is not a concept of "moved" in the DOM.
> There's just removed and inserted.  Trying to insert something that already
> has a parent will remove it and then do the insert.  Thanks to the wonders
> of mutation events and userdata, script can execute in state when the node
> being moved is not in the DOM, which means that ranges need to update
> separately for the removal and insertion because the state of the range in
> the intermediate state with the node outside the DOM needs to be
> well-defined.

Hmm, I see.  I was assuming that insertBefore() would just atomically
change the parent from the new one to the old one.  Looking more
closely at DOM Core, I see it's specced the way you describe it
(although nothing says when the mutation events fire).  That's
certainly a problem for my plan.  It would be possible to work around
it by requiring that insertBefore() and similar methods do special
magic for Ranges independent of the actual DOM mutations done,
however.  That already has to happen for
insertData()/deleteData()/appendData(), right?  All browsers treat
those differently from just setting the data to the equivalent string.

> Now if we dropped support for mutation events and userdata handlers
> first.....

Is that feasible?  I get the impression implementers would all love
it, but somehow they haven't done it yet.

> Gecko's implementation of A.insertBefore(B, C), in pseudocode and ignoring
> all the sanity-checking and for cases when B is not a document fragment is:
>
>  if (B.ownerDocument != A.ownerDocument) {
>    A.ownerDocument.adoptNode(B);  // This can run arbitrary script
>  }
>
>  if (B.parentNode) {
>    B.parentNode.removeChild(B);   // This can run arbitrary script
>  }
>
>  // Mutate the child storage of A to put B in the child list; notify
>  // observers
>
> Both the removeChild call and the mutation of A's child list notify mutation
> observers.  Ranges are mutation observers, and use the notifications to
> update themselves.  So from the point of view of a range, the removal and
> insertion are two distinct events.
>
> -Boris
>
> P.S.  I suspect that actual interoperability for the edge cases of this
> stuff is poor: nothing defines whether the adopt happens before the
> removeChild above, nothing defines what happens if user data handlers or
> mutation events mutate the DOM, etc.

DOM Core says it's supposed to be basically

    if (B.ownerDocument != A.ownerDocument) {
        A.ownerDocument.adoptNode(B);
    }

    // Insert into the children
<http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-node-insertbefore>

where adoptNode() removes the node from its parent before changing the
ownerDocument.  This seems to be how both IE, Gecko and Opera actually
behave -- moving a node from one document to another has the
ownerDocument equal to the old document on DOMNodeRemoved, and the new
document on DOMNodeInserted.  (WebKit throws if you do insertBefore()
cross-document, and I can't get it to fire any DOM mutation events
even if it's same-document.)  When I thoughtlessly mutated the DOM
from the event handler, though, I definitely hit a lack of interop.


Anyway, then what does Gecko do for execCommand()?  Does that just
have special-case logic to adjust the selection to fit the new DOM?
It would be nice if we could eliminate that in a clean way, from both
the specs and implementations.



More information about the whatwg mailing list