- From: Olli Pettay <Olli.Pettay@helsinki.fi>
- Date: Mon, 26 Sep 2011 21:05:30 +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/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 -Olli > 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 18:06:19 UTC