Tampermonkey脚本中MutationObserver在Firefox下始终遗漏首个节点
Hacker News Tampermonkey脚本Firefox遗漏首个节点问题
问题描述
编写了如下Tampermonkey自定义脚本,用于标记Hacker News的标题节点:
// ==UserScript== // @name Hacker News // @namespace https://news.ycombinator.com/* // @version 2024-02-02 // @description try to take over the world! // @author You // @match https://news.ycombinator.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net // @grant none // ==/UserScript== var observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === "childList" && mutation.addedNodes && mutation.addedNodes.length > 0 && mutation.addedNodes[0].nodeType == Node.ELEMENT_NODE) { Array.from(mutation.addedNodes[0].querySelectorAll('span.titleline')).forEach (element=> { console.log(`Matched: ${element.innerText}`); element.style.background = 'yellow'; if (window.webkit) { window.webkit.messageHandlers.gotData.postMessage(element.innerText); } }); } }); }); observer.observe(document, { attributes: true, childList: true, characterData: true, subtree: true } );
该脚本在Chrome中运行正常,但在Firefox中始终遗漏页面的首个标题节点。已将脚本的Run at设置为document-start,按预期此时首个节点不应已加入文档,但实际页面重载时,首个节点会在MutationObserver开始监测前加载,从而躲过监测。Hacker News页面为服务端渲染,需要明确Firefox下的特殊情况及解决办法。
原因分析
- Firefox对
document-start时机的处理差异:Chrome中document-start脚本会在文档开始解析前执行,此时DOM完全为空;但Firefox在处理服务端渲染的HTML流时,会提前解析并构建部分初始DOM节点,这部分节点的插入事件会发生在document-start脚本执行之前,导致MutationObserver无法捕捉到。 - 服务端渲染页面的DOM构建特性:Hacker News的页面是纯服务端生成的完整HTML,Firefox在接收HTML内容时会采用流式解析,逐步将节点插入DOM树。当
document-start脚本执行时,首个标题节点可能已经被插入到文档中,而此时Observer还未启动,自然无法监测到这个节点的添加。
解决方案
最可靠的解决方式是在启动MutationObserver后,主动扫描一次当前已存在的所有标题节点,补上对初始DOM中节点的处理,避免遗漏。修改后的脚本如下:
// ==UserScript== // @name Hacker News // @namespace https://news.ycombinator.com/* // @version 2024-02-02 // @description try to take over the world! // @author You // @match https://news.ycombinator.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net // @grant none // ==/UserScript== var observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === "childList" && mutation.addedNodes && mutation.addedNodes.length > 0 && mutation.addedNodes[0].nodeType == Node.ELEMENT_NODE) { Array.from(mutation.addedNodes[0].querySelectorAll('span.titleline')).forEach (element=> { console.log(`Matched: ${element.innerText}`); element.style.background = 'yellow'; if (window.webkit) { window.webkit.messageHandlers.gotData.postMessage(element.innerText); } }); } }); }); observer.observe(document, { attributes: true, childList: true, characterData: true, subtree: true } ); // 主动处理已存在的初始标题节点 Array.from(document.querySelectorAll('span.titleline')).forEach(element => { console.log(`Matched (initial): ${element.innerText}`); element.style.background = 'yellow'; if (window.webkit) { window.webkit.messageHandlers.gotData.postMessage(element.innerText); } });
这种方式不管浏览器对document-start的处理差异如何,都能覆盖初始DOM中的节点,同时保留MutationObserver对后续动态添加节点的监测能力。
内容的提问来源于stack exchange,提问作者sudoExclamationExclamation
相关产品推荐
相关产品推荐

