Title: [233583] trunk
Revision
233583
Author
[email protected]
Date
2018-07-06 11:31:12 -0700 (Fri, 06 Jul 2018)

Log Message

[Web Animations] Using a Web Animation leaks the Document
https://bugs.webkit.org/show_bug.cgi?id=187088
<rdar://problem/41392046>

Reviewed by Darin Adler.

Source/WebCore:

Test: webanimations/leak-document-with-web-animation.html

We need to ensure that any remaining animation is cleared when the DocumentTimeline is detached from its Document.
We rename WebAnimation::prepareAnimationForRemoval() to WebAnimation::remove() since it really actively disassociates
the animation from its timeline.

An earlier version of this patch (r233349) was rolled out due to crashes caught in the ASan configuration. The following
changes were made to make it safe:

- We protect the DocumentAnimationScheduler instance in displayRefreshFired() against code that might run in a
requestAnimationFrame() callback that would trigger the object to be deleted.

- We protect the WebAnimation instance in remove() against setEffectInternal() or setTimelineInternal() potentially
causing the object to be deleted. Similar protections were addede to setEffect() and setTimeline().

- We changed ~DocumentTimeline() to a default implementation to ensure it calls ~DisplayRefreshMonitorClient()
to avoid callbacks after the object has been marked for deletion.

* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::removeAnimationsForElement): We no longer need the call to removeAnimation()
since the new WebAnimation::remove() method will also set the timeline to null which will eventually call
removeAnimation() on the disassociated timeline.
* animation/DeclarativeAnimation.cpp:
(WebCore::DeclarativeAnimation::remove):
(WebCore::DeclarativeAnimation::prepareAnimationForRemoval): Deleted.
* animation/DeclarativeAnimation.h:
* animation/DocumentAnimationScheduler.cpp:
(WebCore::DocumentAnimationScheduler::displayRefreshFired):
* animation/DocumentTimeline.cpp:
(WebCore::DocumentTimeline::detachFromDocument): Call remove() on all known animations.
(WebCore::DocumentTimeline::~DocumentTimeline): Deleted.
* animation/WebAnimation.cpp:
(WebCore::WebAnimation::remove): Set the timeline to null to fully disassociate this animation from its timeline.
(WebCore::WebAnimation::setEffect):
(WebCore::WebAnimation::setEffectInternal):
(WebCore::WebAnimation::setTimeline): Factor the internal timeline-association code out of this JS API method so
that we can call this code without any JS-facing implications.
(WebCore::WebAnimation::setTimelineInternal):
(WebCore::WebAnimation::prepareAnimationForRemoval): Deleted.
* animation/WebAnimation.h:

LayoutTests:

* webanimations/leak-document-with-web-animation-expected.txt: Added.
* webanimations/leak-document-with-web-animation.html: Added.
* webanimations/resources/web-animation-leak-iframe.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (233582 => 233583)


--- trunk/LayoutTests/ChangeLog	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/LayoutTests/ChangeLog	2018-07-06 18:31:12 UTC (rev 233583)
@@ -1,3 +1,15 @@
+2018-07-06  Antoine Quint  <[email protected]>
+
+        [Web Animations] Using a Web Animation leaks the Document
+        https://bugs.webkit.org/show_bug.cgi?id=187088
+        <rdar://problem/41392046>
+
+        Reviewed by Darin Adler.
+
+        * webanimations/leak-document-with-web-animation-expected.txt: Added.
+        * webanimations/leak-document-with-web-animation.html: Added.
+        * webanimations/resources/web-animation-leak-iframe.html: Added.
+
 2018-07-06  Saam Barati  <[email protected]>
 
         Layout Test editing/selection/navigation-clears-editor-state.html is flaky

Added: trunk/LayoutTests/webanimations/leak-document-with-web-animation-expected.txt (0 => 233583)


--- trunk/LayoutTests/webanimations/leak-document-with-web-animation-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/webanimations/leak-document-with-web-animation-expected.txt	2018-07-06 18:31:12 UTC (rev 233583)
@@ -0,0 +1,13 @@
+This test asserts that Document doesn't leak when a Web Animation is created.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+The iframe has finished loading.
+The iframe has been destroyed.
+PASS internals.numberOfLiveDocuments() is numberOfLiveDocumentsAfterIframeLoaded - 1
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/webanimations/leak-document-with-web-animation.html (0 => 233583)


