Title: [269141] trunk
Revision
269141
Author
[email protected]
Date
2020-10-29 01:15:18 -0700 (Thu, 29 Oct 2020)

Log Message

Source/WebCore:
IntersectionObserverCallback leaks
https://bugs.webkit.org/show_bug.cgi?id=218225

Reviewed by Ryosuke Niwa.

Unless the page is unloaded, JSIntersectionObserverCallback and objects inside it can not be garbage collected properly.
To fix this, make IntersectionObserverCallback as a weak callback. To keep it alive, in JSIntersectionObserver::visitAdditionalChildren
add the callback to visitor.

Test: intersection-observer/intersection-observer-callback-leak.html

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/JSIntersectionObserverCustom.cpp: Copied from Source/WebCore/page/IntersectionObserverCallback.idl.
(WebCore::JSIntersectionObserver::visitAdditionalChildren):
* page/IntersectionObserver.h:
(WebCore::IntersectionObserver::callbackConcurrently):
* page/IntersectionObserver.idl:
* page/IntersectionObserverCallback.h:
(WebCore::IntersectionObserverCallback::hasCallback const):
* page/IntersectionObserverCallback.idl:

LayoutTests:
IntersectionObserverCallback leak
https://bugs.webkit.org/show_bug.cgi?id=218225

Reviewed by Ryosuke Niwa.

In intersection-observer-callback-after-gc.html, the callbacks with observed targets deleted should stay alive as long as observers are alive.
In intersection-observer-callback-leak.html, use `internals.numberOfLiveNodes` to check that the objects inside JSIntersectionObserverCallback
can be garbage collected.

* intersection-observer/intersection-observer-callback-after-gc-expected.txt: Added.
* intersection-observer/intersection-observer-callback-after-gc.html: Added.
* intersection-observer/intersection-observer-callback-leak-expected.txt: Added.
* intersection-observer/intersection-observer-callback-leak.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (269140 => 269141)


--- trunk/LayoutTests/ChangeLog	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/LayoutTests/ChangeLog	2020-10-29 08:15:18 UTC (rev 269141)
@@ -1,3 +1,19 @@
+2020-10-29  Cathie Chen  <[email protected]>
+
+        IntersectionObserverCallback leak
+        https://bugs.webkit.org/show_bug.cgi?id=218225
+
+        Reviewed by Ryosuke Niwa.
+
+        In intersection-observer-callback-after-gc.html, the callbacks with observed targets deleted should stay alive as long as observers are alive.
+        In intersection-observer-callback-leak.html, use `internals.numberOfLiveNodes` to check that the objects inside JSIntersectionObserverCallback
+        can be garbage collected.
+
+        * intersection-observer/intersection-observer-callback-after-gc-expected.txt: Added.
+        * intersection-observer/intersection-observer-callback-after-gc.html: Added.
+        * intersection-observer/intersection-observer-callback-leak-expected.txt: Added.
+        * intersection-observer/intersection-observer-callback-leak.html: Added.
+
 2020-10-28  Julian Gonzalez  <[email protected]>
 
         Null dereference in CompositeEditCommand::cloneParagraphUnderNewElement() due to not checking for top of DOM tree

Added: trunk/LayoutTests/intersection-observer/intersection-observer-callback-after-gc-expected.txt (0 => 269141)


--- trunk/LayoutTests/intersection-observer/intersection-observer-callback-after-gc-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/intersection-observer/intersection-observer-callback-after-gc-expected.txt	2020-10-29 08:15:18 UTC (rev 269141)
@@ -0,0 +1,3 @@
+
+PASS test: IntersectionObserverCallbacks stay alive if according observers is alive
+

Added: trunk/LayoutTests/intersection-observer/intersection-observer-callback-after-gc.html (0 => 269141)


