Source/WebCore/ChangeLog

 12019-10-29 Antoine Quint <graouts@apple.com>
 2
 3 WebAnimation should never prevent entering the back/forward cache
 4 https://bugs.webkit.org/show_bug.cgi?id=203088
 5 <rdar://problem/56374249>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 Test: webanimations/animation-page-cache.html
 10
 11 We remove the Web Animation override of the deprecated method ActiveDOMObject::shouldPreventEnteringBackForwardCache_DEPRECATED()
 12 and instead ensure event dispatch is suspended along with the WebAnimation object through the adoption of a SuspendableTaskQueue.
 13
 14 We also ensure an animation correctly suspends itself when ActiveDOMObject::suspend() and ActiveDOMObject::resume() are called.
 15 Implementing these methods showed that we have some placeholders in DeclarativeAnimation that were not necessary, so we remove those.
 16
 17 Finally, we no longer need to track the stopped state since the SuspendableTaskQueue will close itself when ActiveDOMObject::stop()
 18 is called.
 19
 20 * animation/DeclarativeAnimation.cpp:
 21 (WebCore::DeclarativeAnimation::stop): Deleted.
 22 (WebCore::DeclarativeAnimation::suspend): Deleted.
 23 (WebCore::DeclarativeAnimation::resume): Deleted.
 24 * animation/DeclarativeAnimation.h:
 25 * animation/WebAnimation.cpp:
 26 (WebCore::WebAnimation::WebAnimation):
 27 (WebCore::WebAnimation::enqueueAnimationPlaybackEvent):
 28 (WebCore::WebAnimation::suspend):
 29 (WebCore::WebAnimation::resume):
 30 (WebCore::WebAnimation::stop):
 31 (WebCore::WebAnimation::hasPendingActivity):
 32 (WebCore::WebAnimation::shouldPreventEnteringBackForwardCache_DEPRECATED const): Deleted.
 33 * animation/WebAnimation.h:
 34
1352019-10-29 Chris Dumez <cdumez@apple.com>
236
337 <input type="range">.setAttribute("value") does not update the value

Source/WebCore/animation/DeclarativeAnimation.cpp

@@void DeclarativeAnimation::enqueueDOMEvent(const AtomString& eventType, Seconds
346346 m_eventQueue->enqueueEvent(TransitionEvent::create(eventType, downcast<CSSTransition>(this)->transitionProperty(), time, PseudoElement::pseudoElementNameForEvents(m_owningElement->pseudoId())));
347347}
348348
349 void DeclarativeAnimation::stop()
350 {
351  WebAnimation::stop();
352 }
353 
354 void DeclarativeAnimation::suspend(ReasonForSuspension reason)
355 {
356  WebAnimation::suspend(reason);
357 }
358 
359 void DeclarativeAnimation::resume()
360 {
361  WebAnimation::resume();
362 }
363 
364349} // namespace WebCore

Source/WebCore/animation/DeclarativeAnimation.h

@@private:
8181 void enqueueDOMEvent(const AtomString&, Seconds);
8282 void remove() final;
8383
84  // ActiveDOMObject.
85  void suspend(ReasonForSuspension) final;
86  void resume() final;
87  void stop() final;
88 
8984 bool m_wasPending { false };
9085 AnimationEffectPhase m_previousPhase { AnimationEffectPhase::Idle };
9186

Source/WebCore/animation/WebAnimation.cpp

@@WebAnimation::WebAnimation(Document& document)
6767 : ActiveDOMObject(document)
6868 , m_readyPromise(makeUniqueRef<ReadyPromise>(*this, &WebAnimation::readyPromiseResolve))
6969 , m_finishedPromise(makeUniqueRef<FinishedPromise>(*this, &WebAnimation::finishedPromiseResolve))
 70 , m_taskQueue(SuspendableTaskQueue::create(document))