--- trunk/LayoutTests/webanimations/leak-document-with-web-animation.html	                        (rev 0)
+++ trunk/LayoutTests/webanimations/leak-document-with-web-animation.html	2018-07-06 18:31:12 UTC (rev 233583)
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<body _onload_="runTest()">
+<script src=""
+<script>
+description("This test asserts that Document doesn't leak when a Web Animation is created.");
+
+if (window.internals)
+    jsTestIsAsync = true;
+
+gc();
+
+var numberOfLiveDocumentsAfterIframeLoaded = 0;
+
+function runTest() {
+    if (!window.internals)
+        return;
+
+    var frame = document.body.appendChild(document.createElement("iframe"));
+
+    frame._onload_ = function() {
+        if (frame.src ="" 'about:blank')
+            return true;
+
+        numberOfLiveDocumentsAfterIframeLoaded = internals.numberOfLiveDocuments();
+        debug("The iframe has finished loading.");
+
+        frame.remove();
+        frame = null;
+
+        setTimeout(() => {
+            gc();
+            setTimeout(function () {
+                debug("The iframe has been destroyed.");
+                shouldBe("internals.numberOfLiveDocuments()", "numberOfLiveDocumentsAfterIframeLoaded - 1");
+                debug("");
+                finishJSTest();
+            });
+        });
+    }
+
+    frame.src = '';
+}
+
+</script>
+<script src=""
+</body>
+</html>
\ No newline at end of file

Added: trunk/LayoutTests/webanimations/resources/web-animation-leak-iframe.html (0 => 233583)


--- trunk/LayoutTests/webanimations/resources/web-animation-leak-iframe.html	                        (rev 0)
+++ trunk/LayoutTests/webanimations/resources/web-animation-leak-iframe.html	2018-07-06 18:31:12 UTC (rev 233583)
@@ -0,0 +1,4 @@
+<div></div>
+<script type="text/_javascript_">
+document.querySelector("div").animate({ marginLeft: ["0", "100px"] }, 1000);
+</script>

Modified: trunk/Source/WebCore/ChangeLog (233582 => 233583)


--- trunk/Source/WebCore/ChangeLog	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/ChangeLog	2018-07-06 18:31:12 UTC (rev 233583)
@@ -1,3 +1,53 @@
+2018-07-06  Antoine Quint  <[email protected]>
+
+        [Web Animations] Using a Web Animation leaks the Document
+        https://bugs.webkit.org/show_bug.cgi?id=187088
+        <rdar://problem/41392046>
+
+        Reviewed by Darin Adler.
+
+        Test: webanimations/leak-document-with-web-animation.html
+
+        We need to ensure that any remaining animation is cleared when the DocumentTimeline is detached from its Document.
+        We rename WebAnimation::prepareAnimationForRemoval() to WebAnimation::remove() since it really actively disassociates
+        the animation from its timeline.
+
+        An earlier version of this patch (r233349) was rolled out due to crashes caught in the ASan configuration. The following
+        changes were made to make it safe:
+
+        - We protect the DocumentAnimationScheduler instance in displayRefreshFired() against code that might run in a
+        requestAnimationFrame() callback that would trigger the object to be deleted.
+
+        - We protect the WebAnimation instance in remove() against setEffectInternal() or setTimelineInternal() potentially
+        causing the object to be deleted. Similar protections were addede to setEffect() and setTimeline().
+
+        - We changed ~DocumentTimeline() to a default implementation to ensure it calls ~DisplayRefreshMonitorClient()
+        to avoid callbacks after the object has been marked for deletion.
+
+
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::removeAnimationsForElement): We no longer need the call to removeAnimation()
+        since the new WebAnimation::remove() method will also set the timeline to null which will eventually call
+        removeAnimation() on the disassociated timeline.
+        * animation/DeclarativeAnimation.cpp:
+        (WebCore::DeclarativeAnimation::remove):
+        (WebCore::DeclarativeAnimation::prepareAnimationForRemoval): Deleted.
+        * animation/DeclarativeAnimation.h:
+        * animation/DocumentAnimationScheduler.cpp:
+        (WebCore::DocumentAnimationScheduler::displayRefreshFired):
+        * animation/DocumentTimeline.cpp:
+        (WebCore::DocumentTimeline::detachFromDocument): Call remove() on all known animations.
+        (WebCore::DocumentTimeline::~DocumentTimeline): Deleted.
+        * animation/WebAnimation.cpp:
+        (WebCore::WebAnimation::remove): Set the timeline to null to fully disassociate this animation from its timeline.
+        (WebCore::WebAnimation::setEffect):
+        (WebCore::WebAnimation::setEffectInternal):
+        (WebCore::WebAnimation::setTimeline): Factor the internal timeline-association code out of this JS API method so
+        that we can call this code without any JS-facing implications.
+        (WebCore::WebAnimation::setTimelineInternal):
+        (WebCore::WebAnimation::prepareAnimationForRemoval): Deleted.
+        * animation/WebAnimation.h:
+
 2018-07-06  Daniel Bates  <[email protected]>
 
         Remove Strong Confirmation Password button

