Re: Mutation Observers: a replacement for DOM Mutation Events

Hi, Adam-

I'm glad to see some progress on a replacement for Mutation Events.

Would you be interested in being the editor for this spec?  It's already 
in our charter, we just need someone to take it up.  Olli has offered 
offlist to be a co-editor, so between the two of you, I think it would 
be pretty manageable.

I'd be happy to help get you started.

Thanks-
-Doug (W3C staff contact for WebApps WG)

On 9/23/11 5:16 PM, Adam Klein wrote:
> Chromium (myself, Rafael Weinstein, Erik Arvidsson, Ryosuke Niwa) and
> Mozilla (Olli Pettay, Jonas Sicking) have worked together on a
> proposal for a replacement for Mutation Events.
>
> This proposal represents our best attempt to date at making a set of
> sensible trade offs which allows for a new mutation observation
> mechanism that:
>
> - Is free of the faults of the existing Mutation Events mechanism
> (enumerated in detail here:
> http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html)
>
> - Meets the goal of having the main “questions” that use-cases will
> need answered about the net-effect of changes, be computable in linear
> time complexity roughly proportional to the number of changes that
> occurred.
>
> Significant aspects of this design:
>
> - Delivery of MutationRecords happens asynchronously, at the end of
> the current “microtask”. This is between Options 2 and 3 from this
> discussion http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0780.html.
> Instead of calling listeners at the end of outermost DOM operation or
> at the end of a Task, listeners are called at the end of outermost
> script invocation. If there are no script invocations, listeners are
> called at the end of Task.
>
> - Information about mutations is delivered to observers as an ordered
> sequence of MutationRecords, representing an observed sequence of
> changes that have occurred.
>
> - Subtree observation properly handles the case where nodes are
> transiently removed from, mutated outside of and then returned to the
> subtree.
>
> - Observers specify the types of changes they are interested in and
> (in some cases) level of detail they require.
>
> Sample usage:
>
> var observer = new MutationObserver(function(mutationRecords) {
>    // Handle mutations
> });
>
> observer.observe(myNode,
> {  // options:
>    subtree: true;  // observe the subtree rooted at myNode
>    childList: true;  // include information childNode insertion/removals
>    attribute: true;  // include information about changes to attributes
> within the subtree
> });
>
> …
>
> observer.disconnect();  // Cease observation
>
> Details:
>
> We introduce a new interface MutationObserver with a constructor on DOMWindow:
>
> [Constructor(in MutationCallback callback)]
> interface MutationObserver {
>     void observe(in Node target, in MutationObserverOptions options);
>     void disconnect();
> };
>
> where MutationCallback is
>
> [Callback, NoInterfaceObject]
> interface MutationCallback {
>      void handleEvent(in MutationRecord[] mutations, in
> MutationObserver observer);
> };
>
> Registration&  Observation
> - A call to observe creates a registration for the observer to be
> delivered mutations made to |target|, and optionally, its descendants.
>
> - Subsequent calls to the same MutationObserver made with the same
> |target| have the effect of resetting the options associated with the
> registration.
>
> - Subsequent calls to the same MutationObserver made with different
> |targets| have the effect of expanding the set of nodes which are
> being observed by the observer. All mutations made to all observed
> nodes in all registrations for a given observer are delivered, in
> time-ordered sequence, via a single invocation of the
> MutationCallback’s handleEvent method.
>
> - disconnect ceases observation over the observer’s set of observed nodes.
>
> Registration Options
> The |options| argument provided in observe is defined by the
> MutationObserverOptions interface:
>
> interface MutationObserverOptions {
>      // Mutation types
>      boolean childList;  // If true, mutations affecting node’s
> childNodes are included.
>      boolean attribute;  // If true, mutations affecting element’s
> attributes are included.
>      boolean characterData;  // If true, mutations affecting the value
> of CharacterData
>                                              //nodes are included.
>      // [Note: If none of the known mutation types is specified, an
> Error is thrown]
>
>      // Subtree observation
>      boolean subtree;  // If true, the observed set of nodes for this
> registration should include
>                                   // descendants of MutationTarget
> (behavior described below).
>
>      // Old values
>      boolean attributeOldValue;
>      // If true, MutationRecords describing changes to attributes should
>      // contain the value of the attribute before the change. If true
>      // without attribute: true specified, an Error is thrown.
>
>      boolean characterDataOldValue;
>      // If true, MutationRecords describing changes to
>      // CharacterData nodes should contain the value
>      // of the node before the change. If true without
>      // characterData: true, an Error is thrown.
>
>      // Filtering
>      DOMString[] attributeFilter;
>      // If provided, only changes to attributes with localName equaling
>      // one of the provided strings will be delivered. If provided without
>      // attribute: true, an Error is thrown.
> };
>
> Subtree Observation
> If the subtree option is requested during registration, the observer
> is delivered mutations which occur to a set of observed nodes which is
> computed as follows:
>
> - At the time of registration, the set includes |target| and all
> descendant nodes
> - At any point, if a node becomes a descendant of |target|, it is
> synchronously added to the observed set
> - Immediately before delivering all pending MutationRecords to the
> observer, all nodes which are no longer descendants of |target| are
> removed from the observed set.
>
> Processing Model:
>
> Pending Mutation Queues
> For each MutationObserver with at least one active registration, the
> UA maintains a queue of pending MutationRecords which are pending
> delivery.
>
> Record Creation&  Enqueuing
> MutationRecords are created at the following times:
>
> -When one or more nodes are added to and/or removed from a node’s
> childNodes collection, a 'childList' record is created with target set
> to that node.
>
> Each childList MutationRecord represents that, at a given position in
> the childNodes collection, a contiguous sequence of nodes was removed
> and a contiguous sequence of nodes was inserted. In this way, the
> effect on the target node of many (but not all, e.g. execCommand) DOM
> operations may be represented by a single child list change record
> (e.g. innerHTML, insertBefore, appendChild). The position at which the
> nodes were inserted and/or removed is recorded via the previousSibling
> and nextSibling attributes of MutationRecord.
>
> - When an attribute is added, removed or changed, an 'attribute'
> record is created with target set to the element owning the attribute.
>
> - When the data attribute of a CharacterData (Text or Comment node) is
> changed a 'characterData' record is created with the target set to
> that CharacterData node.
>
> For each observer, if a registration exists which requests the
> matching mutation type and whose observed node set contains the target
> node of the mutation, a MutationRecord is appended to the observer's
> pending mutation queue. If multiple such registrations exist for a
> given observer, a single MutationRecord is delivered having the union
> of the information requested by all registrations (e.g.
> attributeOldValue).
>
> Delivery of Pending Mutations
> If mutations occur during script invocation, delivery must take place
> at the end of the current “microtask”. If mutations are made directly
> by the UA and not by script (for example due to user input), delivery
> must take place by the end of the current Task.
>
> The steps for delivery are:
>
> 1) Visit each observer, in registration order
>
> For each observer,
>
> 2) Remove any non-descendants from the observed set of any subtree
> observation associated with this observer
>
> 3) If the observer’s pending mutation queue is non-empty, clear the
> queue, putting the contents into a MutationRecord[] array and call the
> observer’s handleEvent method, passing the newly created array of
> records.  If the observer’s associated callback is a bare function
> (and not an object with a handleEvent method), it is called with
> |this| set to the observer.
>
> 4) If any observer’s pending queue has become non-empty, goto step 1.
>
> Mutation Records
>
> interface MutationRecord {
>     // Mutation type: one of 'childList', 'attribute', or 'characterData'
>     readonly attribute DOMString type;
>
>     // For childList and attributes, target is the owner node affected.
>     // For CharacterData, target is the node affected.
>     readonly attribute Node target;
>
>     // For type == 'childList’, Sequence of added and removed nodes in
> this operation.
>     readonly attribute NodeList addedNodes;
>     readonly attribute NodeList removedNodes;
>
>     // For type == 'childList’, The siblings in childNodes immediately
> preceding following the first
>     // and last nodes added and/or removed.
>     readonly attribute Node previousSibling;
>     readonly attribute Node nextSibling;
>
>     // For type == 'attribute', the name and namespaceURI of the
> attribute affected
>     readonly attribute DOMString attrName;
>     readonly attribute DOMString namespaceURI;
>
>     // For type == ‘attribute’ or ‘characterData’, if requested, the
> value immediately
>     // preceding the mutation.
>     readonly attribute DOMString oldValue;
> };
>
> MutationRecords are immutable. All the WebIDL attributes are
> non-writable and non-configurable. The UA may deliver the same
> MutationRecord to multiple observers just like Events, MutationRecords
> are still extensible so they could be used as side channels for
> communication.
>
> However, observers must only be delivered MutationRecords with the
> exact level of detail that they requested (e.g. If an observer
> requested attribute mutations, but not attributeOldValue it should
> never be delivered an ‘attribute’ type MutationRecord’s containing
> oldValue).
>
> My next plans are to begin a vendor-prefixed implementation of this
> API in WebKit and to begin work on formalizing the above into a proper
> spec.
>
> - Adam
>

Received on Thursday, 29 September 2011 14:32:20 UTC