W3C home > Mailing lists > Public > public-webapps@w3.org > July to September 2011

Re: Mutation events replacement

From: Olli Pettay <Olli.Pettay@helsinki.fi>
Date: Wed, 06 Jul 2011 00:38:44 +0300
Message-ID: <4E138464.5070602@helsinki.fi>
To: Rafael Weinstein <rafaelw@google.com>
CC: Aryeh Gregor <Simetrical+w3c@gmail.com>, Adam Klein <adamk@google.com>, Jonas Sicking <jonas@sicking.cc>, Anne van Kesteren <annevk@opera.com>, Webapps WG <public-webapps@w3.org>
On 07/06/2011 12:18 AM, Olli Pettay wrote:
> On 07/06/2011 12:06 AM, Rafael Weinstein wrote:
>> Respond
>>
>> On Tue, Jul 5, 2011 at 10:44 AM, Olli Pettay<Olli.Pettay@helsinki.fi>
>> wrote:
>>> On 07/01/2011 02:17 AM, Rafael Weinstein wrote:
>>>>
>>>> On Thu, Jun 30, 2011 at 4:05 AM, Olli Pettay<Olli.Pettay@helsinki.fi>
>>>> wrote:
>>>>>
>>>>> On 06/30/2011 12:54 AM, Rafael Weinstein wrote:
>>>>>>
>>>>>> On Wed, Jun 29, 2011 at 7:13 AM, Aryeh
>>>>>> Gregor<Simetrical+w3c@gmail.com>
>>>>>> wrote:
>>>>>>>
>>>>>>> On Tue, Jun 28, 2011 at 5:24 PM, Jonas Sicking<jonas@sicking.cc>
>>>>>>> wrote:
>>>>>>>>
>>>>>>>> This new proposal solves both these by making all the modifications
>>>>>>>> first, then firing all the events. Hence the implementation can
>>>>>>>> separate implementing the mutating function from the code that
>>>>>>>> sends
>>>>>>>> out notifications.
>>>>>>>>
>>>>>>>> Conceptually, you simply queue all notifications in a queue as
>>>>>>>> you're
>>>>>>>> making modifications to the DOM, then right before returning
>>>>>>>> from the
>>>>>>>> function you insert a call like
>>>>>>>> "flushAllPendingNotifications()". This
>>>>>>>> way you don't have to care at all about what happens when those
>>>>>>>> notifications fire.
>>>>>>>
>>>>>>> So when exactly are these notifications going to be fired? In
>>>>>>> particular, I hope non-DOM Core specifications are going to have
>>>>>>> precise control over when they're fired. For instance, execCommand()
>>>>>>> will ideally want to do all its mutations at once and only then fire
>>>>>>> the notifications (which I'm told is how WebKit currently works).
>>>>>>> How
>>>>>>> will this work spec-wise? Will we have hooks to say things like
>>>>>>> "remove a node but don't fire the notifications yet", and then
>>>>>>> have to
>>>>>>> add an extra line someplace saying to fire all the notifications?
>>>>>>> This could be awkward in some cases. At least personally, I often
>>>>>>> say
>>>>>>> things like "call insertNode(foo) on the range" in the middle of a
>>>>>>> long algorithm, and I don't want magic happening at that point just
>>>>>>> because DOM Range fires notifications before returning from
>>>>>>> insertNode.
>>>>>>>
>>>>>>> Also, even if specs have precise control, I take it the idea is
>>>>>>> authors won't, right? If a library wants to implement some fancy
>>>>>>> feature and be compatible with users of the library firing these
>>>>>>> notifications, they'd really want to be able to control when
>>>>>>> notifications are fired, just like specs want to. In practice, the
>>>>>>> only reason this isn't an issue with DOM mutation events is because
>>>>>>> they can say "don't use them", and in fact people rarely do use
>>>>>>> them,
>>>>>>> but that doesn't seem ideal -- it's just saying library authors
>>>>>>> shouldn't bother to be robust.
>>>>>>
>>>>>> In working on Model Driven Views (http://code.google.com/p/mdv),
>>>>>> we've
>>>>>> run into exactly this problem, and have developed an approach we
>>>>>> think
>>>>>> is promising.
>>>>>>
>>>>>> The idea is to more or less take Jonas's proposal, but instead of
>>>>>> firing callbacks immediately before the outer-most mutation returns,
>>>>>> mutations are recorded for a given observer and handed to it as an
>>>>>> in-order sequence at the "end" of the event.
>>>>>
>>>>> What is the advantage comparing to Jonas' proposal?
>>>>
>>>> You guys did the conceptual heavy lifting WRT this problem. Jonas's
>>>> proposal solves the main problems with current mutation events: (1)
>>>> they fire too often, (2) they are expensive because of event
>>>> propagation, (3) they are crashy WRT some DOM operations.
>>>>
>>>> If Jonas's proposal is the ultimate solution, I think it's a good
>>>> outcome and a big improvement over existing spec or tearing out
>>>> mutation events. I'm asking the group to consider a few changes which
>>>> I'm hoping are improvements.
>>>>
>>>> I'll be happy if I fail =-).
>>>>
>>>> ---
>>>>
>>>> My concern with Jonas's proposal is that its semantics depend on
>>>> context (inside vs. outside of a mutation notification). I feel like
>>>> this is at least a conceptual problem. That, and I kind of shudder
>>>> imagining trying to explain to a webdev why and when mutation
>>>> notifications are sync vs async.
>>>>
>>>> The place it seems likely to fall down is when someone designs an
>>>> abstraction using mutation events and depends on them firing
>>>> synchronously -- then they or someone else attempt to use it inside
>>>> another abstraction which uses mutation events. How likely is that? I
>>>> don't even have a guess, but I'm pretty surprised at the crazy things
>>>> people did with current mutation events.
>>>>
>>>> Our proposal's semantics aren't dependent on context.
>>>>
>>>> Additionally, our proposal makes it clear that handling a mutation
>>>> notification is an exercise in dealing with an arbitrary number of
>>>> ways the DOM could have changed since you were last called. I.e.
>>>> providing the list of changes.
>>>>
>>>> In short, I feel like our proposal is just a small tweak on Jonas's.
>>>> It is more direct in its form and API about the actually difficultly
>>>> of being a mutation observer.
>>>>
>>>> Also, I'll just note a difference in view: We view it as fundamentally
>>>> a bad thing to have more than one actor operating at a time (where
>>>> "actor" == event handler, or abstraction which observes mutations). It
>>>> seems as though you guys view this as a good thing (i.e. All other
>>>> problems aside, mutation events *should* be synchronous).
>>>>
>>>> The example I keep using internally is this: an app which uses
>>>>
>>>> a) A constraint library which manages interactions between form values
>>>> (observes data mutations, makes data mutations)
>>>> b) A templating library (like MDV) which maps data to DOM (observes
>>>> both DOM and data mutations, makes both DOM and data mutations)
>>>> c) A widget library (like jQuery) which "extends" elements to widgets
>>>> (observers DOM mutations, makes DOM mutations)
>>>>
>>>> Our view is that if libraries *can* be built with good robustness, and
>>>> they can interact implicitly via mutation observations (i.e. not have
>>>> API dependencies), this kind of usage will become common and
>>>> desirable. My own and other's experience in systems that provide
>>>> several higher level abstractions like this suggests that it's
>>>> preferable to avoid a big pile up with everyone trying to act at once.
>>>>
>>>>> I think one could implement your proposal on top
>>>>> of Jonas' proposal - especially since both keep the order
>>>>> of the mutations.
>>>>
>>>> It would not be sufficient. The missing bit would be a way to run at
>>>> the "end" of the event. Right now the best way to approach this is to
>>>> capture and delegate all events. MDV, Angular, Sproutcore (and
>>>> probably others) do exactly this. The problem is that
>>>>
>>>> (a) It's hard to capture all events. There's a lot of callback surface
>>>> area in the web platform to cover.
>>>> (b) It doesn't compose. Only one library can play this trick.
>>>>
>>>>> What is "at the 'end' of the event"? You're not talking about
>>>>> DOM event here, but something else.
>>>>
>>>
>>>
>>> You didn't still answer to the question
>>> 'What is "at the 'end' of the event"?' ;)
>>>
>>> I'd really like to understand what that means.
>>
>> Yes. Sorry. This does need more detail.
>>
>> I'm not an expert on the HTML spec or implementation, but I've talked
>> with James Robinson& Tab Atkins and am hopeful that there is a way to
>> spec and implement it.
>>
>> Maybe one of them will chime in here with a more precise definition? =-)
>>
>> Here's how I think about it:
>>
>> Immediately after the UA finishes executing an outer (more below)
>> script event, it invokes a "notifyObservers" (1) function which starts
>> calling back any mutation observers, handing them their list of
>> mutation records. It continues doing so until all mutation records are
>> delivered.
>>
>> Note that mutations *may* occur during this phase. They are simply
>> added to the list of pending mutations to be delivered. When all
>> mutations are delivered, this phase exits and the UA returns.
>>
>> This phase (notifyObservers) is more or less a finalization phase and
>> would be considered a part of the outer-most script for the purposes
>> of the UA's poorly-behaved script timer (the work being done here is
>> the same work that previously would have been done synchronously,
>> during the event -- now it is more or less put into an inner queue
>> which runs when outer event's stack winds down to 0).
>>
>> An "outer" script event is a script event which it NOT the result of
>> an event dispatch which occurred as a result of a synchronous action
>> taken by an already running script event. I.e. secondary, sync script
>> events do not deliver mutation records on exit -- only when control is
>> about to return the UA to select a new Task to run.
>
> So "outer script event" (the word 'event' is quite misleading here)
> is something like
> http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
>
> except that DOM mutations are allowed?
>


