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: Tue, 05 Jul 2011 20:44:38 +0300
Message-ID: <4E134D86.4080406@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/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.


-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 17:45:29 GMT

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