Modified: trunk/Source/WebCore/animation/AnimationTimeline.cpp (233582 => 233583)


--- trunk/Source/WebCore/animation/AnimationTimeline.cpp	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/AnimationTimeline.cpp	2018-07-06 18:31:12 UTC (rev 233583)
@@ -176,10 +176,8 @@
 
 void AnimationTimeline::removeAnimationsForElement(Element& element)
 {
-    for (auto& animation : animationsForElement(element)) {
-        animation->prepareAnimationForRemoval();
-        removeAnimation(animation.releaseNonNull());
-    }
+    for (auto& animation : animationsForElement(element))
+        animation->remove();
 }
 
 void AnimationTimeline::cancelDeclarativeAnimationsForElement(Element& element)

Modified: trunk/Source/WebCore/animation/DeclarativeAnimation.cpp (233582 => 233583)


--- trunk/Source/WebCore/animation/DeclarativeAnimation.cpp	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/DeclarativeAnimation.cpp	2018-07-06 18:31:12 UTC (rev 233583)
@@ -49,10 +49,10 @@
 {
 }
 
-void DeclarativeAnimation::prepareAnimationForRemoval()
+void DeclarativeAnimation::remove()
 {
-    WebAnimation::prepareAnimationForRemoval();
     m_eventQueue.close();
+    WebAnimation::remove();
 }
 
 void DeclarativeAnimation::setBackingAnimation(const Animation& backingAnimation)

Modified: trunk/Source/WebCore/animation/DeclarativeAnimation.h (233582 => 233583)


--- trunk/Source/WebCore/animation/DeclarativeAnimation.h	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/DeclarativeAnimation.h	2018-07-06 18:31:12 UTC (rev 233583)
@@ -45,7 +45,6 @@
     const Animation& backingAnimation() const { return m_backingAnimation; }
     void setBackingAnimation(const Animation&);
     void invalidateDOMEvents(Seconds elapsedTime = 0_s);
-    void prepareAnimationForRemoval() final;
 
     void setTimeline(RefPtr<AnimationTimeline>&&) final;
     void cancel() final;
@@ -59,6 +58,7 @@
 private:
     AnimationEffectReadOnly::Phase phaseWithoutEffect() const;
     void enqueueDOMEvent(const AtomicString&, Seconds);
+    void remove() final;
 
     Element& m_target;
     Ref<Animation> m_backingAnimation;

Modified: trunk/Source/WebCore/animation/DocumentAnimationScheduler.cpp (233582 => 233583)


--- trunk/Source/WebCore/animation/DocumentAnimationScheduler.cpp	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/DocumentAnimationScheduler.cpp	2018-07-06 18:31:12 UTC (rev 233583)
@@ -75,6 +75,9 @@
     if (!m_document || !m_document->domWindow())
         return;
 
+    // This object could be deleted after scripts in the the requestAnimationFrame callbacks are executed.
+    auto protectedThis = makeRef(*this);
+
     m_isFiring = true;
     m_lastTimestamp = Seconds(m_document->domWindow()->nowTimestamp());
 

Modified: trunk/Source/WebCore/animation/DocumentTimeline.cpp (233582 => 233583)


--- trunk/Source/WebCore/animation/DocumentTimeline.cpp	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/DocumentTimeline.cpp	2018-07-06 18:31:12 UTC (rev 233583)
@@ -55,9 +55,7 @@
 {
 }
 
-DocumentTimeline::~DocumentTimeline()
-{
-}
+DocumentTimeline::~DocumentTimeline() = default;
 
 void DocumentTimeline::detachFromDocument()
 {
@@ -64,6 +62,11 @@
     m_invalidationTaskQueue.close();
     m_eventDispatchTaskQueue.close();
     m_animationScheduleTimer.stop();
+
+    auto& animationsToRemove = animations();
+    while (!animationsToRemove.isEmpty())
+        animationsToRemove.first()->remove();
+
     m_document = nullptr;
 }
 

Modified: trunk/Source/WebCore/animation/WebAnimation.cpp (233582 => 233583)


--- trunk/Source/WebCore/animation/WebAnimation.cpp	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/WebAnimation.cpp	2018-07-06 18:31:12 UTC (rev 233583)
@@ -71,9 +71,12 @@
 {
 }
 