What is the reason to require a new mechanism for
async handling? Could listeners be handled in
a task?
Basically, if DOM is mutated during task A, a new
task, B, is scheduled and all the mutation listeners will be called
there.



>
>>
>> 1: One approach for notifyObservers / enqueueMutation:
>> http://code.google.com/p/mdv/source/browse/trunk/platform/observers.js
>>
>>
>>>
>>>
>>> -Olli
>>>
>>>
>>>
>>>> Another way to think of it is this: If Jonas's proposal treated *all*
>>>> event callbacks as already handling a mutation event, then we'd have
>>>> exactly the timing semantics we're looking for.
>>>>
>>>>> How is that different comparing to "immediately before the outer-most
>>>>> mutation"?
>>>>>
>>>>>>
>>>>>> var observer = window.createMutationObserver(callback);
>>>>>
>>>>> Why is createMutationObserver needed?
>>>>
>>>> Yeah, it's not. In our proposal, "Observers" behave differently (are
>>>> called "later" and with batches of changes) from event listeners. We
>>>> thought that if the API looked too much like addEventListener, it'd be
>>>> confusing for people. Creating an observer seemed like a way to make
>>>> it obvious. It could (and probably should) just be a callback.
>>>>
>>>>>
>>>>>
>>>>>> document.body.addSubtreeChangedObserver(observer);
>>>>>> document.body.addSubtreeAttributeChangedObserver(observer);
>>>>>> ...
>>>>>> var div = document.createElement('div');
>>>>>> document.body.appendChild(div);
>>>>>> div.setAttribute('data-foo', 'bar');
>>>>>> div.innerHTML = '<b>something</b> <i>something else</i>';
>>>>>> div.removeChild(div.childNodes[1]);
>>>>>> ...
>>>>>>
>>>>>> // mutationList is an array, all the entries added to
>>>>>> // |observer| during the preceding script event
>>>>>> function callback(mutationList) {
>>>>>> // mutationList === [
>>>>>> // { type: 'ChildlistChanged', target: document.body, inserted: [div]
>>>>>> },
>>>>>> // { type: 'AttributeChanged', target: div, attrName: 'data-foo' },
>>>>>> // { type: 'ChildlistChanged', target: div, inserted: [b, i] },
>>>>>> // { type: 'ChildlistChanged', target: div, removed: [i] }
>>>>>> // ];
>>>>>> }
>>>>>>
>>>>>>>
>>>>>>> Maybe this is a stupid question, since I'm not familiar at all with
>>>>>>> the use-cases involved, but why can't we delay firing the
>>>>>>> notifications until the event loop spins? If we're already delaying
>>>>>>> them such that there are no guarantees about what the DOM will look
>>>>>>> like by the time they fire, it seems like delaying them further
>>>>>>> shouldn't hurt the use-cases too much more. And then we don't
>>>>>>> have to
>>>>>>> put further effort into saying exactly when they fire for each
>>>>>>> method.
>>>>>>
>>>>>> Agreed.
>>>>>>
>>>>>> For context, after considering this issue, we've tentatively
>>>>>> concluded
>>>>>> a few things that don't seem to be widely agreed upon:
>>>>>>
>>>>>> 1) In terms of when to notify observers: Sync is too soon. Async (add
>>>>>> a Task) is too late.
>>>>>>
>>>>>> - The same reasoning for why firing sync callbacks in the middle of
>>>>>> DOM operations is problematic for C++ also applies to application
>>>>>> script. Calling mutation observers synchronously can invalidate the
>>>>>> assumptions of the code which is making the modifications. It's
>>>>>> better
>>>>>> to allow one bit of code to finish doing what it needs to and let
>>>>>> mutation observers operate "later" over the changes.
>>>>>>
>>>>>> - Many uses of mutation events would actually *prefer* to not run
>>>>>> sync
>>>>>> because the "originating" code may be making multiple changes which
>>>>>> more or less comprise a "transaction". For consistency and
>>>>>> performance, the abstraction which is watching changes would like to
>>>>>> operate on the final state.
>>>>>>
>>>>>> - However, typical uses of mutation events do want to operate more or
>>>>>> less "in the same event" because they are trying to create a new
>>>>>> consistent state. They'd like to run after the "application code" is
>>>>>> finished, but before paint occurs or the next scheduled event runs.
>>>>>>
>>>>>> 2) Because the system must allow multiple "observers" and allow
>>>>>> observers to make further modifications, it's possible for an
>>>>>> arbitrary number of mutations to have occurred before any given
>>>>>> observer is called. Thus is it preferable to simply record what
>>>>>> happened, and provide the list to the observer.
>>>>>>
>>>>>> - An observer can be naive and assume that only "simple" mutations
>>>>>> have occurred. However, it's more likely that an observer is an
>>>>>> abstraction (like MDV) which only wants to do work WRT to the net of
>>>>>> what has happened. This can be done by creating a projection which
>>>>>> takes the sequence of mutations as input.
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>>
>>
>>
>
>
>
Received on Tuesday, 5 July 2011 21:39:42 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 18:49:46 GMT