When are MutationObserver callbacks fired?
Asked Answered
G

2

50

I know that MutationObservers callbacks may get called sometime after the DOM change. But the question is: What is the timing of these callbacks? Do the callbacks enter the event queue of the browsers? If so, when do they enter the queue?

Are the callbacks:

  • called immediately after the DOM mutation take place,
  • called as soon as the function that manipulate DOM finishes,
  • called as soon as the call stack is empty,
  • enqueued immediately after the DOM mutation take place,
  • enqueued as soon as the function that manipulate DOM finishes, or
  • at some other time?

For example, if the following piece of code is executed (with setZeroTimeout defined here):

var target = document.body;

new MutationObserver(function(mutations) {
  console.log('MutationObserver');
}).observe(target, {
  attributes: true,
  childList: true,
  characterData: true
});


// Post message
setZeroTimeout(function () { console.log('message event'); });
// DOM mutation
target.setAttribute("data-test", "value");

Should "MutationObserver" be printed before "message event" or after it? Or is it implementation-defined?

I'm getting "MutationObserver" before "message event" on Chromium 26, though the DOM mutation is after message posting. Maybe this is indicating that MutationObserver callbacks are not using the event queue.

I have googled for HTML specification, DOM specification or browser implementation documents, but I didn't found anything related to this behavior.

Any explanation or documentation on the timing of MutationObservers callbacks please?

Gangboard answered 28/1, 2013 at 14:51 Comment(7)
My guess is that if mutation observers use the event queue, it's totally unsurprising that message event comes before MutationObserver. The postMessage handler in setZeroTimeout fires and triggers an event (which is added to the event queue), and then the setAttribute call trips the observer (and that probably also adds an event to the queue, behind the message event).Boeotian
@Boeotian I'm getting "MutationObserver" before "message event", not the opposite.Gangboard
Ah, my mistake. Then, yes, that seems indicative that MutationObservers don't use the event queue.Boeotian
This might be further complicated by the fact that postMessage events might not behave the same as other events, but I'm not 100% sure on that.Boeotian
@Boeotian Do you suggest any other async events that can be used for testing in this question?Gangboard
@Boeotian seems correct, they do not use event queues: dvcs.w3.org/hg/domcore/raw-file/tip/…Centripetal
@Centripetal But this document does not define WHEN they are "invoked". Instead, it says "The HTML standard defines how this concept integrates with the rest of the platform as well as when invoke is invoked."Gangboard
G
24

I'm going to answer my own question two years later according to the updated DOM spec from WHATWG.

As shown in the spec:

To queue a mutation observer compound microtask, run these steps:

  1. If mutation observer compound microtask queued flag is set, terminate these steps.
  2. Set mutation observer compound microtask queued flag.
  3. Queue a compound microtask to notify mutation observers.

While "Queuing a compound microtask" links to a section in the HTML spec explaining the microtask queue model.

Therefore, we can conclude that MutationObserver callbacks are fired as microtasks, which are indeed sooner than the task queue tasks as suggested by the answer of @Scott Miles above.

For further understanding of the event loop and processing model, the Event Loop section of the HTML spec would be perfect.

Personally, I'm glad to see that MutationObservers are part of the standard and have a well-documented and consistent timing model. With MutationObservers supported in most modern browsers, I think they are solid for production use now.

Gangboard answered 16/12, 2015 at 12:7 Comment(2)
Thanks for the comments, @FelisCatus; I'm using MutationObserver to detect changes in an Angular SPA application, and the problem is that these callbacks are fired many times for every user navigation. I'm wondering if there's a way to detect when this is finished, that is, when the DOM is finally loaded/changed and no more changes are going to happen unless the user navigates again.Kicker
@david You cannot be sure that no more changes are gonna happen, since those watchers can keep firing and there could be more changes in $timeout and so on. However, I think it is pretty safe to assume that the DOM will be no longer changed as soon as there are no pending AJAX requests AND the DOM haven't been updated for 1 or 2 seconds.Gangboard
P
25

MutationObservers are fired asynchronously but 'soon', which means they fire before other things in the queue, such as layout, paint, or triggered events.

This ameliorates the loss of synchrony, because you don't have to worry about screen flashing or other bad things happening before your observer gets a chance to react.

In developer notes, they talk about an 'end-of-microtask' timing model. I agree this is poorly documented.

Pfaff answered 8/4, 2013 at 21:45 Comment(1)
Thanks for your detailed explanation! I think I should wait for the model to be fixed and documented before writing anything based on assumption about the order of callbacks.Gangboard
G
24

I'm going to answer my own question two years later according to the updated DOM spec from WHATWG.

As shown in the spec:

To queue a mutation observer compound microtask, run these steps:

  1. If mutation observer compound microtask queued flag is set, terminate these steps.
  2. Set mutation observer compound microtask queued flag.
  3. Queue a compound microtask to notify mutation observers.

While "Queuing a compound microtask" links to a section in the HTML spec explaining the microtask queue model.

Therefore, we can conclude that MutationObserver callbacks are fired as microtasks, which are indeed sooner than the task queue tasks as suggested by the answer of @Scott Miles above.

For further understanding of the event loop and processing model, the Event Loop section of the HTML spec would be perfect.

Personally, I'm glad to see that MutationObservers are part of the standard and have a well-documented and consistent timing model. With MutationObservers supported in most modern browsers, I think they are solid for production use now.

Gangboard answered 16/12, 2015 at 12:7 Comment(2)
Thanks for the comments, @FelisCatus; I'm using MutationObserver to detect changes in an Angular SPA application, and the problem is that these callbacks are fired many times for every user navigation. I'm wondering if there's a way to detect when this is finished, that is, when the DOM is finally loaded/changed and no more changes are going to happen unless the user navigates again.Kicker
@david You cannot be sure that no more changes are gonna happen, since those watchers can keep firing and there could be more changes in $timeout and so on. However, I think it is pretty safe to assume that the DOM will be no longer changed as soon as there are no pending AJAX requests AND the DOM haven't been updated for 1 or 2 seconds.Gangboard

© 2022 - 2024 — McMap. All rights reserved.