- From: Olli Pettay <Olli.Pettay@helsinki.fi>
- Date: Wed, 06 Jul 2011 00:38:44 +0300
- 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 UTC