Title: [268860] trunk
Revision
268860
Author
[email protected]
Date
2020-10-22 05:41:10 -0700 (Thu, 22 Oct 2020)

Log Message

ResizeObserver is not properly garbage collected
https://bugs.webkit.org/show_bug.cgi?id=215158

Reviewed by Frédéric Wang.

Source/WebCore:

If ResizeObservers are referenced inside ResizeObserverCallbacks, they are not garbage collected properly. To fix this,
add IsWeakCallback to ResizeObserverCallback interface so that it uses JSC::Weak to store the callback. And add the callback
to visitor in JSResizeObserver::visitAdditionalChildren() to keep it alive. In order to test ResizeObserver leak, add test interface
numberOfResizeObservers() to Internals.idl.

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

* Sources.txt:
* WebCore.xcodeproj/project.pbxproj:
* bindings/js/JSResizeObserverCustom.cpp: Copied from Source/WebCore/page/ResizeObserverCallback.idl.
(WebCore::JSResizeObserver::visitAdditionalChildren): Add callback to SlotVisitor.
* dom/Document.h:
(WebCore::Document::numberOfResizeObservers const):
* page/ResizeObserver.h:
(WebCore::ResizeObserver::callback):
* page/ResizeObserver.idl: Add JSCustomMarkFunction to the interface.
* page/ResizeObserverCallback.h:
* page/ResizeObserverCallback.idl: Add IsWeakCallback to the interface.
* testing/Internals.cpp: Add numberOfResizeObservers.
(WebCore::Internals::numberOfResizeObservers const):
* testing/Internals.h:
* testing/Internals.idl:

LayoutTests:

* resize-observer/resize-observer-callback-leak-expected.txt: Added.
* resize-observer/resize-observer-callback-leak.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (268859 => 268860)


--- trunk/LayoutTests/ChangeLog	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/LayoutTests/ChangeLog	2020-10-22 12:41:10 UTC (rev 268860)
@@ -1,3 +1,13 @@
+2020-10-22  Cathie Chen  <[email protected]>
+
+        ResizeObserver is not properly garbage collected
+        https://bugs.webkit.org/show_bug.cgi?id=215158
+
+        Reviewed by Frédéric Wang.
+
+        * resize-observer/resize-observer-callback-leak-expected.txt: Added.
+        * resize-observer/resize-observer-callback-leak.html: Added.
+
 2020-10-22  Martin Robinson  <[email protected]>
 
         Scroll snap: don't create implicit snap points at scrollmin/scrollmax

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


--- trunk/LayoutTests/resize-observer/resize-observer-callback-leak-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/resize-observer/resize-observer-callback-leak-expected.txt	2020-10-22 12:41:10 UTC (rev 268860)
@@ -0,0 +1,4 @@
+
+PASS ResizeObserver implemented
+PASS test: Resize observer inside callback should be garbage collected if no one refers to it
+

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


--- trunk/LayoutTests/resize-observer/resize-observer-callback-leak.html	                        (rev 0)
+++ trunk/LayoutTests/resize-observer/resize-observer-callback-leak.html	2020-10-22 12:41:10 UTC (rev 268860)
@@ -0,0 +1,47 @@
+<!DOCTYPE html><!-- webkit-test-runner [ experimental:ResizeObserverEnabled=true ] -->
+<html>
+<head>
+<script src=""
+<script src=""
+<script src=""
+</head>
+<body>
+<div id="container"></div>
+<script>
+function accessToObserverInCallback() {
+    let test = async_test("test: Resize observer inside callback should be garbage collected if no one refers to it");
+    let initialObserverCount = internals.numberOfResizeObservers(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 ResizeObserver(() => {
+            callbackCount++;
+            observer.disconnect();
+            container.removeChild(div);
+            if (callbackCount == iterationCount) {
+                window.requestAnimationFrame(() => {
+                    gc();
+                    test.step(() => {
+                        let observerCount = internals.numberOfResizeObservers(document) - initialObserverCount;
+                        assert_true(observerCount < iterationCount, 'Resize observers should be collected.');
+                        test.done();
+                    });
+                });
+            }
+        });
+        observer.observe(div);
+    }
+}
+
+test(_ => {
+    assert_own_property(window, "ResizeObserver");
+}, "ResizeObserver implemented");
+
+window._onload_ = accessToObserverInCallback();
+</script>
+
+</body>
+
+</html>
\ No newline at end of file

Modified: trunk/Source/WebCore/ChangeLog (268859 => 268860)


