[jquery]jQuery 和 Sizzle 的 Iframe 内存泄漏

2023-12-05 620 views
7

描述

我们遇到的情况是有 3 层嵌套的 iframe。第三个 iframe 初始化了一些大尺寸对象,但每当用户在应用程序中导航时,该 iframe 就会被丢弃并在 DOM 中重建。

每当丢弃并构造第三层的新 iframe 时,应用程序的堆大小就会不断增加,从而会出现内存泄漏。

测试用例链接

在 Chrome 中转到https://vamsikrishnach90.github.io/。打开开发人员工具(按 F12)并导航到“内存”选项卡。注意 JS 堆大小,arppox 5 MB。

  1. 单击“加载子页面”按钮。子页面加载到父页面的 iframe 内。
  2. 点击“轰炸页面!” 子页面上的按钮。这将使用一个巨大的 json 初始化 1 个自定义类型的全局变量。打开 devtools 并检查堆现在为 75 MB。
  3. 单击“删除子页面”按钮。子页面 (iframe) 将从 DOM 中删除。

重复步骤 1 - 3 约 2 次。步骤 2 后,记下堆大小。您会注意到,每次我们添加子 iframe 并对其进行轰炸时,堆都会不断增加。即使在步骤 3 中,我们丢弃了该帧,垃圾收集器也不会声明该堆。 用例演示帮助(图)

观察结果

重复上述1-3步骤3次后,拍摄堆快照。请注意,每次添加子页面并对其进行轰炸时创建的自定义类“Vamsi”的 3 个实例仍然存在于堆中,即使相应的 iframe 已从 DOM 中删除。

请注意,在对象图中,unloadHandler() 已绑定到属于废弃 iframe 的 Window 对象。堆内存快照

根本原因

子页面在父页面中有一条jquery查找语句: $('div.msg-banner', window.parent.document).show();

这一行语句导致了内存泄漏问题。

$('div.msg-banner', window.parent.document)

上面的部分语句,调用了JQuery find api。这个查找 API 被Sizzle覆盖,Sizzle 是 JQuery 采用的纯 JavaScript CSS 选择器引擎。来自 JQuery 的 find api 调用 Sizzle 的 setDocument 函数(参见图片),该函数向所请求文档的 (window.parent.document) 窗口对象注册 unloadHandler()。这导致父页面的卸载事件绑定到子页面中的 unloadHandler() 回调函数。因此,即使子页面 iframe 从 DOM 中删除,窗口对象也永远没有资格被垃圾收集,这就是内存泄漏的原因。

链接到Stackoverflow线程:https://stackoverflow.com/questions/76579826/iframes-memory-leak-with-jquery-and-sizzle

回答

0

感谢您的报告。IE 和 Edge Legacy 需要卸载处理程序;在 jQuery 4 中,放弃了对后者的支持,我们将仅在 IE 中明确设置它,以避免 IE 之外的内存泄漏。

为了避免泄漏,无论是否存在 IE,我们可能必须unload在加载 jQuery 的文档上添加一个额外的处理程序,该处理程序将收集unload附加到其他文档的所有处理程序,并在主文档卸载时将其删除。不过,这可能需要大量代码才能完成,对此我不太高兴。

1

我们还可以考虑通过进行间接支持测试来将问题限制在 IE 和 Edge Legacy 中,即使是在 3.x 系列中 - 通过检查是否存在documentElement.msMatchesSelector: https: //caniuse.com/matchesselector

IE 和 Edge Legacy 都不再开发,我无法想象现代浏览器现在突然添加这个非标准 API,所以这样的检查应该是安全的。

对所有浏览器的真正修复可能需要定义如下内容:

function setupUnloadHandler( subWindow ) {
    function unloadHandler() {
        window.removeEventListener( "unload", unloadHandler );
        subWindow.removeEventListener( "unload", unloadHandler );
        setDocument();
    }
    window.addEventListener( "unload", unloadHandler );
    subWindow.addEventListener( "unload", unloadHandler );
}

然后,在我们当前附加的位置unloadHandler,改为调用:

setupUnloadHandler( subWindow );

不过,这将是+24 字节。如果我们这样做,我还想首先确保这仍然可以解决 IE 问题,并且我在unloadHandler删除逻辑后在 IE 11 中实际重现它时遇到了麻烦。我查看了https://bugs.jquery.com/ticket/13936https://bugs.jquery.com/ticket/14535中的测试用例,如果没有补丁,它们都不会失败。我怀疑代码得到了简化,并且没有setDocument足够早调用以解决问题的代码路径更少。但如果我们想这样做,我想首先找到这样的代码路径。

欢迎帮助寻找好的测试用例!

0

感谢您的回复并承认问题。在内部会议和审查后,当您有下一个解决此问题的“行动计划”时,请告诉我。

2

仅供参考 - 如果我们解决 IE 和 Edge Legacy 之外的问题,您会满意吗?还是您也关心这些​​旧版浏览器?

我这么问是因为每种解决方法都会产生成本:额外的尺寸、测试和维护负担。这个问题在代码库中存在很长时间了,据我所知,这是我们收到的第一份关于它的报告。这表明它可能是一个边缘情况,并且在 IE 中关心它可能是一个更大的边缘情况。

9

我很满意除 IE 和 Edge Legacy 之外的所有其他浏览器都可以使用此修复程序。@mgol 您认为哪个版本的 JQuery 会修复此问题?

9

我们仍然需要讨论这个问题,但我预计 jQuery 3.7.1 中会出现更改。

1

在 #5282 中修复了非 IE、非 Edge Legacy 浏览器 - 这就是我们的计划,结束。