Doug Schepers wrote:
Jonas proposes two substantive changes to this:
* DOMNodeRemoved and DOMNodeRemovedFromDocument would be fired after the
mutation rather than before
* DOM operations that perform multiple sub-operations (such as moving an
element) would be dispatched (in order of operation) after all the
sub-operations are complete.
So based on the feedback so far in this thread, here is a revised
proposal from me, with the added feature that it's backwards compatible
with DOM Events Level 2:
* Add a |readonly attribute long relatedIndex;| property to
the MutationEvent interface.
* Add a DOMChildRemoved event which is fired on a node when one
of its children is removed. The relatedNode property contains the
removed child, and relatedIndex contains the index the child had
immediately before the removal. The event is fired after the removal
takes place.
* Add a DOMDescendantRemovedFromDocument event which is fired on a node
when the node is in a document, but any of nodes the descendants is
removed from the document. The event is fired after the removal takes
place.
The relatedNode property contains the removed descendant. The
relatedIndex property contains the index the child had
immediately before the removal. (Should relatedIndex be -1 when
the node wasn't removed from its parent, but rather an ancestor was?)
* Specify *when* the events fire (see details below).
* Deprecate the DOMNodeRemoved and DOMNodeRemovedFromDocument events.
If this means making them optional or just discouraged I don't really
care. I'd even be ok with simply leaving them in as is. Mozilla will
simply remove our implementation of the DOMNodeRemoved event. We've
never supported the DOMNodeRemovedFromDocument event.
As for when the events fire (note that this is just clarifications of
the spec, not changes to it):
For events that fire after the mutation takes place I propose that we
add a concept of a "compound operation" and state that while compound
operations are in progress no mutation events are fired. Instead the
events are queued up. After the outermost compound operation finishes,
but before it returns control to the caller, the queue of mutation
events is processed.
A compound operation may itself contain several compound operations. For
example parent.replaceChild(newChild, oldChild) can consist of a removal
(removing newChild from its old parent) a second removal (removing
oldChild from parent) and an insertion (inserting newChild into its new
parent). Processing of the queue wouldn't start until after the
outermost compound operation finishes.
One important detail here is that processing of the queue starts *after*
the compound operation finishes. This means that if a mutation listener
causes another mutation to happen, this is considered a new compound
operation. So if the mutation listener causes another mutation to
happen, then the mutation events queued up during that compound
operation fires before that compound operation returns.
There are two ways to implement this:
Either you move the currently queued events off of the queue before
starting to process them.
Or, when starting an outermost compound operation, remember what the
current length of the queue is, and when finishing the operation, only
process queued events added after that index.
The whole point of this inner queuing is to allow mutations inside
mutation listeners to behave like mutations outside them. So if code
inside a mutation listeners calls .replaceChild, it can count on that
the mutation listeners for that mutation has fired by the time
replaceChild returns.
What exactly constitutes a compound operation is left up to the specs
that describe the operations. For example setting .innerHTML should
likely constitute a compound operation. For DOM Core, every function
that mutates the DOM should be considered a compound operation.
This all does sound a bit complicated. However the same would be true
for any sufficiently detailed specification of how mutation events
behave. As stated, the current wording of the spec allows for wildly
different and useless firing logic.
The only thing that we could somewhat simplify would be to not use
separate queues for mutations inside mutation listeners. However the
simplification would be pretty marginal, and I think it would be
confusing that mutations inside mutation listeners wouldn't cause events
to fire until after all other pending events had fired.
/ Jonas