DOM 解析器 Chrome 扩展内存泄漏

DOM Parser Chrome extension memory leak

问题

我开发了一个拦截 Web 请求的扩展程序,获取 HTML Web 请求的来源并对其进行处理。我已经使用 DOMParser 解析 HTML,我意识到 DOMParser 会导致大量内存泄漏问题,最终导致 chrome 扩展崩溃。

这是导致问题的代码。 https://gist.github.com/uche1/20929b6ece7d647250828c63e4a2ffd4

我试过的

开发工具记录的性能

我在拦截请求时记录了 chrome 扩展,我注意到在调用 DOMParser.parseFromString 方法时,创建了更多未被破坏的节点和文档。

开发工具截图 https://i.imgur.com/pMY50kR.png

任务管理器内存占用

我查看了 chrome 上的任务管理器,发现它有一个巨大的内存占用空间,不会随着时间的推移而减少(因为垃圾收集应该在一段时间后启动)。当内存占用太大时,扩展会崩溃。

任务管理器内存占用截图 https://i.imgur.com/c8fLWCy.png

堆快照

我拍了一些堆前后的屏幕截图,我可以看到问题似乎源于 HTML正在分配但未被垃圾收集的文档。

快照(之前) https://i.imgur.com/Rg2CRi6.png

快照(之后) https://i.imgur.com/UQgLuT1.png

预期结果

我想了解为什么 DOMParser 会导致此类内存问题,为什么它没有被垃圾收集器清理以及如何解决它。

谢谢

您基本上是在内存中复制整个 DOM,然后从不释放内存。

我们在客户端应用程序中解决了这个问题,因为当我们离开时,该页面上的脚本使用的内存被回收。

在后台脚本中,不会发生这种情况,现在由您负责。

因此,当您使用完后,将 parserdocument 都设置为 null

chrome.webRequest.onCompleted.addListener(async request => {
    if (request.tabId !== -1) {
        let html = await getHtmlForTab(request.tabId);
        let parser = new DOMParser();
        let document = parser.parseFromString(html, "text/html");
        let title = document.querySelector("title").textContent;
        console.log(title);
        parser = null; // <----- DO THIS
        document = null; // <----- DO THIS
    }
}, requestFilter);

我已经解决了这个问题。问题似乎是因为 DOMParser class 出于某种原因将其解析的 HTML 文档的引用保留在内存中并且没有释放它。因为我的扩展是在后台运行的 Chrome 扩展,所以夸大了这个问题。

解决方案是使用另一种方法来解析 HTML 文档,该文档将使用

let parseHtml = (html) => {
    let template = document.createElement('template');
    template.innerHTML = html;
    return template; 
}

这有助于解决问题。

我无法指出 Chromium 中已确认的错误报告,但我们也遇到了内存泄漏问题。如果您正在开发扩展程序,DOMParser 会在基于 Chromium 的浏览器的后台脚本中泄漏,但不会在 Firefox 上泄漏。

我们无法获得此处提到的任何解决方法来解决泄漏问题,因此我们最终用 linkedom 库替换了本机 DOMParser,后者提供了 drop-in 替换并在浏览器中工作(不仅在 NodeJs 中)。它解决了泄漏,所以你可以考虑它,但有一些方面你需要注意:

  • 它不会泄漏,但它的初始内存占用比使用本机解析器高
  • 性能很可能较慢(但我没有对其进行基准测试)
  • 其 HTML 解析器生成的 DOM 可能与 Firefox 或 Chrome 生成的略有不同。这种效果在 HTML 中最为明显,它已损坏,浏览器将尝试对其进行错误更正。

我们也首先尝试了jsdom,它试图以其代码库更高的复杂性为代价来与主流浏览器更加兼容。不幸的是,我们发现很难让 jsdom 在浏览器中工作(但在 NodeJs 上它工作得很好)。