- From: Jonas Sicking <jonas@sicking.cc>
- Date: Tue, 19 Jul 2011 16:01:00 -0700
- To: Rafael Weinstein <rafaelw@google.com>
- Cc: Ryosuke Niwa <rniwa@webkit.org>, Olli@pettay.fi, Aryeh Gregor <Simetrical+w3c@gmail.com>, Adam Klein <adamk@google.com>, Anne van Kesteren <annevk@opera.com>, Webapps WG <public-webapps@w3.org>
On Thu, Jul 7, 2011 at 6:38 PM, Jonas Sicking <jonas@sicking.cc> wrote: > On Thu, Jul 7, 2011 at 5:23 PM, Rafael Weinstein <rafaelw@google.com> wrote: >>> So yes, my proposal only solves the usecase outside mutation handlers. >>> However this is arguably better than never solving the use case as in >>> your proposal. I'm sure people will end up writing buggy code, but >>> ideally this will be found and fixed fairly easily as the behavior is >>> consistent. We are at least giving people the tools needed to >>> implement the synchronous behavior. >> >> Ok. Thanks for clarifying. It's helpful to understand this. >> >> I'm glad there's mostly common ground on the larger issue. The point >> of contention is clearly whether accommodating some form of sync >> mutation actions is a goal or non-goal. > > Yup, that seems to be the case. > > I think the main reason I'm arguing for allowing synchronous callbacks > is that I'm concerned that without them people are going to stick to > mutation events. If I was designing this feature from scratch, I'd be > much happier to use some sort of async callback. However given that we > need something that people can migrate to, and we don't really know > what they're using mutation events for, I'm more conservative. Ok, here is my updated proposal. There are two issues at stake here: When to send notifications, and what they contain. I'll get to when to send them second as that is a more controversial. As for what the notification contain, lets first start at how to register for notifications. Since we want a single callback to contain information about all mutations that has happened, we need the ability to choose, for a single callback, which mutations we should tell it about. Something like this would work: node.addMutationListener(listener, { childlist: true, attributes: true, characterdata: true }); node.removeMutationListener(listener); 'listener' above would be a function which receives a single argument when notifications fire. The value of this argument would be an Array which could look something like this: [ { target: node1, type: "childlist", added: [a, b, c, d], removed: [x, y] }, { target: node1, type: "attributes", changed: ["class", "bgcolor", "href"] }, { target: node2, type: "characterdata" }, { target: node3, type: "childlist", added: [r, s, t, x], removed: [z] } ] A few things to note here: * There is only ever one entry in the array for a given target+type pair. If, for example, multiple changes are made to the classlist of a given node, these changes are added to the added/removed lists. * For "childlist" changes, you get the full list of which nodes were added and removed. * For "attributes" changes you get a full list of which attributes were changed. However you do not get the new and old value of the attributes as this could result in significant overhead for attributes like "style" for example. * For "characterdata" you don't get the old or new value of the node. We could also simply add the before/after values here as there shouldn't be as much serialization overhead involved. A nice thing with the above approach is that it is very expandable if we want to introduce more types of notifications in the future. Some examples that have been mentioned are the ability to be notified about class changes, text-content changes and changes to individual attributes. We could do that using: node.addMutationListener(listener, { class: ["myclass1", "warning"], textcontent: true, attributes: ["type", "title", "data-foo"] }); We could also add the ability to get notified about microdata changes or data-prefix-* changes. But for now I think we should start with a minimal set and see if people use it. But it's good to know that we have a path forward. There are of course a few more things that needs to be defined. Here some of them: * The notification-objects are added to the list in the order they happen. With the exception that if there is a notification-object for the specific target+type then a new object isn't created, but rather added to the existing one. * If you call addMutationListener with the same listener multiple times any new "flags" are added to the existing registration. So node.addMutationListener(listener, { attributes: true }); node.addMutationListener(listener, { childlist: true }); is equivalent to node.addMutationListener(listener, { childlist: true, attributes: true }); * For the "childlist" notifications, nodes are added to the added/removed lists in document order when a whole list of them are added or removed. For example for .appendChild(docfragment) or .textContent = "". * If a node is first added and then removed from a childlist, it doesn't appear in neither the "added" nor the "removed" lists for the childlist notification. * If a node is removed and then readded to a childlist, it appears in both the "added" and the "removed" lists. This is needed to indicate that it might have a different location now. So, this leaves the issue of when to fire these notifications. I had a very interesting talk with Rafael Weinstein about this last week and he presented some very strong points. The two proposals that we have on the table are: 1. Mostly-synchronous. The notifications are dispatched at the end of any mutating DOM call. The notifications are not fully synchronous when the mutation happens inside another mutation notification callback. 2. Almost-asynchronous. The notifications are dispatched at the end of the task which mutated the the DOM. So before any other scheduled tasks get a chance to run. The distinction to fully asynchronous is important since it means that all tasks still see a consistent state, including tasks that paint. The advantage of synchronous are fairly obvious. Strictly speaking it provides a superset of functionality compared to the asynchronous method. It also has the advantage that it is more similar to the API we're trying to replace, mutation events, which might make migration easier. However, the asynchronous version also has advantages. First of all it's more consistent in that for a given mutating DOM operation, the callbacks have never been called by the time the operation returns. I.e. code that runs inside a mutation handler doesn't see different behavior from code that runs outside it. An even bigger advantage however, and this was the convincing argument for me, is that it's a simpler API to develop against for web developers. As browser implementors, synchronous events (and other callbacks) are always a pain in the ass because the world can change under us by the time the event is done firing. With the mostly-synchronous option, we create the same situation for web developers. By the time that they get control again from the notifications, all sorts of things might have changed under them. This is especially the case when you have several independent libraries running on a page, which is exactly the target audience for mutation notifications. Hence I'm leaning towards using the almost-asynchronous proposal for now. If we end up getting the feedback from people that use mutation events today that they won't be able to solve the same use cases, then we can consider using the synchronous notifications. However I think that it would be beneficial to try to go almost-async for now. / Jonas
Received on Tuesday, 19 July 2011 23:02:01 UTC