I am building a Tampermonkey user script. My actual script is a lot more complex but I am able to reproduce with the following example.
My script needs to work on Hacker News website.
For example on this page:
https://news.ycombinator.com/item?id=39778570
Their html has .athing class which contains each comment. For each such class, I need to do a certain task (not relevant to this question). I am using MutationObserver to observe for those nodes in my user script:
My user script is this:
// ==UserScript==
// @name HN
// @namespace https://news.ycombinator.com/*
// @version 2024-02-02
// @description For Hacker News
// @author Bobby
// @match https://news.ycombinator.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
const recordObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(m => {
if (m.type === "childList" && m.addedNodes) {
m.addedNodes.forEach(target => {
if (target.nodeType == Node.ELEMENT_NODE) {
target.querySelectorAll(`.athing:not([read_123])`).forEach (element=> {
element.setAttribute(`read_123`, true);
element.style.background = '#00FF0055';
console.log(element.id);
});
}
});
}
});
});
recordObserver.observe(document, {
attributes: false,
childList: true,
characterData: false,
subtree: true
});
Notice how once detected, my script changes the background color of that element.
This seems to work fine to a certain point.
It randomly stops working at random nodes. This screenshot shows the issue:
Notice how it detected until the comment Yoga is great but only really supports flexbox. For..... after which it stopped detecting and also stopped printing the id in console.
If I access the same page again, it stops at different node.
How can I pin point what exactly is causing this issue?
Note that this issue isn't specific to Tampermonkey. My actual issue is occurring on iOS WKWebView where a user inserted script shows the same issue. I am able to reproduce this on desktop this way.

MutationObserver callbacks are async so loading the script as a user one plus hoping it will trigger on loading DOM from the original page source is not a perfect idea. The whole venture doesn't work in Firefox for example.
Here I've explained in more details: Why mutation observer is processed with microtask queue and not macrotask queue?
To mitigate in Chrome try to make your callback as short as possible so you would catch up with the loading page: