Re: Mutation events replacement

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.

var observer = window.createMutationObserver(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 Wednesday, 29 June 2011 21:55:13 UTC