Re: Mutation Observers: a replacement for DOM Mutation Events

On Mon, Sep 26, 2011 at 12:08 PM, Olli Pettay <Olli.Pettay@helsinki.fi> wrote:
> 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

I like 'attributes' as the type, for readability. I'm not entirely
convinced we need to lengthen the rest of the names, but I'm all for
consistency, so on that basis I think it's OK.

- Adam

>
>
> -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 20:58:54 UTC