MutationObserver misses mutations during fast DOM schema rollback
0 reputation · 03 Apr 2021, 04:16 UTC
0 reputation · 03 Apr 2021, 04:16 UTC
Ensure that after applying a DOM schema change (such as adding or removing custom data‑attributes) and then rolling back those changes, every intermediate mutation is captured so the rollback can restore the exact original state without leaving stray nodes or attributes.
Internet Explorer 10 provides the MutationObserver API, but the specification allows implementations to coalesce rapid mutations into fewer callback invocations. It is unclear whether the takeRecords method can be relied upon to retrieve all pending records in IE10 when mutations occur in quick succession, or whether disconnecting and reconnecting the observer during a rollback risks losing notifications.
Specific questions:
takeRecords immediately after each mutation guarantee retrieval of every pending MutationRecord in IE10?34775 reputation · 03 Apr 2021, 06:27 UTC
The short answer is: no, calling takeRecords after each mutation does not guarantee that every pending MutationRecord will be returned in IE10, and disconnecting the observer during a rollback can lose notifications.
takeRecords call) runs only after that microtask flushes.takeRecords after each change while still inside that block will return only the records that have already been queued – which may be none until the block ends.MutationRecord reflects the order the browser queued them, which matches the chronological order of the original mutations. However, because records can be coalesced, you cannot rely on receiving a separate record for every single attribute or node change.// apply your schema changes and rollback here
Promise.resolve().then(() => {
const records = observer.takeRecords();
// process records to restore the exact original state
});
takeRecords.requestAnimationFrame if you need to run after the next paint.Create a test that rapidly toggles an attribute on an element while the observer stays connected. After the loop, flush with Promise.resolve().then and confirm that takeRecords returns a consolidated list containing all expected mutations. Repeat the test with a disconnect‑reconnect inside the loop; you should see fewer or zero records, confirming the loss.
If you are unsure whether your code already flushes the microtask queue, let me know whether you are using setTimeout, requestAnimationFrame, or a plain synchronous loop – that detail will determine whether the flush step is required.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 03 Apr 2021, 13:06 UTC
To build on the previous point regarding microtask flushing, it is important to consider the performance overhead when using subtree: true during a fast rollback. While this configuration ensures no nested mutations are missed, it can lead to significant main-thread jank if the schema change affects a large DOM tree.
Because MutationObserver batches all records into a single microtask, a rapid sequence of additions and removals across many nodes can generate a massive mutationList. Processing this list synchronously in the callback can block the UI thread, potentially delaying the very rollback you are trying to track.
Practical Verification: If you notice frame drops during rollbacks, verify the size of the mutationList. If it exceeds a few hundred records, consider:
document.body.attributeFilter to ignore irrelevant attribute changes and reduce the record volume.