7071{
7172 m_readyPromise->resolve(*this);
7273 suspendIfNeeded();

@@void WebAnimation::enqueueAnimationPlaybackEvent(const AtomString& type, Optiona
613614 downcast<DocumentTimeline>(*m_timeline).enqueueAnimationPlaybackEvent(WTFMove(event));
614615 } else {
615616 // Otherwise, queue a task to dispatch event at animation. The task source for this task is the DOM manipulation task source.
616  callOnMainThread([this, pendingActivity = makePendingActivity(*this), event = WTFMove(event)]() {
617  if (!m_isStopped)
618  this->dispatchEvent(event);
 617 m_taskQueue->enqueueTask([this, event = WTFMove(event)] {
 618 dispatchEvent(event);
619619 });
620620 }
621621}

@@const char* WebAnimation::activeDOMObjectName() const
11571157 return "Animation";
11581158}
11591159
1160 // FIXME: This should never prevent entering the back/forward cache.
1161 bool WebAnimation::shouldPreventEnteringBackForwardCache_DEPRECATED() const
 1160void WebAnimation::suspend(ReasonForSuspension)
11621161{
1163  // Use the base class's implementation of hasPendingActivity() since we wouldn't want the custom implementation
1164  // in this class designed to keep JS wrappers alive to interfere with the ability for a page using animations
1165  // to enter the back/forward cache.
1166  return ActiveDOMObject::hasPendingActivity();
 1162 setSuspended(true);
 1163}
 1164
 1165void WebAnimation::resume()
 1166{
 1167 setSuspended(false);
11671168}
11681169
11691170void WebAnimation::stop()
11701171{
 1172 m_taskQueue->cancelAllTasks();
11711173 ActiveDOMObject::stop();
1172  m_isStopped = true;
11731174 removeAllEventListeners();
11741175}
11751176
11761177bool WebAnimation::hasPendingActivity() const
11771178{
11781179 // Keep the JS wrapper alive if the animation is considered relevant or could become relevant again by virtue of having a timeline.
1179  return m_timeline || m_isRelevant || ActiveDOMObject::hasPendingActivity();
 1180 return m_timeline || m_isRelevant || m_taskQueue->hasPendingTasks() || ActiveDOMObject::hasPendingActivity();
11801181}
11811182
11821183void WebAnimation::updateRelevance()

Source/WebCore/animation/WebAnimation.h

2929#include "EventTarget.h"
3030#include "ExceptionOr.h"
3131#include "IDLTypes.h"
 32#include "SuspendableTaskQueue.h"
3233#include "WebAnimationUtilities.h"
3334#include <wtf/Markable.h>
3435#include <wtf/RefCounted.h>

@@public:
140141protected:
141142 explicit WebAnimation(Document&);
142143
143  void stop() override;
144 
145144private:
146145 enum class DidSeek : uint8_t { Yes, No };
147146 enum class SynchronouslyNotify : uint8_t { Yes, No };

@@private:
176175 RefPtr<AnimationTimeline> m_timeline;
177176 UniqueRef<ReadyPromise> m_readyPromise;
178177 UniqueRef<FinishedPromise> m_finishedPromise;
 178 UniqueRef<SuspendableTaskQueue> m_taskQueue;
179179 Markable<Seconds, Seconds::MarkableTraits> m_previousCurrentTime;
180180 Markable<Seconds, Seconds::MarkableTraits> m_startTime;
181181 Markable<Seconds, Seconds::MarkableTraits> m_holdTime;

@@private:
185185
186186 int m_suspendCount { 0 };
187187
188  bool m_isStopped { false };
189188 bool m_isSuspended { false };
190189 bool m_finishNotificationStepsMicrotaskPending;
191190 bool m_isRelevant;

@@private:
197196
198197 // ActiveDOMObject.
199198 const char* activeDOMObjectName() const final;
200  bool shouldPreventEnteringBackForwardCache_DEPRECATED() const final;
 199 void suspend(ReasonForSuspension) final;
 200 void resume() final;
 201 void stop() final;
201202
202203 // EventTarget
203204 EventTargetInterface eventTargetInterface() const final { return WebAnimationEventTargetInterfaceType; }

Source/WebCore/platform/SuspendableTaskQueue.h

@@public:
5858 void close();
5959 bool isClosed() const { return m_isClosed; }
6060
61  bool hasPendingTasks() const { return m_pendingTasks.isEmpty(); }
 61 bool hasPendingTasks() const { return !m_pendingTasks.isEmpty(); }
6262
6363private:
6464 friend UniqueRef<SuspendableTaskQueue> WTF::makeUniqueRefWithoutFastMallocCheck<SuspendableTaskQueue, WebCore::ScriptExecutionContext*&>(WebCore::ScriptExecutionContext*&);

LayoutTests/ChangeLog

 12019-10-29 Antoine Quint <graouts@apple.com>
 2
 3 WebAnimation should never prevent entering the back/forward cache
 4 https://bugs.webkit.org/show_bug.cgi?id=203088
 5 <rdar://problem/56374249>
 6
 7 Reviewed by NOBODY (OOPS!).
 8
 9 Add a new test that checks that an Animation that would run past a page's navigation is correctly suspended
 10 and resumed as it enters and leaves the back/forward cache.
 11
 12 * webanimations/animation-page-cache-expected.txt: Added.
 13 * webanimations/animation-page-cache.html: Added.
 14
1152019-10-29 Chris Dumez <cdumez@apple.com>
216
317 <input type="range">.setAttribute("value") does not update the value

LayoutTests/webanimations/animation-page-cache-expected.txt

 1This test verifies that the page cache suspends and resumes Web Animations from the page cache. The test starts an animation, then navigates away, waits a bit, navigates back, confirming that the timeline froze at a given time and resumed in the same spot, advancing the animation. If successful, it outputs 'PASS' below.
 2PASS.

LayoutTests/webanimations/animation-page-cache.html

 1<!doctype html><!-- webkit-test-runner [ enableBackForwardCache=true ] -->
 2<html>
 3<script>
 4
 5let suspended = false;
 6let restored = false;
 7
 8function finish(msg)
 9{
 10 document.getElementById("result").textContent = msg;
 11 if (window.testRunner)
 12 testRunner.notifyDone();
 13}
 14
 15window.addEventListener("pagehide", event => {
 16 suspended = event.persisted;
 17 if (!suspended)
 18 finish("FAIL: event.persisted was false for pagehide event.")
 19});
 20
 21window.addEventListener("pageshow", event => {
 22 // If we haven't been suspended, then this is the initial page load, not the back navigation.
 23 if (!suspended)
 24 return;
 25
 26 restored = event.persisted;
 27 if (!restored)
 28 finish("FAIL: event.persisted was false for pageshow event.")
 29});
 30
 31window.addEventListener("DOMContentLoaded", event => {
 32 if (window.testRunner) {
 33 testRunner.dumpAsText();
 34 testRunner.waitUntilDone();
 35 }
 36
 37 // We start an animation that is just long enough that it would finish while the page is hidden.
 38 document.body.animate({ backgroundColor: "red" }, 500).finished.then(() => {
 39 if (!suspended)
 40 finish("FAIL: Animation finished but prevented the page from being suspended.");
 41 else if (!restored)
 42 finish("FAIL: Animation finished but prevented the page from being restored.");
 43 else
 44 finish("PASS.");
 45 });
 46
 47 requestAnimationFrame(() => {
 48 // Load a new page, and let it go back after 250ms.
 49 window.location.href = "data:text/html,<body onload='setTimeout(() => history.back(), 250)'></body>";
 50 });
 51});
 52
 53</script>
 54<body>
 55This test verifies that the page cache suspends and resumes Web Animations from the page cache. The test starts an animation, then navigates away, waits a bit, navigates back, confirming that the timeline froze at a given time and resumed in the same spot, advancing the animation. If successful, it outputs 'PASS' below.
 56<div id="result"></div>
 57</body>
 58</html>

LayoutTests/webanimations/leak-document-with-web-animation-expected.txt

@@On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
44
55
66The iframe has finished loading.
7 The iframe has been destroyed.
8 PASS internals.numberOfLiveDocuments() is numberOfLiveDocumentsAfterIframeLoaded - 1
9 
 7PASS The document was destroyed
108PASS successfullyParsed is true
119
1210TEST COMPLETE

LayoutTests/webanimations/leak-document-with-web-animation.html

11<!DOCTYPE html>
22<html>
33<body onload="runTest()">
4 <script src="../resources/js-test-pre.js"></script>
 4<script src="../resources/js-test.js"></script>
55<script>
66description("This test asserts that Document doesn't leak when a Web Animation is created.");
77
88if (window.internals)
99 jsTestIsAsync = true;
1010
11 gc();
12 
13 var numberOfLiveDocumentsAfterIframeLoaded = 0;
14 
1511function runTest() {
1612 if (!window.internals)
1713 return;

@@function runTest() {
2218 if (frame.src === 'about:blank')
2319 return true;
2420
25  numberOfLiveDocumentsAfterIframeLoaded = internals.numberOfLiveDocuments();
 21 documentIdentifier = internals.documentIdentifier(frame.contentDocument);
2622 debug("The iframe has finished loading.");
2723
2824 frame.remove();
2925 frame = null;
3026
31  setTimeout(() => {
32  gc();
33  setTimeout(function () {
34  debug("The iframe has been destroyed.");
35  shouldBe("internals.numberOfLiveDocuments()", "numberOfLiveDocumentsAfterIframeLoaded - 1");
36  debug("");
 27 gc();
 28 timeout = 0;
 29 handle = setInterval(() => {
 30 if (!internals.isDocumentAlive(documentIdentifier)) {
 31 clearInterval(handle);
 32 testPassed("The document was destroyed");
3733 finishJSTest();
38  });
39  });
 34 return;
 35 }
 36 timeout++;
 37 if (timeout == 500) {
 38 clearInterval(handle);
 39 testFailed("The document was leaked");
 40 finishJSTest();
 41 return;
 42 }
 43 gc();
 44 }, 10);
4045 }
4146
4247 frame.src = 'resources/web-animation-leak-iframe.html';
4348}
4449
4550</script>
46 <script src="../resources/js-test-post.js"></script>
4751</body>
48 </html>
4952\ No newline at end of file
 53</html>