- From: Olli Pettay <Olli.Pettay@helsinki.fi>
- Date: Tue, 05 Jul 2011 20:44:38 +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/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 UTC