Reputation: 583
I'm building an undomanager, similar to the W3C undomanager that's not quite yet ready in the various browsers. I implemented a simply transact call that calls a callback while watching for changes to the DOM, and then adds the necessary structures to an array that can later be used to undo (or redo) the change.
A simple example:
function transact(callback){
/* Watch content area for mutations */
observer = new MutationObserver(function(){
/* TODO: collect mutations in here */
alert('Mutations observed');
});
observer.observe(document.getElementById('content'), {
attributes: false,
childList: true,
characterData: false,
subtree: false
});
/* Perform the callback */
callback();
/* Stop observing */
//observer.disconnect();
setTimeout(function(){ observer.disconnect();}, 1);
}
To use this:
transact(function(){
var p = document.createElement('p');
p.innerHTML = 'Hello';
document.getElementById('content').appendChild(p);
});
If I call observer.disconnect()
immediately, the mutation observer never reaches the alert
call, but if I use setTimeout, it works fine.
I would be perfectly happy to live with the setTimeout call, the only problem seems to be that for larger changes, you have to delay the disconnect as much as 800 milliseconds.
It is almost as if the disconnect happens before the DOM change has actually been completed, and so nothing is detected.
This happens in both Firefox 25 and Chrome 32.
I thought for a second that because observer
is a local variable, perhaps it goes out of scope too soon, but changing it to a global variable didn't help. I have to delay the call to disconnect()
to give the DOM a chance to catch up it seems.
Is this a browser bug? Is there a better way to call disconnect()
as soon as the DOM is ready again?
Upvotes: 2
Views: 4649
Reputation: 11353
MutationObserver
s are async by specfication, in that they will wait for the current stack to be empty before it calls your callback function. This is useful so your callback is not called each time you make a change to the DOM but only after all your changes have been made. See how are MutationObserver callbacks fired?
If you look at the specification link you will notice the steps involved before a MutationEvent
are:
takeRecords
setTimeout
will call the function after the timeout and stack empties)Update sorry, to address your actual question, I'm thinking it may have to do with the alert
in the MutationObserver
callback. It definitely shouldn't take more than a couple of milliseconds for mutations to be processed and it should definitely occur before the setTimeout
. Anyway a solution that would definitely work is to add a queue processor in the MutationObserver callback instead of using a timeout.
function transact(callback){
var queue = [], listener; //queue of callbacks to process whenever a MO event occurs
/* Watch content area for mutations */
var observer = new MutationObserver(function(){ //made observer local
/* TODO: collect mutations in here */
alert('Mutations observed');
while(listener = queue.shift()) listener();
});
observer.observe(document.getElementById('content'), {
attributes: false,
childList: true,
characterData: false,
subtree: false
});
/* Perform the callback */
callback();
/* Stop observing */
//observer.disconnect();
queue.push(observer.disconnect.bind(observer));
}
Upvotes: 3