我正在构建一个
Tampermonkey
用户脚本。我的实际脚本要复杂得多,但我可以通过以下示例进行重现。
我的脚本需要在黑客新闻网站上运行。
例如本页:
https://news.ycombinator.com/item?id=39778570
他们的 html 有
.athing
类,其中包含每个评论。对于每个这样的课程,我需要执行某个任务(与这个问题无关)。我正在使用 MutationObserver
来观察用户脚本中的这些节点:
我的用户脚本是这样的:
// ==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
});
请注意,一旦检测到,我的脚本就会更改该元素的背景颜色。
这在某种程度上似乎工作得很好。
它在随机节点上随机停止工作。此屏幕截图显示了问题:
注意它是如何检测的,直到出现注释
Yoga is great but only really supports flexbox. For.....
,之后它停止检测并停止在控制台中打印 id
。
如果我再次访问同一页面,它会停在不同的节点。
我无法确定到底是什么导致了这个问题。任何帮助将不胜感激。
请注意,此问题并非特定于
Tampermonkey
。我的实际问题发生在 iOS WKWebView 上,其中用户插入的脚本显示了相同的问题。我可以通过这种方式在桌面上重现此内容。
MutationObserver 回调是异步的,因此以用户身份加载脚本加上希望它会在从原始页面源加载 DOM 时触发并不是一个完美的想法。例如,整个项目在 Firefox 中就行不通。
这里我更详细地解释了:为什么突变观察者是用微任务队列而不是宏任务队列处理的?
为了在 Chrome 中缓解这一问题,请尝试使回调尽可能短,以便赶上加载页面:
// ==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 mutations = [];
let timeout;
const recordObserver = new MutationObserver(ms => (mutations.push(...ms), clearTimeout(timeout), timeout = setTimeout(processMutations)));
function processMutations(){
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
});