--- trunk/Source/WebCore/ChangeLog	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/ChangeLog	2020-10-22 12:41:10 UTC (rev 268860)
@@ -1,3 +1,33 @@
+2020-10-22  Cathie Chen  <[email protected]>
+
+        ResizeObserver is not properly garbage collected
+        https://bugs.webkit.org/show_bug.cgi?id=215158
+
+        Reviewed by Frédéric Wang.
+
+        If ResizeObservers are referenced inside ResizeObserverCallbacks, they are not garbage collected properly. To fix this,
+        add IsWeakCallback to ResizeObserverCallback interface so that it uses JSC::Weak to store the callback. And add the callback
+        to visitor in JSResizeObserver::visitAdditionalChildren() to keep it alive. In order to test ResizeObserver leak, add test interface
+        numberOfResizeObservers() to Internals.idl.
+
+        Test: resize-observer/resize-observer-callback-leak.html
+
+        * Sources.txt:
+        * WebCore.xcodeproj/project.pbxproj:
+        * bindings/js/JSResizeObserverCustom.cpp: Copied from Source/WebCore/page/ResizeObserverCallback.idl.
+        (WebCore::JSResizeObserver::visitAdditionalChildren): Add callback to SlotVisitor.
+        * dom/Document.h:
+        (WebCore::Document::numberOfResizeObservers const):
+        * page/ResizeObserver.h:
+        (WebCore::ResizeObserver::callback):
+        * page/ResizeObserver.idl: Add JSCustomMarkFunction to the interface.
+        * page/ResizeObserverCallback.h:
+        * page/ResizeObserverCallback.idl: Add IsWeakCallback to the interface.
+        * testing/Internals.cpp: Add numberOfResizeObservers.
+        (WebCore::Internals::numberOfResizeObservers const):
+        * testing/Internals.h:
+        * testing/Internals.idl:
+
 2020-10-22  Philippe Normand  <[email protected]>
 
         [GStreamer] Restrict ImageDecoder to WebProcess

Modified: trunk/Source/WebCore/Sources.txt (268859 => 268860)


--- trunk/Source/WebCore/Sources.txt	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/Sources.txt	2020-10-22 12:41:10 UTC (rev 268860)
@@ -570,6 +570,7 @@
 bindings/js/JSReadableStreamSourceCustom.cpp
 bindings/js/JSRemoteDOMWindowBase.cpp
 bindings/js/JSRemoteDOMWindowCustom.cpp
+bindings/js/JSResizeObserverCustom.cpp
 bindings/js/JSResizeObserverEntryCustom.cpp
 bindings/js/JSSVGPathSegCustom.cpp
 bindings/js/JSSVGViewSpecCustom.cpp

Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (268859 => 268860)


--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2020-10-22 12:41:10 UTC (rev 268860)
@@ -9211,6 +9211,7 @@
 		5824ABA51AE81384009074B7 /* RubyTextElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RubyTextElement.h; sourceTree = "<group>"; };
 		582DE3221C30C85400BE02A8 /* TextDecorationPainter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextDecorationPainter.cpp; sourceTree = "<group>"; };
 		582DE3231C30C85400BE02A8 /* TextDecorationPainter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextDecorationPainter.h; sourceTree = "<group>"; };
+		5868C7C42539DA3300BF9DF3 /* JSResizeObserverCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSResizeObserverCustom.cpp; sourceTree = "<group>"; };
 		5884FE5622813E2D0040AFF6 /* JSResizeObserverEntryCustom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSResizeObserverEntryCustom.cpp; sourceTree = "<group>"; };
 		589556EC18D4A44000764B03 /* BorderEdge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BorderEdge.h; sourceTree = "<group>"; };
 		58AEE2F318D4BCCF0022E7FE /* BorderEdge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BorderEdge.cpp; sourceTree = "<group>"; };
@@ -22358,6 +22359,7 @@
 				CB38FD551CD21D5B00592A3F /* JSPerformanceEntryCustom.cpp */,
 				833CF70F20DB3F5F00141BCC /* JSPerformanceObserverCustom.cpp */,
 				A4A69B8BB91B49D0A804C31D /* JSPromiseRejectionEventCustom.cpp */,
+				5868C7C42539DA3300BF9DF3 /* JSResizeObserverCustom.cpp */,
 				5884FE5622813E2D0040AFF6 /* JSResizeObserverEntryCustom.cpp */,
 				83F572941FA1066F003837BE /* JSServiceWorkerClientCustom.cpp */,
 				460D19441FCE21DD00C3DB85 /* JSServiceWorkerGlobalScopeCustom.cpp */,

Copied: trunk/Source/WebCore/bindings/js/JSResizeObserverCustom.cpp (from rev 268859, trunk/Source/WebCore/page/ResizeObserverCallback.idl) (0 => 268860)


