Re: Mutation Observers: a replacement for DOM Mutation Events

On 09/26/2011 09:09 PM, Adam Klein wrote:
> On Mon, Sep 26, 2011 at 11:05 AM, Olli Pettay<Olli.Pettay@helsinki.fi>  wrote:
>> On 09/24/2011 12:16 AM, 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.
>>
>> We need to change this name, since per WebIDL 'attribute' can't be used
>> here.
>>
>> I propose we use attr everywhere in the API. MutationRecord has already
>> attrName.
>> attributeOldValue ->  attrOldValue and attributeFilter ->  attrFilter
>
> Good catch (and I should've caught it when typing this up, as it's
> pseudo-idl), 'attr' works for me.  If that shortening isn't
> appreciated, we could go with 'attributes' instead.


Apparently I was wrong.
In WebIDL one can escape names using _ prefix.

But actually, attributes sounds more right than
attribute. One really does want to observe all the attributes, unless
names are filtered.

so:
attributes
attributeName
attributeNamespace
attributeOldValue
attributeFilter



-Olli


>
> - Adam
>
>>
>>
>>>      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 Monday, 26 September 2011 19:09:53 UTC