Re: Mutation events replacement

On 06/04/2009 01:07 PM, Jonas Sicking wrote:
> Here's an API that might work:
> The following methods are added to the Document, Element and
> DocumentFragment interfaces:
>    addAttributeChangedListener(NodeDataCallback);
>    addSubtreeAttributeChangedListener(NodeDataCallback);
>    addChildlistChangedListener(NodeDataCallback);
>    addSubtreeChangedListener(NodeDataCallback);
>    addTextDataChangedListener(NodeDataCallback);
>    removeAttributeChangedListener(NodeDataCallback);
>    removeSubtreeAttributeChangedListener(NodeDataCallback);
>    removeChildlistChangedListener(NodeDataCallback);
>    removeSubtreeChangedListener(NodeDataCallback);
>    removeTextDataChangedListener(NodeDataCallback);
> And add the following interface:
> [CallBack=FunctionOnly, NoInterfaceObject]
> interface NodeDataCallback {
>   void handleEvent(in Node node);
> };

There is now a patch for Gecko to implement a variant of this

The patch adds the following methods (with moz prefix) to Node
(I assume Attr will not extend Node in the near future)
void addAttributeChangedListener(in MutationCallback aCallback);
void addChildlistChangedListener(in MutationCallback aCallback);
void addTextDataChangedListener(in MutationCallback aCallback);
void removeAttributeChangedListener(in MutationCallback aCallback);
void removeChildlistChangedListener(in MutationCallback aCallback);
void removeTextDataChangedListener(in MutationCallback aCallback);

[Callback, NoInterfaceObject]
interface MutationCallback
   // aNode is the node to which the listener was added.
   // aChangeTarget is the node in which the mutation was made.
   void handleMutation(in Node aNode, in Node aChangeTarget);

The changes to Jonas' proposal are merging non-Subtree methods to 
xxxSubtreexxx methods and adding aChangeTarget to the callback.

The basic idea behind the API is to have something simple, yet powerful.
Script libraries could add all sorts of filtering above the low level API.



> These are *not* DOM-Event listeners. No DOM Events are created, there
> are no capture phases or bubbling phases. Instead you register a
> listener on the node you are interested in being notified about, and
> will get a call after a mutation takes place. The listeners are called
> as follows:
> 'AttributeChanged': Called when an attribute on the node is changed.
> This can be either a attribute addition, removal, or change in value.
> If setAttribute is called the 'AttributeChanged' listener is always
> called, even if the attribute is given the same value as it already
> had. This is because it may be expensive to compare if the old and new
> value was the same, if the value is internally stored in a parsed
> form.
> 'SubtreeAttributeChanged': As for 'AttributeChanged', but called not
> just for attribute changes to the node, but also to any descendants of
> the node.
> 'ChildlistChanged': Called if one or or mode children are added or
> removed to the node. So for example when innerHTML is set, replacing
> 10 existing nodes with 20 new ones, only one call is made to the
> 'ChildlistChanged' listeners on that node.
> 'SubtreeChanged': Same as 'ChildlistChanged', but called not just for
> children added/removed to the node, but also to any descendants of the
> node.
> 'TextDataChanged': Called when the data in a Text, CDATASection or
> ProcessingInstruction, is changed on the node or any of its
> decendants.
> The exact algorithm would go something like this:
> The implementation contains a global list, pendingCallbacks. Each item
> in pendingCallbacks is a tuple:<callback, node>.
> The implementation also contains a flag, notifyingCallbacks, which
> initially is set to false.
> If an attribute is changed on node N, perform the following steps:
> 1. For each 'AttributeChanged' callback, C, registered on N, add an
> item to the end of pendingCallbacks with the value<C, N>
> 2. For each 'SubtreeAttributeChanged' callback, C, registered on N,
> add an item to the end of pendingCallbacks with the value<C, N>
> 3. Set N to N's parentNode.
> 4. If N is non-null, go to step 2.
> 5. If notifyingCallbacks is set to true, or pendingCallbacks is empty,
> abort these steps.
> 6. Set notifyingCallbacks to true.
> 7. Remove the first tuple from pendingCallbacks.
> 8. Call the handleEvent function on the callback in the tuple, with
> the 'node' argument set to the node from the tuple.
> 9. If pendingCallbacks is not empty, go to step 7.
> 10 Set notifyingCallbacks to false.
> A similar algorithm is run when children are added and/or removed from
> an element/document/document-fragment, just replace 'AttributeChanged'
> with 'ChildlistChanged', and 'SubtreeAttributeChanged', with
> 'SubtreeChanged'.
> And when Text/CDATASection/ProcessingInstruction nodes have their
> contents modified the same algorithm is run, but without step 1, and
> with 'SubtreeAttributeChanged' replaced with 'TextDataChanged'.
> There's a few of properties here that are desirable, and one that is
> sub-optimal.
> First of all no notifications are done until after the implementation
> is done with the mutations. This significantly simplifies the
> implementation since all critical work can be done without worrying
> about script performing arbitrary operations in the middle.
> Second, all callbacks are notified in the order mutations happen in
> the tree, even when another callback performs further mutations. So if
> a callback for 'ChildlistChanged' sets a attribute on the newly
> inserted node, the other callbacks are still going to get first
> notified about the node being inserted, and then about it having an
> attribute set. This is thanks to the notifyingCallbacks flag.
> Third, these notifications should be significantly faster. Since there
> are no DOM Events, there is no need to build propagation chains.
> There's also no need to recreated the old attribute value, or to fire
> one event for each node if multiple nodes are added and removed.
> The only undesirable feature is that code that mutates the DOM from
> inside a callback, say by calling setAttribute, can't rely on that by
> the time that setAttribute returns, all callbacks have been notified.
> This is unfortunately required if we want the second desirable
> property listed above.
> The only feature of mutation events that this doesn't provide is the
> functionality that the
> DOMNodeRemovedFromDocument/DOMNodeInsertedIntoDocument. I believe that
> these are implemented in some browsers. I don't know how these
> browsers would feel about dropping support without supplying some sort
> of alternative. If needed we could add the following
>    addInDocumentChangedDocumentListener(NodeDataCallback);
>    removeInDocumentChangedDocumentListener(NodeDataCallback);
> But I'm not sure that there is web-dependency on this, or good use cases.
> / Jonas

Received on Tuesday, 28 June 2011 11:31:13 UTC