--- trunk/LayoutTests/intersection-observer/intersection-observer-callback-after-gc.html	                        (rev 0)
+++ trunk/LayoutTests/intersection-observer/intersection-observer-callback-after-gc.html	2020-10-29 08:15:18 UTC (rev 269141)
@@ -0,0 +1,57 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:IntersectionObserverEnabled=true ] -->
+<html>
+
+<head>
+    <script src=""
+    <script src=""
+    <script src=""
+</head>
+
+<body>
+    <div id="container"></div>
+    <script>
+        async function callbackSurvivesAfterGC() {
+            let test = async_test("test: IntersectionObserverCallbacks stay alive if according observers is alive");
+            const iterationCount = 10;
+            let deletedNodesCount = 0;
+            let callbackCountAfterDelete = 0;
+            let observers = [];
+            await new Promise((resolve, reject) => {
+                for (let j = 0; j < iterationCount; ++j) {
+                    const div = document.createElement('div');
+                    div.state = "toDelete";
+                    container.appendChild(div);
+                    let observer = new IntersectionObserver(entries => {
+                        for (const entry of entries) {
+                            if (entry.target.state == "toDelete") {
+                                deletedNodesCount++;
+                                container.removeChild(entry.target);
+                                entry.target.state = "deleted";
+                                if (deletedNodesCount == iterationCount)
+                                    resolve();
+                            } else if (entry.target.state == "new") {
+                                entry.target.state = "checked";
+                                callbackCountAfterDelete++;
+                            }
+                        }
+                        if (callbackCountAfterDelete == iterationCount)
+                            test.done();
+                    });
+                    observer.observe(div);
+                    observers[j] = observer;
+                }
+            });
+            gc();
+            for (let j = 0; j < iterationCount; ++j) {
+                const div = document.createElement('div');
+                div.state = "new";
+                container.appendChild(div);
+                observers[j].observe(div);
+            }
+        }
+        window._onload_ = callbackSurvivesAfterGC();
+    </script>
+
+</body>
+
+</html>
\ No newline at end of file

Added: trunk/LayoutTests/intersection-observer/intersection-observer-callback-leak-expected.txt (0 => 269141)


--- trunk/LayoutTests/intersection-observer/intersection-observer-callback-leak-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/intersection-observer/intersection-observer-callback-leak-expected.txt	2020-10-29 08:15:18 UTC (rev 269141)
@@ -0,0 +1,3 @@
+
+PASS test: Nodes inside callback should be garbage collected if no one refers to it
+

Added: trunk/LayoutTests/intersection-observer/intersection-observer-callback-leak.html (0 => 269141)


--- trunk/LayoutTests/intersection-observer/intersection-observer-callback-leak.html	                        (rev 0)
+++ trunk/LayoutTests/intersection-observer/intersection-observer-callback-leak.html	2020-10-29 08:15:18 UTC (rev 269141)
@@ -0,0 +1,45 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:IntersectionObserverEnabled=true ] -->
+<html>
+
+<head>
+    <script src=""
+    <script src=""
+    <script src=""
+</head>
+
+<body>
+    <div id="container"></div>
+    <script>
+        function accessToObserverInCallback() {
+            let test = async_test("test: Nodes inside callback should be garbage collected if no one refers to it");
+            let initialNodesCount = internals.numberOfLiveNodes(document);
+            const iterationCount = 50;
+            let callbackCount = 0;
+            for (let j = 0; j < iterationCount; ++j) {
+                const div = document.createElement('div');
+                container.appendChild(div);
+                let observer = new IntersectionObserver(() => {
+                    callbackCount++;
+                    observer.disconnect();
+                    container.removeChild(div);
+                    if (callbackCount == iterationCount) {
+                        window.requestAnimationFrame(() => {
+                            gc();
+                            test.step(() => {
+                                let additionalNodesCount = internals.numberOfLiveNodes(document) - initialNodesCount;
+                                assert_true(additionalNodesCount < iterationCount, 'Nodes inside callback should be collected.');
+                                test.done();
+                            });
+                        });
+                    }
+                });
+                observer.observe(div);
+            }
+        }
+
+        window._onload_ = accessToObserverInCallback();
+    </script>
+
+</body>
+
+</html>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (269140 => 269141)


