Title: [257138] trunk
Revision
257138
Author
[email protected]
Date
2020-02-21 09:21:45 -0800 (Fri, 21 Feb 2020)

Log Message

[Web Animations] Repeated animations on pseudo elements will fail to run after a while
https://bugs.webkit.org/show_bug.cgi?id=207993
Source/WebCore:

<rdar://problem/59428472>

Reviewed by Zalan Bujtas.

We failed to clear PseudoElement* from AnimationTimeline's various HashMaps on destruction,
causing animations to fail to run when those pointer addresses were reused.

Make DeclarativeAnimation::owningElement() be a WeakPtr<>.

Test: animations/many-pseudo-animations.html

* animation/AnimationTimeline.cpp:
(WebCore::AnimationTimeline::willDestoryRendererForElement):
(WebCore::AnimationTimeline::elementWasRemoved):
* animation/AnimationTimeline.h:
* animation/DeclarativeAnimation.cpp:
(WebCore::DeclarativeAnimation::DeclarativeAnimation):
(WebCore::DeclarativeAnimation::enqueueDOMEvent):
* animation/DeclarativeAnimation.h:
(WebCore::DeclarativeAnimation::owningElement const):
* dom/Element.cpp:
(WebCore::Element::removedFromAncestor):
* dom/PseudoElement.cpp:
(WebCore::PseudoElement::clearHostElement):
* rendering/updating/RenderTreeUpdater.cpp:
(WebCore::RenderTreeUpdater::tearDownRenderers):

LayoutTests:

Reviewed by Zalan Bujtas.

* animations/many-pseudo-animations-expected.txt: Added.
* animations/many-pseudo-animations.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (257137 => 257138)


--- trunk/LayoutTests/ChangeLog	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/LayoutTests/ChangeLog	2020-02-21 17:21:45 UTC (rev 257138)
@@ -1,3 +1,13 @@
+2020-02-21  Simon Fraser  <[email protected]>
+
+        [Web Animations] Repeated animations on pseudo elements will fail to run after a while
+        https://bugs.webkit.org/show_bug.cgi?id=207993
+
+        Reviewed by Zalan Bujtas.
+
+        * animations/many-pseudo-animations-expected.txt: Added.
+        * animations/many-pseudo-animations.html: Added.
+
 2020-02-21  Frederic Wang  <[email protected]>
 
         Update WPT tests for Intersection Observer

Added: trunk/LayoutTests/animations/many-pseudo-animations-expected.txt (0 => 257138)


--- trunk/LayoutTests/animations/many-pseudo-animations-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/animations/many-pseudo-animations-expected.txt	2020-02-21 17:21:45 UTC (rev 257138)
@@ -0,0 +1,19 @@
+Tests that animations on pseudo-elements do not eventually stop working.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS animationsStartedThisFrame is numElements
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/animations/many-pseudo-animations.html (0 => 257138)


--- trunk/LayoutTests/animations/many-pseudo-animations.html	                        (rev 0)
+++ trunk/LayoutTests/animations/many-pseudo-animations.html	2020-02-21 17:21:45 UTC (rev 257138)
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        .box {
+            width: 20px;
+            height: 20px;
+            margin: 3px;
+            display: inline-block;
+            background-color: silver;
+        }
+        
+        body.changed .box::before {
+            content: '';
+            display: block;
+            width: 10px;
+            height: 10px;
+            animation: fade 0.1s;
+        }
+        
+        @keyframes fade {
+            from { background-color: red; }
+            to   { background-color: green; }
+        }
+    </style>
+    <script src=""
+    <script>
+        window.jsTestIsAsync = true;
+        
+        const numElements = 10;
+        let animationsStartedThisFrame = 0;
+
+        function makeElement(container)
+        {
+            let div = document.createElement('div');
+            div.className = 'box';
+            container.appendChild(div);
+            
+            div.addEventListener('animationstart', (e) => {
+                ++animationsStartedThisFrame;
+            }, false);
+
+            div.addEventListener('animationend', (e) => {
+                startNextIterationSoon();
+            }, false);
+        }
+        
+        function checkStartedAnimationsCount()
+        {
+            shouldBe('animationsStartedThisFrame', 'numElements');
+        }
+        
+        let nextIterationStarted = false;
+        function startAnimationSet()
+        {
+            nextIterationStarted = false;
+            animationsStartedThisFrame = 0;
+
+            document.body.classList.add('changed');
+            requestAnimationFrame(checkStartedAnimationsCount);
+        }
+        
+        const numTrials = 10;
+        let trialCount = 0;
+
+        function startNextIterationSoon()
+        {
+            if (nextIterationStarted)
+                return;
+
+            nextIterationStarted = true;
+
+            if (trialCount++ == numTrials) {
+                finishJSTest();
+                return;
+            }
+
+            document.body.classList.remove('changed');
+            document.body.offsetWidth; // Hack.
+            requestAnimationFrame(startAnimationSet);
+        }
+        
+        function doTest()
+        {
+            description('Tests that animations on pseudo-elements do not eventually stop working.');
+
+            let container = document.getElementById('container');
+            for (let i = 0; i < numElements; ++i)
+                makeElement(container);
+
+            requestAnimationFrame(startNextIterationSoon);
+        }
+        
+        window.addEventListener('load', doTest, false);
+        var successfullyParsed = true;
+    </script>
+</head>
+<body>
+    <div id="container"></div>
+<script src=""
+</body>
+</html>

