Re: [D3E] Possible Changes to Mutation Events

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

Received on Friday, 18 July 2008 00:53:13 UTC