--- trunk/Source/WebCore/ChangeLog	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/ChangeLog	2020-10-29 08:15:18 UTC (rev 269141)
@@ -1,3 +1,27 @@
+2020-10-29  Cathie Chen  <[email protected]>
+
+        IntersectionObserverCallback leaks
+        https://bugs.webkit.org/show_bug.cgi?id=218225
+
+        Reviewed by Ryosuke Niwa.
+
+        Unless the page is unloaded, JSIntersectionObserverCallback and objects inside it can not be garbage collected properly.
+        To fix this, make IntersectionObserverCallback as a weak callback. To keep it alive, in JSIntersectionObserver::visitAdditionalChildren
+        add the callback to visitor.
+
+        Test: intersection-observer/intersection-observer-callback-leak.html
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/JSIntersectionObserverCustom.cpp: Copied from Source/WebCore/page/IntersectionObserverCallback.idl.
+        (WebCore::JSIntersectionObserver::visitAdditionalChildren):
+        * page/IntersectionObserver.h:
+        (WebCore::IntersectionObserver::callbackConcurrently):
+        * page/IntersectionObserver.idl:
+        * page/IntersectionObserverCallback.h:
+        (WebCore::IntersectionObserverCallback::hasCallback const):
+        * page/IntersectionObserverCallback.idl:
+
 2020-10-28  Julian Gonzalez  <[email protected]>
 
         Null dereference in CompositeEditCommand::cloneParagraphUnderNewElement() due to not checking for top of DOM tree

Modified: trunk/Source/WebCore/Sources.txt (269140 => 269141)


--- trunk/Source/WebCore/Sources.txt	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/Sources.txt	2020-10-29 08:15:18 UTC (rev 269141)
@@ -545,6 +545,7 @@
 bindings/js/JSIDBTransactionCustom.cpp
 bindings/js/JSIDBSerializationGlobalObject.cpp
 bindings/js/JSImageDataCustom.cpp
+bindings/js/JSIntersectionObserverCustom.cpp
 bindings/js/JSIntersectionObserverEntryCustom.cpp
 bindings/js/JSLazyEventListener.cpp
 bindings/js/JSLocationCustom.cpp

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (269140 => 269141)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-10-29 08:15:18 UTC (rev 269141)
@@ -10012,6 +10012,7 @@
 		77A17AA312F28B2A004E02F6 /* JSOESVertexArrayObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSOESVertexArrayObject.h; sourceTree = "<group>"; };
 		77AAD6831ECFB66200BFA2D1 /* CredentialCreationOptions.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CredentialCreationOptions.idl; sourceTree = "<group>"; };
 		77AAD6851ECFBD3900BFA2D1 /* CredentialCreationOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CredentialCreationOptions.h; sourceTree = "<group>"; };
+		5868C7D52546E0B300BF9DF3 /* JSIntersectionObserverCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSIntersectionObserverCustom.cpp; sourceTree = "<group>"; };
 		77C13F042165658A002D9C5F /* JSIntersectionObserverEntryCustom.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSIntersectionObserverEntryCustom.cpp; sourceTree = "<group>"; };
 		77CAAAEF1F2FC35000CB5C8D /* VisualViewport.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = VisualViewport.idl; sourceTree = "<group>"; };
 		77D50FFA1ED4EC7800DA4C87 /* CredentialRequestOptions.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CredentialRequestOptions.idl; sourceTree = "<group>"; };
@@ -22363,6 +22364,7 @@
 				511EF2CE17F0FDF100E4FA16 /* JSIDBObjectStoreCustom.cpp */,
 				51E269321DD3BC43006B6A58 /* JSIDBTransactionCustom.cpp */,
 				A7D0318D0E93540300E24ACD /* JSImageDataCustom.cpp */,
