Dashiell Rose Bark-Huss
Dashiell Rose Bark-Huss

Reputation: 2965

What could cause Mutation Observer on entire document and subtree to not fire when child added?

Sometimes the mutation observer callback doesn't fire when I expect it to.

If I run this code in developer tools console:

// callback for mutations observer
 callbackForAllChanges = function (mutationsList, observer) {
  console.log("mutations: ", mutationsList);
};

// create mutation observer
 allChanges = new MutationObserver(callbackForAllChanges);


// attach mutation observer to document
  allChanges.observe(document, {
    childList: true,
      subtree:true
  });

// create new child
document.body.appendChild(document.createElement("div"));

I would expect callback to fire when I create a new child. But sometimes it does and sometimes it doesn't.

When I run the code in the devtools console while on stackoverflow it works: I see mutations: [MutationRecord] logged to the console.

When I go on twitter and run the above code in the devtools console, it doesn't seem to work: mutations: [MutationRecord] is not logged to the console.

What could cause Mutation Observer to not work on twitter?

To recreate issue

  1. Go to twitter
  2. Open the devtools console
  3. Paste the above code and see mutations: [MutationRecord] does not log

UPDATE

Now it works for me on twitter so seems intermittent.

UPDATE 2

It's stopped working again on twitter. I also found if I add a mutation observer to a div I create, that also doesn't work but works on other sites.
const newDiv = document.createElement('div')
newDiv.setAttribute('id','newDiv')
// callback for mutations observer
 callbackForAllChanges = function (mutationsList, observer) {
  console.log("mutations: ", mutationsList);
};

// create mutation observer
 allChanges = new MutationObserver(callbackForAllChanges);


// attach mutation observer to document
  allChanges.observe(newDiv, {
    childList: true,
      subtree:true
  });

// create new child
newDiv.appendChild(document.createElement("div"));

UPDATE 3

UPDATE 4

It may be related to an infinite loop where when a mutation is observed, a node is added. This causes a mutation to be observed (the new added node). And so on...

Example

  const callbackForAllChanges = function (mutationsList, observer) {
    if (mutationsList.find((record) => record.type === "childList")) {      
  document.body.appendChild(document.createElement("div"));
    }
  };

I think if this happens it might trigger the mutation work to stop working.

Upvotes: 8

Views: 1240

Answers (3)

Pedro Antonio Favuzzi
Pedro Antonio Favuzzi

Reputation: 11

I am facing the same problem, for now I patched the callback by using a loop and checking if some changes were made manually.

setInterval(() => {
  const recs = allChanges.takeRecords()
  if (recs.length === ) return
  yourcallback(recs)
}, 30)

Upvotes: 1

dimoochka
dimoochka

Reputation: 1

This answer dovetails on the response from mayfield which is excellent. I agree that this is a Chromium Devtools bug, reproducible with the steps that he mentioned in his answer. setTimeout() seems to work (albeit inconsistently) to mitigate this. I wonder if this bug might also impact other observers as well ...

I wanted to avoid refactoring a lot of code so I came up with this hacky solution to wrap the native MutationObserver class in your own anonymous class (needs to be run prior to any observers being instantiated):

function fixObserver(name) {
  let original=window[name];
  window[name]=class {
    constructor (cb) {
      return new original((...params) => { 
        setTimeout(() => cb(...params));
      });
    }
  };
}
fixObserver('MutationObserver');
fixObserver('ResizeObserver');
fixObserver('IntersectionObserver');

Upvotes: 0

mayfield
mayfield

Reputation: 59

I'm able to reproduce this with these specific circumstances:

  1. Dev Tools are open
  2. Code has breakpoint or debugger statement inside MutationObserver callback
  3. Load page first time and the page stops on the breakpoint successfully
  4. Reload the page while still in the breakpoint
  5. MutationObserver callback never runs again for any future page reloads
  6. Closing the page and any others to the site and starting a new tab (ie. create new page process) resets the state back to the first step

Other anecdotes:

  • I tried this with some older builds of chromium (circa v97) and interestingly I found that many of them would crash if trying to reload the page from a mutation observer callback breakpoint.
  • Storing a reference to the MutationObserver and using takeRecord() works as expected. As in, it was full of mutation records.
  • Using queueMicrotask(<cb>) to decouple your work from the mutation observer callback does NOT resolve the issue.
  • Using setTimout(<cb>, 0) DOES resolve the issue.

So if you're experience the same issue that I am, the workaround will be to wrap your mutation observer callback in a setTimeout() as follows...

new MutationObserver(() => setTimeout(myHandler, 0));

I don't feel comfortable calling this an answer to be honest. I believe there is a heisen-bug in Chromium here. If you never open Dev Tools this workaround is superfluous.

Upvotes: 0

Related Questions