On 07/06/2011 12:48 AM, Rafael Weinstein wrote:
On Tue, Jul 5, 2011 at 2:38 PM, Olli Pettay<olli.pet...@helsinki.fi>  wrote:
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.pet...@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.pet...@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+...@gmail.com>
wrote:

On Tue, Jun 28, 2011 at 5:24 PM, Jonas Sicking<jo...@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.

It's too late by then. Most importantly, visual artifacts of
incomplete DOM work may have been seen by the user.


If that is the reason, I don't think it really holds.
The script may force a layout flush right after
DOM mutation and then cause some popup to shows up which
may cause repainting in the main page.
Listeners would be called only after that.
This all might be highly browser engine dependent
(I'll test this on Chrome tomorrow)

-Olli



Mutation observers serve the purpose of doing secondary work on behalf
of the application script (typically, they create further abstractions
not implemented by the platform). They are conceptually *a part of*
the work being done during any given script event. The application
isn't back to a consistent state until they are run.






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.

















Reply via email to