-void WebAnimation::prepareAnimationForRemoval()
+void WebAnimation::remove()
 {
+    // This object could be deleted after either clearing the effect or timeline relationship.
+    auto protectedThis = makeRef(*this);
     setEffectInternal(nullptr);
+    setTimelineInternal(nullptr);
 }
 
 void WebAnimation::suspendEffectInvalidation()
@@ -128,6 +131,9 @@
     // In the case of a declarative animation, we don't want to remove the animation from the relevant maps because
     // while the effect was set via the API, the element still has a transition or animation set up and we must
     // not break the timeline-to-animation relationship.
+
+    // This object could be deleted after clearing the effect relationship.
+    auto protectedThis = makeRef(*this);
     setEffectInternal(WTFMove(newEffect), isDeclarativeAnimation());
 
     // 8. Run the procedure to update an animation’s finished state for animation with the did seek flag set to false,
@@ -139,9 +145,10 @@
 
 void WebAnimation::setEffectInternal(RefPtr<AnimationEffectReadOnly>&& newEffect, bool doNotRemoveFromTimeline)
 {
-    auto oldEffect = m_effect;
+    if (m_effect == newEffect)
+        return;
 
-    m_effect = WTFMove(newEffect);
+    auto oldEffect = std::exchange(m_effect, WTFMove(newEffect));
 
     Element* previousTarget = nullptr;
     if (is<KeyframeEffectReadOnly>(oldEffect))
@@ -178,12 +185,6 @@
     if (m_startTime)
         setHoldTime(std::nullopt);
 
-    if (m_timeline)
-        m_timeline->removeAnimation(*this);
-
-    if (timeline)
-        timeline->addAnimation(*this);
-
     if (is<KeyframeEffectReadOnly>(m_effect)) {
         auto* keyframeEffect = downcast<KeyframeEffectReadOnly>(m_effect.get());
         auto* target = keyframeEffect->target();
@@ -198,7 +199,9 @@
         }
     }
 
-    m_timeline = WTFMove(timeline);
+    // This object could be deleted after clearing the timeline relationship.
+    auto protectedThis = makeRef(*this);
+    setTimelineInternal(WTFMove(timeline));
 
     setSuspended(is<DocumentTimeline>(m_timeline) && downcast<DocumentTimeline>(*m_timeline).animationsAreSuspended());
 
@@ -209,6 +212,20 @@
     updateFinishedState(DidSeek::No, SynchronouslyNotify::No);
 }
 
+void WebAnimation::setTimelineInternal(RefPtr<AnimationTimeline>&& timeline)
+{
+    if (m_timeline == timeline)
+        return;
+
+    if (m_timeline)
+        m_timeline->removeAnimation(*this);
+
+    m_timeline = WTFMove(timeline);
+
+    if (m_timeline)
+        m_timeline->addAnimation(*this);
+}
+
 void WebAnimation::effectTargetDidChange(Element* previousTarget, Element* newTarget)
 {
     if (!m_timeline)

Modified: trunk/Source/WebCore/animation/WebAnimation.h (233582 => 233583)


--- trunk/Source/WebCore/animation/WebAnimation.h	2018-07-06 18:25:59 UTC (rev 233582)
+++ trunk/Source/WebCore/animation/WebAnimation.h	2018-07-06 18:31:12 UTC (rev 233583)
@@ -63,7 +63,6 @@
 
     AnimationEffectReadOnly* effect() const { return m_effect.get(); }
     void setEffect(RefPtr<AnimationEffectReadOnly>&&);
-    void setEffectInternal(RefPtr<AnimationEffectReadOnly>&&, bool = false);
     AnimationTimeline* timeline() const { return m_timeline.get(); }
     virtual void setTimeline(RefPtr<AnimationTimeline>&&);
 
@@ -117,7 +116,7 @@
     void unsuspendEffectInvalidation();
     void setSuspended(bool);
     bool isSuspended() const { return m_isSuspended; }
-    virtual void prepareAnimationForRemoval();
+    virtual void remove();
 
     String description();
 
@@ -156,7 +155,9 @@
     void runPendingPauseTask();
     void runPendingPlayTask();
     void resetPendingTasks();
-    
+    void setEffectInternal(RefPtr<AnimationEffectReadOnly>&&, bool = false);
+    void setTimelineInternal(RefPtr<AnimationTimeline>&&);
+
     String m_id;
     RefPtr<AnimationEffectReadOnly> m_effect;
     RefPtr<AnimationTimeline> m_timeline;
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to