Re: Mutation events replacement

On 07/20/2011 02:01 AM, Jonas Sicking wrote:
> 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.
I don't understand this.
There are cases when event loop may spin while handling a task.
How should the almost-async listeners be handled in that case - at which
point would they be called?


>
> The advantage of synchronous are fairly obvious.
Indeed. So I'm quite surprised that you are leaning towards async.

> 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.
How so? Mostly-sync is in general close to how event handling works, so
web devs should be familiar with it.

> 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.

I disagree.



>
> / Jonas
>
>

Received on Wednesday, 20 July 2011 12:21:22 UTC