Modified: trunk/Source/WebCore/ChangeLog (257137 => 257138)


--- trunk/Source/WebCore/ChangeLog	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/ChangeLog	2020-02-21 17:21:45 UTC (rev 257138)
@@ -1,3 +1,34 @@
+2020-02-21  Simon Fraser  <[email protected]>
+
+        [Web Animations] Repeated animations on pseudo elements will fail to run after a while
+        https://bugs.webkit.org/show_bug.cgi?id=207993
+        <rdar://problem/59428472>
+
+        Reviewed by Zalan Bujtas.
+
+        We failed to clear PseudoElement* from AnimationTimeline's various HashMaps on destruction,
+        causing animations to fail to run when those pointer addresses were reused.
+
+        Make DeclarativeAnimation::owningElement() be a WeakPtr<>.
+
+        Test: animations/many-pseudo-animations.html
+
+        * animation/AnimationTimeline.cpp:
+        (WebCore::AnimationTimeline::willDestoryRendererForElement):
+        (WebCore::AnimationTimeline::elementWasRemoved):
+        * animation/AnimationTimeline.h:
+        * animation/DeclarativeAnimation.cpp:
+        (WebCore::DeclarativeAnimation::DeclarativeAnimation):
+        (WebCore::DeclarativeAnimation::enqueueDOMEvent):
+        * animation/DeclarativeAnimation.h:
+        (WebCore::DeclarativeAnimation::owningElement const):
+        * dom/Element.cpp:
+        (WebCore::Element::removedFromAncestor):
+        * dom/PseudoElement.cpp:
+        (WebCore::PseudoElement::clearHostElement):
+        * rendering/updating/RenderTreeUpdater.cpp:
+        (WebCore::RenderTreeUpdater::tearDownRenderers):
+
 2020-02-21  Lauro Moura  <[email protected]>
 
         [GStreamer] TextCombinerGStreamer is failing to compile with Gst1.14

Modified: trunk/Source/WebCore/animation/AnimationTimeline.cpp (257137 => 257138)


--- trunk/Source/WebCore/animation/AnimationTimeline.cpp	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/animation/AnimationTimeline.cpp	2020-02-21 17:21:45 UTC (rev 257138)
@@ -224,10 +224,11 @@
     }
 }
 
-void AnimationTimeline::elementWasRemoved(Element& element)
+void AnimationTimeline::willDestroyRendererForElement(Element& element)
 {
     for (auto& cssTransition : m_elementToCSSTransitionsMap.get(&element))
         cssTransition->cancel(WebAnimation::Silently::Yes);
+
     for (auto& cssAnimation : m_elementToCSSAnimationsMap.get(&element)) {
         if (is<CSSAnimation>(cssAnimation))
             removeCSSAnimationCreatedByMarkup(element, downcast<CSSAnimation>(*cssAnimation));
@@ -235,6 +236,17 @@
     }
 }
 