--- trunk/Source/WebCore/bindings/js/JSResizeObserverCustom.cpp	                        (rev 0)
+++ trunk/Source/WebCore/bindings/js/JSResizeObserverCustom.cpp	2020-10-22 12:41:10 UTC (rev 268860)
@@ -0,0 +1,40 @@
+/*
+ * 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 "JSResizeObserver.h"
+
+#include <_javascript_Core/JSCInlines.h>
+
+namespace WebCore {
+
+void JSResizeObserver::visitAdditionalChildren(JSC::SlotVisitor& visitor)
+{
+    ResizeObserverCallback* callback = wrapped().callbackConcurrently();
+    if (callback)
+        callback->visitJSFunction(visitor);
+}
+
+}

Modified: trunk/Source/WebCore/dom/Document.h (268859 => 268860)


--- trunk/Source/WebCore/dom/Document.h	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/dom/Document.h	2020-10-22 12:41:10 UTC (rev 268860)
@@ -1412,6 +1412,7 @@
 #if ENABLE(RESIZE_OBSERVER)
     void addResizeObserver(ResizeObserver&);
     void removeResizeObserver(ResizeObserver&);
+    unsigned numberOfResizeObservers() const { return m_resizeObservers.size(); }
     bool hasResizeObservers();
     // Return the minDepth of the active observations.
     size_t gatherResizeObservations(size_t deeperThan);

Modified: trunk/Source/WebCore/page/ResizeObserver.h (268859 => 268860)


--- trunk/Source/WebCore/page/ResizeObserver.h	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/page/ResizeObserver.h	2020-10-22 12:41:10 UTC (rev 268860)
@@ -64,6 +64,8 @@
     bool hasSkippedObservations() const { return m_hasSkippedObservations; }
     void setHasSkippedObservations(bool skipped) { m_hasSkippedObservations = skipped; }
 
+    ResizeObserverCallback* callbackConcurrently() { return m_callback.get(); }
+
 private:
     ResizeObserver(Document&, Ref<ResizeObserverCallback>&&);
 

Modified: trunk/Source/WebCore/page/ResizeObserver.idl (268859 => 268860)


--- trunk/Source/WebCore/page/ResizeObserver.idl	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/page/ResizeObserver.idl	2020-10-22 12:41:10 UTC (rev 268860)
@@ -29,7 +29,8 @@
     ActiveDOMObject,
     Conditional=RESIZE_OBSERVER,
     EnabledBySetting=ResizeObserver,
-    Exposed=Window
+    Exposed=Window,
+    JSCustomMarkFunction,
 ] interface ResizeObserver {
     [CallWith=Document] constructor(ResizeObserverCallback callback);
 

Modified: trunk/Source/WebCore/page/ResizeObserverCallback.h (268859 => 268860)


--- trunk/Source/WebCore/page/ResizeObserverCallback.h	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/page/ResizeObserverCallback.h	2020-10-22 12:41:10 UTC (rev 268860)
@@ -39,6 +39,9 @@
 class ResizeObserverCallback : public RefCounted<ResizeObserverCallback>, public ActiveDOMCallback {
 public:
     using ActiveDOMCallback::ActiveDOMCallback;
+
+    virtual bool hasCallback() const = 0;
+
     virtual CallbackResult<void> handleEvent(ResizeObserver&, const Vector<Ref<ResizeObserverEntry>>&, ResizeObserver&) = 0;
 };
 

Modified: trunk/Source/WebCore/page/ResizeObserverCallback.idl (268859 => 268860)


--- trunk/Source/WebCore/page/ResizeObserverCallback.idl	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/page/ResizeObserverCallback.idl	2020-10-22 12:41:10 UTC (rev 268860)
@@ -28,4 +28,5 @@
 [
     Conditional=RESIZE_OBSERVER,
     CallbackThisObject=ResizeObserver,
+    IsWeakCallback,
 ] callback ResizeObserverCallback = undefined (sequence<ResizeObserverEntry> entries, ResizeObserver observer);

Modified: trunk/Source/WebCore/testing/Internals.cpp (268859 => 268860)


--- trunk/Source/WebCore/testing/Internals.cpp	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/testing/Internals.cpp	2020-10-22 12:41:10 UTC (rev 268860)
@@ -2634,6 +2634,13 @@
 }
 #endif
 
+#if ENABLE(RESIZE_OBSERVER)
+unsigned Internals::numberOfResizeObservers(const Document& document) const
+{
+    return document.numberOfResizeObservers();
+}
+#endif
+
 uint64_t Internals::documentIdentifier(const Document& document) const
 {
     return document.identifier().toUInt64();

Modified: trunk/Source/WebCore/testing/Internals.h (268859 => 268860)


--- trunk/Source/WebCore/testing/Internals.h	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/testing/Internals.h	2020-10-22 12:41:10 UTC (rev 268860)
@@ -447,6 +447,10 @@
     unsigned numberOfIntersectionObservers(const Document&) const;
 #endif
 
+#if ENABLE(RESIZE_OBSERVER)
+    unsigned numberOfResizeObservers(const Document&) const;
+#endif
+
     uint64_t documentIdentifier(const Document&) const;
     bool isDocumentAlive(uint64_t documentIdentifier) const;
 

Modified: trunk/Source/WebCore/testing/Internals.idl (268859 => 268860)


--- trunk/Source/WebCore/testing/Internals.idl	2020-10-22 10:00:32 UTC (rev 268859)
+++ trunk/Source/WebCore/testing/Internals.idl	2020-10-22 12:41:10 UTC (rev 268860)
@@ -514,6 +514,7 @@
     unsigned long numberOfLiveDocuments();
     unsigned long referencingNodeCount(Document document);
     [Conditional=INTERSECTION_OBSERVER] unsigned long numberOfIntersectionObservers(Document document);
+    [Conditional = RESIZE_OBSERVER] unsigned long numberOfResizeObservers(Document document);
     WindowProxy? openDummyInspectorFrontend(DOMString url);
     undefined closeDummyInspectorFrontend();
     [MayThrowException] undefined setInspectorIsUnderTest(boolean isUnderTest);
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to