- From: Olli Pettay <Olli.Pettay@helsinki.fi>
- Date: Mon, 26 Sep 2011 22:08:50 +0300
- To: Adam Klein <adamk@chromium.org>
- CC: public-webapps@w3.org, Ojan Vafai <ojan@chromium.org>, rafaelw@chromium.org, rniwa@chromium.org, Jonas Sicking <jonas@sicking.cc>, annevk@opera.com, arv@chromium.org
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