Commit 6341dbcc authored by Robert Flack's avatar Robert Flack Committed by Commit Bot

Perform a microtask checkpoint after updating timelines.

Updating timelines can enqueue tasks related to ready and finish
promises which should run before animation events and updated
rendering.

Bug: 1116020
Change-Id: Ica1927bb3921eef574abeaca23bb198dda658ccd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2354164Reviewed-by: default avatarKevin Ellis <kevers@chromium.org>
Commit-Queue: Robert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#798259}
parent 09e03bb6
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
#include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/page/page_animator.h" #include "third_party/blink/renderer/core/page/page_animator.h"
#include "third_party/blink/renderer/platform/bindings/microtask.h"
namespace blink { namespace blink {
...@@ -73,6 +74,14 @@ void DocumentAnimations::AddTimeline(AnimationTimeline& timeline) { ...@@ -73,6 +74,14 @@ void DocumentAnimations::AddTimeline(AnimationTimeline& timeline) {
void DocumentAnimations::UpdateAnimationTimingForAnimationFrame() { void DocumentAnimations::UpdateAnimationTimingForAnimationFrame() {
UpdateAnimationTiming(*document_, timelines_, kTimingUpdateForAnimationFrame); UpdateAnimationTiming(*document_, timelines_, kTimingUpdateForAnimationFrame);
// Perform a microtask checkpoint per step 3 of
// https://drafts.csswg.org/web-animations-1/#timelines. This is to
// ensure that any microtasks queued up as a result of resolving or
// rejecting Promise objects as part of updating timelines run their
// callbacks prior to dispatching animation events and generating
// the next main frame.
Microtask::PerformCheckpoint(V8PerIsolateData::MainThreadIsolate());
} }
bool DocumentAnimations::NeedsAnimationTimingUpdate() { bool DocumentAnimations::NeedsAnimationTimingUpdate() {
......
...@@ -198,6 +198,11 @@ promise_test(async t => { ...@@ -198,6 +198,11 @@ promise_test(async t => {
scroller.scrollTop = maxScroll; scroller.scrollTop = maxScroll;
await animation.finished; await animation.finished;
// Wait for next frame to allow the animation to send finish events. The
// finished promise fires before events are sent.
await waitForNextFrame();
assert_true(sent_finish_event, assert_true(sent_finish_event,
"Animation finished event is sent on reaching max scroll."); "Animation finished event is sent on reaching max scroll.");
}, 'Sending animation finished events by finished animation on reverse ' + }, 'Sending animation finished events by finished animation on reverse ' +
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
<script src="/resources/testharness.js"></script> <script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script> <script src="/resources/testharnessreport.js"></script>
<script src="../../testcommon.js"></script> <script src="../../testcommon.js"></script>
<style>
@keyframes opacity-animation {
from { opacity: 1; }
to { opacity: 0; }
}
</style>
<div id="log"></div> <div id="log"></div>
<script> <script>
'use strict'; 'use strict';
...@@ -84,4 +90,23 @@ async_test(t => { ...@@ -84,4 +90,23 @@ async_test(t => {
})); }));
}, 'Performs a microtask checkpoint after updating timelins'); }, 'Performs a microtask checkpoint after updating timelins');
async_test(t => {
const div = createDiv(t);
let readyPromiseRan = false;
let finishedPromiseRan = false;
div.style.animation = 'opacity-animation 1ms';
let anim = div.getAnimations()[0];
anim.ready.then(t.step_func(() => {
readyPromiseRan = true;
}));
div.addEventListener('animationstart', t.step_func(() => {
assert_true(readyPromiseRan, 'It should run ready promise before animationstart event');
}));
anim.finished.then(t.step_func(() => {
finishedPromiseRan = true;
}));
div.addEventListener('animationend', t.step_func_done(() => {
assert_true(finishedPromiseRan, 'It should run finished promise before animationend event');
}));
}, 'Runs finished promise before animation events');
</script> </script>
...@@ -38,7 +38,7 @@ PASS Does NOT remove an animation after making a redundant change to another ani ...@@ -38,7 +38,7 @@ PASS Does NOT remove an animation after making a redundant change to another ani
PASS Does NOT remove an animation after making a redundant change to its effect's properties PASS Does NOT remove an animation after making a redundant change to its effect's properties
FAIL Updates ALL timelines before checking for replacement promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter" FAIL Updates ALL timelines before checking for replacement promise_test: Unhandled rejection with value: object "TypeError: Cannot set property timeline of #<Animation> which has only a getter"
PASS Dispatches remove events after finish events PASS Dispatches remove events after finish events
FAIL Fires remove event before requestAnimationFrame assert_true: Not expecting event, but got remove event expected true got false PASS Fires remove event before requestAnimationFrame
PASS Queues all remove events before running them PASS Queues all remove events before running them
PASS Performs removal in deeply nested iframes PASS Performs removal in deeply nested iframes
Harness: the test ran to completion. Harness: the test ran to completion.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment