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

Reply via email to