+void AnimationTimeline::elementWasRemoved(Element& element)
+{
+    willDestroyRendererForElement(element);
+
+    m_elementToAnimationsMap.remove(&element);
+    m_elementToCSSAnimationsMap.remove(&element);
+    m_elementToCSSTransitionsMap.remove(&element);
+    m_elementToRunningCSSTransitionByCSSPropertyID.remove(&element);
+    m_elementToCSSAnimationsCreatedByMarkupMap.remove(&element);
+}
+
 void AnimationTimeline::removeAnimationsForElement(Element& element)
 {
     for (auto& animation : animationsForElement(element))

Modified: trunk/Source/WebCore/animation/AnimationTimeline.h (257137 => 257138)


--- trunk/Source/WebCore/animation/AnimationTimeline.h	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/animation/AnimationTimeline.h	2020-02-21 17:21:45 UTC (rev 257138)
@@ -59,12 +59,17 @@
 
     enum class Ordering : uint8_t { Sorted, Unsorted };
     Vector<RefPtr<WebAnimation>> animationsForElement(Element&, Ordering ordering = Ordering::Unsorted) const;
+
     void elementWasRemoved(Element&);
     void removeAnimationsForElement(Element&);
+
     void willChangeRendererForElement(Element&);
+    void willDestroyRendererForElement(Element&);
     void cancelDeclarativeAnimationsForElement(Element&);
+
     virtual void animationWasAddedToElement(WebAnimation&, Element&);
     virtual void animationWasRemovedFromElement(WebAnimation&, Element&);
+
     void removeDeclarativeAnimationFromListsForOwningElement(WebAnimation&, Element&);
 
     void updateCSSAnimationsForElement(Element&, const RenderStyle* currentStyle, const RenderStyle& afterChangeStyle);

Modified: trunk/Source/WebCore/animation/DeclarativeAnimation.cpp (257137 => 257138)


--- trunk/Source/WebCore/animation/DeclarativeAnimation.cpp	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/animation/DeclarativeAnimation.cpp	2020-02-21 17:21:45 UTC (rev 257138)
@@ -43,7 +43,7 @@
 
 DeclarativeAnimation::DeclarativeAnimation(Element& owningElement, const Animation& backingAnimation)
     : WebAnimation(owningElement.document())
-    , m_owningElement(&owningElement)
+    , m_owningElement(makeWeakPtr(owningElement))
     , m_backingAnimation(const_cast<Animation&>(backingAnimation))
 {
 }
@@ -52,6 +52,11 @@
 {
 }
 
+Element* DeclarativeAnimation::owningElement() const
+{
+    return m_owningElement.get();
+}
+
 void DeclarativeAnimation::tick()
 {
     bool wasRelevant = isRelevant();
@@ -334,12 +339,14 @@
 
 void DeclarativeAnimation::enqueueDOMEvent(const AtomString& eventType, Seconds elapsedTime)
 {
-    ASSERT(m_owningElement);
+    if (!m_owningElement)
+        return;
+
     auto time = secondsToWebAnimationsAPITime(elapsedTime) / 1000;
     const auto& pseudoId = PseudoElement::pseudoElementNameForEvents(m_owningElement->pseudoId());
     auto timelineTime = timeline() ? timeline()->currentTime() : WTF::nullopt;
     auto event = createEvent(eventType, time, pseudoId, timelineTime);
-    event->setTarget(m_owningElement);
+    event->setTarget(m_owningElement.get());
     enqueueAnimationEvent(WTFMove(event));
 }
 

Modified: trunk/Source/WebCore/animation/DeclarativeAnimation.h (257137 => 257138)


--- trunk/Source/WebCore/animation/DeclarativeAnimation.h	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/animation/DeclarativeAnimation.h	2020-02-21 17:21:45 UTC (rev 257138)
@@ -29,6 +29,7 @@
 #include "AnimationEffectPhase.h"
 #include "WebAnimation.h"
 #include <wtf/Ref.h>
+#include <wtf/WeakPtr.h>
 
 namespace WebCore {
 
@@ -44,7 +45,7 @@
 
     bool isDeclarativeAnimation() const final { return true; }
 
-    Element* owningElement() const { return m_owningElement; }
+    Element* owningElement() const;
     const Animation& backingAnimation() const { return m_backingAnimation; }
     void setBackingAnimation(const Animation&);
     void cancelFromStyle();
@@ -87,7 +88,7 @@
     bool m_wasPending { false };
     AnimationEffectPhase m_previousPhase { AnimationEffectPhase::Idle };
 
-    Element* m_owningElement;
+    WeakPtr<Element> m_owningElement;
     Ref<Animation> m_backingAnimation;
     double m_previousIteration;
 };

Modified: trunk/Source/WebCore/dom/Element.cpp (257137 => 257138)


--- trunk/Source/WebCore/dom/Element.cpp	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/dom/Element.cpp	2020-02-21 17:21:45 UTC (rev 257138)
@@ -2272,6 +2272,7 @@
     RefPtr<Frame> frame = document().frame();
     if (auto* timeline = document().existingTimeline())
         timeline->elementWasRemoved(*this);
+
     if (frame)
         frame->animation().cancelAnimations(*this);
 

Modified: trunk/Source/WebCore/dom/PseudoElement.cpp (257137 => 257138)


--- trunk/Source/WebCore/dom/PseudoElement.cpp	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/dom/PseudoElement.cpp	2020-02-21 17:21:45 UTC (rev 257138)
@@ -90,7 +90,8 @@
     InspectorInstrumentation::pseudoElementDestroyed(document().page(), *this);
 
     if (auto* timeline = document().existingTimeline())
-        timeline->removeAnimationsForElement(*this);
+        timeline->elementWasRemoved(*this);
+    
     if (auto* frame = document().frame())
         frame->animation().cancelAnimations(*this);
 

Modified: trunk/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp (257137 => 257138)


--- trunk/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp	2020-02-21 17:06:43 UTC (rev 257137)
+++ trunk/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp	2020-02-21 17:21:45 UTC (rev 257138)
@@ -570,7 +570,7 @@
             case TeardownType::RendererUpdateCancelingAnimations:
                 if (timeline) {
                     if (document.renderTreeBeingDestroyed())
-                        timeline->elementWasRemoved(element);
+                        timeline->willDestroyRendererForElement(element);
                     else if (teardownType == TeardownType::RendererUpdateCancelingAnimations)
                         timeline->cancelDeclarativeAnimationsForElement(element);
                 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to