+				5868C7D52546E0B300BF9DF3 /* JSIntersectionObserverCustom.cpp */,
 				77C13F042165658A002D9C5F /* JSIntersectionObserverEntryCustom.cpp */,
 				CD0320152279F5BF00DD9F0B /* JSMediaCapabilitiesCustom.h */,
 				AD726FE716D9F204003A4E6D /* JSMediaListCustom.h */,

Copied: trunk/Source/WebCore/bindings/js/JSIntersectionObserverCustom.cpp (from rev 269137, trunk/Source/WebCore/page/IntersectionObserverCallback.idl) (0 => 269141)


--- trunk/Source/WebCore/bindings/js/JSIntersectionObserverCustom.cpp	                        (rev 0)
+++ trunk/Source/WebCore/bindings/js/JSIntersectionObserverCustom.cpp	2020-10-29 08:15:18 UTC (rev 269141)
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "JSIntersectionObserver.h"
+
+#include <_javascript_Core/JSCInlines.h>
+
+namespace WebCore {
+
+void JSIntersectionObserver::visitAdditionalChildren(JSC::SlotVisitor& visitor)
+{
+    if (auto* callback = wrapped().callbackConcurrently())
+        callback->visitJSFunction(visitor);
+}
+
+}

Modified: trunk/Source/WebCore/page/IntersectionObserver.h (269140 => 269141)


--- trunk/Source/WebCore/page/IntersectionObserver.h	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/page/IntersectionObserver.h	2020-10-29 08:15:18 UTC (rev 269141)
@@ -99,6 +99,8 @@
     void appendQueuedEntry(Ref<IntersectionObserverEntry>&&);
     void notify();
 
+    IntersectionObserverCallback* callbackConcurrently() { return m_callback.get(); }
+
 private:
     IntersectionObserver(Document&, Ref<IntersectionObserverCallback>&&, ContainerNode* root, LengthBox&& parsedRootMargin, Vector<double>&& thresholds);
 

Modified: trunk/Source/WebCore/page/IntersectionObserver.idl (269140 => 269141)


--- trunk/Source/WebCore/page/IntersectionObserver.idl	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/page/IntersectionObserver.idl	2020-10-29 08:15:18 UTC (rev 269141)
@@ -29,7 +29,8 @@
     ActiveDOMObject,
     Conditional=INTERSECTION_OBSERVER,
     EnabledBySetting=IntersectionObserver,
-    Exposed=Window
+    Exposed=Window,
+    JSCustomMarkFunction,
 ] interface IntersectionObserver {
     [CallWith=Document] constructor(IntersectionObserverCallback callback, optional IntersectionObserverInit options);
 

Modified: trunk/Source/WebCore/page/IntersectionObserverCallback.h (269140 => 269141)


--- trunk/Source/WebCore/page/IntersectionObserverCallback.h	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/page/IntersectionObserverCallback.h	2020-10-29 08:15:18 UTC (rev 269141)
@@ -41,6 +41,8 @@
 public:
     using ActiveDOMCallback::ActiveDOMCallback;
 
+    virtual bool hasCallback() const { return false; }
+
     virtual CallbackResult<void> handleEvent(IntersectionObserver&, const Vector<Ref<IntersectionObserverEntry>>&, IntersectionObserver&) = 0;
 };
 

Modified: trunk/Source/WebCore/page/IntersectionObserverCallback.idl (269140 => 269141)


--- trunk/Source/WebCore/page/IntersectionObserverCallback.idl	2020-10-29 06:31:36 UTC (rev 269140)
+++ trunk/Source/WebCore/page/IntersectionObserverCallback.idl	2020-10-29 08:15:18 UTC (rev 269141)
@@ -28,4 +28,5 @@
 [
     Conditional=INTERSECTION_OBSERVER,
     CallbackThisObject=IntersectionObserver,
+    IsWeakCallback,
 ] callback IntersectionObserverCallback = undefined (sequence<IntersectionObserverEntry> entries, IntersectionObserver observer);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to