Commit c49d046d authored by Olga Gerchikov's avatar Olga Gerchikov Committed by Commit Bot

Initial support for WorkletAnimation.playbackRate

Changes for scroll-linked worklet animations will come in a separate pull request.


Bug: 852475
Change-Id: Ie6dd14242797cbc14a7e1e377f23c3dda174fe15
Reviewed-on: https://chromium-review.googlesource.com/c/1423537Reviewed-by: default avatarStephen McGruer <smcgruer@chromium.org>
Reviewed-by: default avatarMajid Valipour <majidvp@chromium.org>
Commit-Queue: Olga Gerchikov <gerchiko@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#628800}
parent 28106947
......@@ -37,7 +37,7 @@ class AnimationHostTest : public AnimationTimelinesTest {
client_impl_.RegisterElement(element_id_, ElementListType::ACTIVE);
worklet_animation_ = WorkletAnimation::Create(
worklet_animation_id_, "test_name", nullptr, nullptr);
worklet_animation_id_, "test_name", 1, nullptr, nullptr);
int cc_id = worklet_animation_->id();
worklet_animation_->AttachElement(element_id_);
host_->AddAnimationTimeline(timeline_);
......@@ -334,7 +334,7 @@ TEST_F(AnimationHostTest, LayerTreeMutatorUpdateReflectsScrollAnimations) {
// Create a worklet animation that is bound to the scroll timeline.
scoped_refptr<WorkletAnimation> worklet_animation(
new WorkletAnimation(animation_id2, worklet_animation_id, "test_name",
new WorkletAnimation(animation_id2, worklet_animation_id, "test_name", 1,
std::move(scroll_timeline), nullptr, true));
worklet_animation->AttachElement(element_id);
timeline_->AttachAnimation(worklet_animation);
......
......@@ -4,6 +4,7 @@
#include "cc/animation/worklet_animation.h"
#include <utility>
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/keyframe_effect.h"
#include "cc/animation/scroll_timeline.h"
......@@ -15,12 +16,14 @@ WorkletAnimation::WorkletAnimation(
int cc_animation_id,
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance)
: WorkletAnimation(cc_animation_id,
worklet_animation_id,
name,
playback_rate,
std::move(scroll_timeline),
std::move(options),
is_controlling_instance,
......@@ -30,6 +33,7 @@ WorkletAnimation::WorkletAnimation(
int cc_animation_id,
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance,
......@@ -38,6 +42,7 @@ WorkletAnimation::WorkletAnimation(
worklet_animation_id_(worklet_animation_id),
name_(name),
scroll_timeline_(std::move(scroll_timeline)),
playback_rate_(playback_rate),
options_(std::move(options)),
local_time_(base::nullopt),
start_time_(base::nullopt),
......@@ -50,11 +55,12 @@ WorkletAnimation::~WorkletAnimation() = default;
scoped_refptr<WorkletAnimation> WorkletAnimation::Create(
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options) {
return WrapRefCounted(new WorkletAnimation(
AnimationIdProvider::NextAnimationId(), worklet_animation_id, name,
std::move(scroll_timeline), std::move(options), false));
playback_rate, std::move(scroll_timeline), std::move(options), false));
}
scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const {
......@@ -62,9 +68,9 @@ scoped_refptr<Animation> WorkletAnimation::CreateImplInstance() const {
if (scroll_timeline_)
impl_timeline = scroll_timeline_->CreateImplInstance();
return WrapRefCounted(new WorkletAnimation(id(), worklet_animation_id_,
name(), std::move(impl_timeline),
CloneOptions(), true));
return WrapRefCounted(
new WorkletAnimation(id(), worklet_animation_id_, name(), playback_rate_,
std::move(impl_timeline), CloneOptions(), true));
}
void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) {
......@@ -74,6 +80,7 @@ void WorkletAnimation::PushPropertiesTo(Animation* animation_impl) {
scroll_timeline_->PushPropertiesTo(
worklet_animation_impl->scroll_timeline_.get());
}
worklet_animation_impl->SetPlaybackRate(playback_rate_);
}
void WorkletAnimation::Tick(base::TimeTicks monotonic_time) {
......@@ -140,7 +147,34 @@ void WorkletAnimation::SetOutputState(
local_time_ = state.local_times[0];
}
// TODO(crbug.com/780151): Multiply the result by the play back rate.
void WorkletAnimation::SetPlaybackRate(double playback_rate) {
if (playback_rate == playback_rate_)
return;
// Setting playback rate is rejected in the blink side if any of the
// conditions below is false.
DCHECK(playback_rate_ && !scroll_timeline_);
if (start_time_ && last_current_time_) {
// Update startTime in order to maintain previous currentTime and,
// as a result, prevent the animation from jumping.
base::TimeDelta current_time =
base::TimeDelta::FromMillisecondsD(last_current_time_.value());
start_time_ = start_time_.value() + current_time / playback_rate_ -
current_time / playback_rate;
}
playback_rate_ = playback_rate;
}
void WorkletAnimation::UpdatePlaybackRate(double playback_rate) {
if (playback_rate == playback_rate_)
return;
playback_rate_ = playback_rate;
SetNeedsPushProperties();
}
// TODO(gerchiko): Implement support playback_rate for scroll-linked
// animations. http://crbug.com/852475.
double WorkletAnimation::CurrentTime(base::TimeTicks monotonic_time,
const ScrollTree& scroll_tree,
bool is_active_tree) {
......@@ -148,7 +182,8 @@ double WorkletAnimation::CurrentTime(base::TimeTicks monotonic_time,
// by the start time. See: https://github.com/w3c/csswg-drafts/issues/2075
if (scroll_timeline_)
return scroll_timeline_->CurrentTime(scroll_tree, is_active_tree);
return (monotonic_time - start_time_.value()).InMillisecondsF();
return (monotonic_time - start_time_.value()).InMillisecondsF() *
playback_rate_;
}
bool WorkletAnimation::NeedsUpdate(base::TimeTicks monotonic_time,
......
......@@ -25,6 +25,13 @@ class ScrollTimeline;
// A WorkletAnimation is an animation that allows its animation
// timing to be controlled by an animator instance that is running in a
// AnimationWorkletGlobalScope.
// Two instances of this class are created for Blink WorkletAnimation:
// 1. UI thread instance that keeps all the meta data.
// 2. Impl thread instance that ticks the animations on the Impl thread.
// When Blink WorkletAnimation is updated, it calls the UI thread instance to
// modify its properties. The updated properties are pushed by the UI thread
// instance to the Impl thread instance during commit.
class CC_ANIMATION_EXPORT WorkletAnimation final
: public SingleKeyframeEffectAnimation {
public:
......@@ -32,12 +39,14 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
WorkletAnimation(int cc_animation_id,
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance);
static scoped_refptr<WorkletAnimation> Create(
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options);
scoped_refptr<Animation> CreateImplInstance() const override;
......@@ -70,6 +79,12 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
// require updating the ElementId for the ScrollTimeline scroll source.
void PromoteScrollTimelinePendingToActive() override;
// Called by Blink WorkletAnimation when its playback rate is updated.
void UpdatePlaybackRate(double playback_rate);
void SetPlaybackRateForTesting(double playback_rate) {
SetPlaybackRate(playback_rate);
}
void RemoveKeyframeModel(int keyframe_model_id) override;
private:
......@@ -78,6 +93,7 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
WorkletAnimation(int cc_animation_id,
WorkletAnimationId worklet_animation_id,
const std::string& name,
double playback_rate,
std::unique_ptr<ScrollTimeline> scroll_timeline,
std::unique_ptr<AnimationOptions> options,
bool is_controlling_instance,
......@@ -101,6 +117,10 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
return options_ ? options_->Clone() : nullptr;
}
// Updates the playback rate of the Impl thread instance.
// Called by the UI thread WorletAnimation instance during commit.
void SetPlaybackRate(double playback_rate);
WorkletAnimationId worklet_animation_id_;
std::string name_;
......@@ -112,6 +132,15 @@ class CC_ANIMATION_EXPORT WorkletAnimation final
// some other future implementation.
std::unique_ptr<ScrollTimeline> scroll_timeline_;
// Controls speed of the animation.
// https://drafts.csswg.org/web-animations-2/#animation-effect-playback-rate
// For UI thread instance contains the meta value to be pushed to the Impl
// thread instance.
// For the Impl thread instance contains the actual playback rate of the
// animation.
double playback_rate_;
std::unique_ptr<AnimationOptions> options_;
// Local time is used as an input to the keyframe effect of this animation.
......
......@@ -4,6 +4,7 @@
#include "cc/animation/worklet_animation.h"
#include <utility>
#include "base/memory/ptr_util.h"
#include "cc/animation/scroll_timeline.h"
#include "cc/test/animation_test_common.h"
......@@ -35,7 +36,7 @@ class WorkletAnimationTest : public AnimationTimelinesTest {
client_.RegisterElement(element_id_, ElementListType::ACTIVE);
worklet_animation_ = WrapRefCounted(
new WorkletAnimation(1, worklet_animation_id_, "test_name", nullptr,
new WorkletAnimation(1, worklet_animation_id_, "test_name", 1, nullptr,
nullptr, true /* controlling instance*/));
worklet_animation_->AttachElement(element_id_);
host_->AddAnimationTimeline(timeline_);
......@@ -64,7 +65,7 @@ TEST_F(WorkletAnimationTest, NonImplInstanceDoesNotTickKeyframe) {
scoped_refptr<WorkletAnimation> worklet_animation =
WrapRefCounted(new WorkletAnimation(
1, worklet_animation_id_, "test_name", nullptr, nullptr,
1, worklet_animation_id_, "test_name", 1, nullptr, nullptr,
false /* not impl instance*/, std::move(effect)));
EXPECT_CALL(*mock_effect, Tick(_)).Times(0);
......@@ -109,8 +110,9 @@ TEST_F(WorkletAnimationTest, LocalTimeIsUsedWhenTicking) {
TEST_F(WorkletAnimationTest, CurrentTimeCorrectlyUsesScrollTimeline) {
auto scroll_timeline = std::make_unique<MockScrollTimeline>();
EXPECT_CALL(*scroll_timeline, CurrentTime(_, _)).WillRepeatedly(Return(1234));
scoped_refptr<WorkletAnimation> worklet_animation = WorkletAnimation::Create(
worklet_animation_id_, "test_name", std::move(scroll_timeline), nullptr);
scoped_refptr<WorkletAnimation> worklet_animation =
WorkletAnimation::Create(worklet_animation_id_, "test_name", 1,
std::move(scroll_timeline), nullptr);
ScrollTree scroll_tree;
std::unique_ptr<MutatorInputState> state =
......@@ -125,7 +127,7 @@ TEST_F(WorkletAnimationTest, CurrentTimeCorrectlyUsesScrollTimeline) {
TEST_F(WorkletAnimationTest,
CurrentTimeFromRegularTimelineIsOffsetByStartTime) {
scoped_refptr<WorkletAnimation> worklet_animation = WorkletAnimation::Create(
worklet_animation_id_, "test_name", nullptr, nullptr);
worklet_animation_id_, "test_name", 1, nullptr, nullptr);
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111);
......@@ -156,6 +158,56 @@ TEST_F(WorkletAnimationTest,
EXPECT_EQ(246.8, input->updated_animations[0].current_time);
}
// Verifies correctness of current time when playback rate is set on
// initializing the animation and while the animation is playing.
TEST_F(WorkletAnimationTest, DocumentTimelineSetPlaybackRate) {
const double playback_rate_double = 2;
const double playback_rate_half = 0.5;
scoped_refptr<WorkletAnimation> worklet_animation =
WorkletAnimation::Create(worklet_animation_id_, "test_name",
playback_rate_double, nullptr, nullptr);
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111);
base::TimeTicks second_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111 + 123.4);
base::TimeTicks third_ticks =
base::TimeTicks() +
base::TimeDelta::FromMillisecondsD(111 + 123.4 + 200.0);
ScrollTree scroll_tree;
std::unique_ptr<MutatorInputState> state =
std::make_unique<MutatorInputState>();
// Start the animation.
worklet_animation->UpdateInputState(state.get(), first_ticks, scroll_tree,
true);
state.reset(new MutatorInputState);
// Play until second_ticks.
worklet_animation->UpdateInputState(state.get(), second_ticks, scroll_tree,
true);
std::unique_ptr<AnimationWorkletInput> input =
state->TakeWorkletState(worklet_animation_id_.worklet_id);
// Verify that the current time is updated twice faster than the timeline
// time.
EXPECT_EQ(123.4 * playback_rate_double,
input->updated_animations[0].current_time);
// Update the playback rate.
worklet_animation->SetPlaybackRateForTesting(playback_rate_half);
state.reset(new MutatorInputState());
// Play until third_ticks.
worklet_animation->UpdateInputState(state.get(), third_ticks, scroll_tree,
true);
input = state->TakeWorkletState(worklet_animation_id_.worklet_id);
// Verify that the current time is updated half as fast as the timeline time.
EXPECT_EQ(123.4 * playback_rate_double + 200.0 * playback_rate_half,
input->updated_animations[0].current_time);
}
// This test verifies that worklet animation state is properly updated.
TEST_F(WorkletAnimationTest, UpdateInputStateProducesCorrectState) {
AttachWorkletAnimation();
......
......@@ -16,6 +16,7 @@
#include "third_party/blink/renderer/core/animation/worklet_animation_controller.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
......@@ -132,6 +133,17 @@ void StartEffectOnCompositor(CompositorAnimation* animation,
int group = 0;
base::Optional<double> start_time = base::nullopt;
double time_offset = 0;
// Normally the playback rate of a blink animation gets translated into
// equivalent playback rate of cc::KeyframeModels.
// This has worked for regular animations since their current time was not
// exposed in cc. However, for worklet animations this does not work because
// the current time is exposed and it is an animation level concept as
// opposed to a keyframe model level concept.
// So it makes sense here that we use "1" as playback rate for KeyframeModels
// and separately plumb the playback rate to cc worklet animation.
// TODO(majidvp): Remove playbackRate from KeyframeModel in favor of having
// it on animation. https://crbug.com/925373.
double playback_rate = 1;
effect->StartAnimationOnCompositor(group, start_time, time_offset,
......@@ -229,6 +241,7 @@ WorkletAnimation::WorkletAnimation(
animator_name_(animator_name),
play_state_(Animation::kIdle),
last_play_state_(play_state_),
playback_rate_(1),
document_(document),
effects_(effects),
timeline_(timeline),
......@@ -332,6 +345,69 @@ void WorkletAnimation::UpdateIfNecessary() {
Update(kTimingUpdateOnDemand);
}
double WorkletAnimation::playbackRate(ScriptState* script_state) const {
return playback_rate_;
}
void WorkletAnimation::setPlaybackRate(ScriptState* script_state,
double playback_rate) {
if (playback_rate == playback_rate_)
return;
// TODO(https://crbug.com/821910): Implement 0 playback rate after pause()
// support is in.
if (!playback_rate) {
if (document_->GetFrame() && ExecutionContext::From(script_state)) {
document_->GetFrame()->Console().AddMessage(
ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel,
"WorkletAnimation currently does not support "
"playback rate of Zero."));
}
return;
}
// TODO(gerchiko): Implement support playback_rate for scroll-linked
// animations. http://crbug.com/852475.
if (timeline_->IsScrollTimeline()) {
if (document_->GetFrame() && ExecutionContext::From(script_state)) {
document_->GetFrame()->Console().AddMessage(
ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel,
"Scroll-linked WorkletAnimation currently "
"does not support setting playback rate."));
}
return;
}
SetPlaybackRateInternal(playback_rate);
}
void WorkletAnimation::SetPlaybackRateInternal(double playback_rate) {
DCHECK(std::isfinite(playback_rate));
DCHECK_NE(playback_rate, playback_rate_);
DCHECK(playback_rate);
DCHECK(!timeline_->IsScrollTimeline());
if (start_time_) {
base::Optional<base::TimeDelta> current_time = CurrentTime();
// TODO(gerchiko): support unresolved current time.
// Blocked by ability to change timeline.
if (current_time) {
// Update startTime in order to maintain previous currentTime and, as a
// result, prevent the animation from jumping.
// See currentTime calculation in CurrentTime().
bool is_null;
double timeline_time_ms = timeline_->currentTime(is_null);
DCHECK(!is_null);
start_time_ = base::TimeDelta::FromMillisecondsD(timeline_time_ms) -
current_time.value() / playback_rate;
}
}
playback_rate_ = playback_rate;
if (Playing())
document_->GetWorkletAnimationController().InvalidateAnimation(*this);
}
void WorkletAnimation::EffectInvalidated() {
InvalidateCompositingState();
}
......@@ -445,7 +521,7 @@ bool WorkletAnimation::StartOnCompositor() {
// update the compositor to have the correct orientation and start/end
// offset information.
compositor_animation_ = CompositorAnimation::CreateWorkletAnimation(
id_, animator_name_,
id_, animator_name_, playback_rate_,
scroll_timeline_util::ToCompositorScrollTimeline(timeline_),
std::move(options_));
compositor_animation_->SetAnimationDelegate(this);
......@@ -506,6 +582,7 @@ void WorkletAnimation::UpdateOnCompositor() {
scroll_timeline_util::GetCompositorScrollElementId(scroll_source),
start_scroll_offset, end_scroll_offset);
}
compositor_animation_->UpdatePlaybackRate(playback_rate_);
}
void WorkletAnimation::DestroyCompositorAnimation() {
......@@ -551,7 +628,7 @@ base::Optional<base::TimeDelta> WorkletAnimation::CurrentTime() const {
if (timeline_->IsScrollTimeline())
return timeline_time;
DCHECK(start_time_);
return timeline_time - start_time_.value();
return (timeline_time - start_time_.value()) * playback_rate_;
}
bool WorkletAnimation::NeedsPeek(base::TimeDelta current_time) {
......
......@@ -73,6 +73,8 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
AnimationTimeline* timeline() { return timeline_; }
String playState();
double currentTime(bool& is_null);
double playbackRate(ScriptState* script_state) const;
void setPlaybackRate(ScriptState* script_state, double playback_rate);
void play(ExceptionState& exception_state);
void cancel();
......@@ -142,6 +144,13 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
bool CheckCanStart(String* failure_message);
void SetStartTimeToNow();
// For DocumentTimeline animations, adjusts start_time_ according to playback
// rate change to preserve current time and avoid the animation output from
// jumping.
// Setting playback rate is currently not supported for scroll-linked
// animations.
void SetPlaybackRateInternal(double);
// Updates a running animation on the compositor side.
void UpdateOnCompositor();
......@@ -161,6 +170,9 @@ class MODULES_EXPORT WorkletAnimation : public WorkletAnimationBase,
const String animator_name_;
Animation::AnimationPlayState play_state_;
Animation::AnimationPlayState last_play_state_;
// Controls speed of the animation.
// https://drafts.csswg.org/web-animations-2/#animation-effect-playback-rate
double playback_rate_;
base::Optional<base::TimeDelta> start_time_;
Vector<base::Optional<base::TimeDelta>> local_times_;
// We use this to skip updating if current time has not changed since last
......
......@@ -19,6 +19,7 @@
readonly attribute AnimationTimeline? timeline;
readonly attribute AnimationPlayState playState;
readonly attribute double? currentTime;
[CallWith=ScriptState] attribute double playbackRate;
[RaisesException] void play();
void cancel();
};
......@@ -4,6 +4,7 @@
#include "third_party/blink/renderer/modules/animationworklet/worklet_animation.h"
#include <memory>
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value.h"
#include "third_party/blink/renderer/bindings/modules/v8/animation_effect_or_animation_effect_sequence.h"
......@@ -256,4 +257,87 @@ TEST_F(WorkletAnimationTest, MainThreadSendsPeekRequestTest) {
state.reset(new AnimationWorkletDispatcherInput);
}
// Verifies correctness of current time when playback rate is set while the
// animation is in idle state.
TEST_F(WorkletAnimationTest, DocumentTimelineSetPlaybackRate) {
GetDocument().GetAnimationClock().ResetTimeForTesting();
GetDocument().Timeline().ResetForTesting();
double error = base::TimeDelta::FromMicrosecondsD(1).InMillisecondsF();
WorkletAnimationId id = worklet_animation_->GetWorkletAnimationId();
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0);
base::TimeTicks second_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0 + 123.4);
double playback_rate = 2.0;
GetDocument().GetAnimationClock().ResetTimeForTesting(first_ticks);
DummyExceptionStateForTesting exception_state;
worklet_animation_->setPlaybackRate(nullptr, playback_rate);
worklet_animation_->play(exception_state);
worklet_animation_->UpdateCompositingState();
std::unique_ptr<AnimationWorkletDispatcherInput> state =
std::make_unique<AnimationWorkletDispatcherInput>();
worklet_animation_->UpdateInputState(state.get());
std::unique_ptr<AnimationWorkletInput> input =
state->TakeWorkletState(id.worklet_id);
// Zero current time is not impacted by playback rate.
EXPECT_NEAR(0, input->added_and_updated_animations[0].current_time, error);
state.reset(new AnimationWorkletDispatcherInput);
// Play the animation until second_ticks.
GetDocument().GetAnimationClock().ResetTimeForTesting(second_ticks);
worklet_animation_->UpdateInputState(state.get());
input = state->TakeWorkletState(id.worklet_id);
// Verify that the current time is updated playback_rate faster than the
// timeline time.
EXPECT_NEAR(123.4 * playback_rate, input->updated_animations[0].current_time,
error);
}
// Verifies correctness of current time when playback rate is set while the
// animation is playing.
TEST_F(WorkletAnimationTest, DocumentTimelineSetPlaybackRateWhilePlaying) {
GetDocument().GetAnimationClock().ResetTimeForTesting();
GetDocument().Timeline().ResetForTesting();
double error = base::TimeDelta::FromMicrosecondsD(1).InMillisecondsF();
WorkletAnimationId id = worklet_animation_->GetWorkletAnimationId();
base::TimeTicks first_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0);
base::TimeTicks second_ticks =
base::TimeTicks() + base::TimeDelta::FromMillisecondsD(111.0 + 123.4);
base::TimeTicks third_ticks =
base::TimeTicks() +
base::TimeDelta::FromMillisecondsD(111.0 + 123.4 + 200.0);
double playback_rate = 0.5;
// Start animation.
GetDocument().GetAnimationClock().ResetTimeForTesting(first_ticks);
DummyExceptionStateForTesting exception_state;
worklet_animation_->play(exception_state);
worklet_animation_->UpdateCompositingState();
std::unique_ptr<AnimationWorkletDispatcherInput> state =
std::make_unique<AnimationWorkletDispatcherInput>();
worklet_animation_->UpdateInputState(state.get());
state.reset(new AnimationWorkletDispatcherInput);
// Update playback rate after second tick.
GetDocument().GetAnimationClock().ResetTimeForTesting(second_ticks);
worklet_animation_->UpdateInputState(state.get());
worklet_animation_->setPlaybackRate(nullptr, playback_rate);
state.reset(new AnimationWorkletDispatcherInput);
// Verify current time after third tick.
GetDocument().GetAnimationClock().ResetTimeForTesting(third_ticks);
worklet_animation_->UpdateInputState(state.get());
std::unique_ptr<AnimationWorkletInput> input =
state->TakeWorkletState(id.worklet_id);
EXPECT_NEAR(123.4 + 200.0 * playback_rate,
input->updated_animations[0].current_time, error);
}
} // namespace blink
......@@ -22,11 +22,12 @@ std::unique_ptr<CompositorAnimation>
CompositorAnimation::CreateWorkletAnimation(
cc::WorkletAnimationId worklet_animation_id,
const String& name,
double playback_rate,
std::unique_ptr<CompositorScrollTimeline> scroll_timeline,
std::unique_ptr<cc::AnimationOptions> options) {
return std::make_unique<CompositorAnimation>(cc::WorkletAnimation::Create(
worklet_animation_id, std::string(name.Ascii().data(), name.length()),
std::move(scroll_timeline), std::move(options)));
playback_rate, std::move(scroll_timeline), std::move(options)));
}
CompositorAnimation::CompositorAnimation(
......@@ -90,6 +91,10 @@ void CompositorAnimation::UpdateScrollTimeline(
end_scroll_offset);
}
void CompositorAnimation::UpdatePlaybackRate(double playback_rate) {
cc::ToWorkletAnimation(animation_.get())->UpdatePlaybackRate(playback_rate);
}
void CompositorAnimation::NotifyAnimationStarted(base::TimeTicks monotonic_time,
int target_property,
int group) {
......
......@@ -37,6 +37,7 @@ class PLATFORM_EXPORT CompositorAnimation : public cc::AnimationDelegate {
static std::unique_ptr<CompositorAnimation> CreateWorkletAnimation(
cc::WorkletAnimationId,
const String& name,
double playback_rate,
std::unique_ptr<CompositorScrollTimeline>,
std::unique_ptr<cc::AnimationOptions>);
......@@ -64,6 +65,7 @@ class PLATFORM_EXPORT CompositorAnimation : public cc::AnimationDelegate {
void UpdateScrollTimeline(base::Optional<cc::ElementId>,
base::Optional<double> start_scroll_offset,
base::Optional<double> end_scroll_offset);
void UpdatePlaybackRate(double playback_rate);
private:
// cc::AnimationDelegate implementation.
......
<!DOCTYPE html>
<meta charset=utf-8>
<title>The playback rate of a worklet animation</title>
<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
'use strict';
// Presence of playback rate adds FP operations to calculating start_time
// and current_time of animations. That's why it's needed to increase FP error
// for comparing times in these tests.
window.assert_times_equal = (actual, expected, description) => {
assert_approx_equals(actual, expected, 0.002, description);
};
</script>
<script src="/web-animations/testcommon.js"></script>
<script src="common.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
function InstantiateWorkletAnimation(test) {
const DURATION = 10000; // ms
const KEYFRAMES = { height : ['100px', '50px'] };
return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test),
KEYFRAMES, DURATION), document.timeline);
}
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
animation.playbackRate = 0.5;
animation.play();
assert_equals(animation.currentTime, 0,
'Zero current time is not affected by playbackRate.');
}, 'Zero current time is not affected by playbackRate set while the animation is in idle state.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
animation.play();
animation.playbackRate = 0.5;
assert_equals(animation.currentTime, 0,
'Zero current time is not affected by playbackRate.');
}, 'Zero current time is not affected by playbackRate set while the animation is in play-pending state.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
const playbackRate = 2;
animation.play();
await waitForNextFrame();
// Set playback rate while the animation is playing.
const prevCurrentTime = animation.currentTime;
animation.playbackRate = playbackRate;
assert_times_equal(animation.currentTime, prevCurrentTime,
'The current time should stay unaffected by setting playback rate.');
}, 'Non zero current time is not affected by playbackRate set while the animation is in play state.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);
const playbackRate = 2;
animation.play();
await waitForNextFrame();
// Set playback rate while the animation is playing
const prevCurrentTime = animation.currentTime;
const prevTimelineTime = document.timeline.currentTime;
animation.playbackRate = playbackRate;
// Play the animation some more.
await waitForNextFrame();
const currentTime = animation.currentTime;
const currentTimelineTime = document.timeline.currentTime;
assert_times_equal(currentTime - prevCurrentTime, (currentTimelineTime - prevTimelineTime) * playbackRate,
'The current time should increase two times faster than timeline.');
}, 'The playback rate affects the rate of progress of the current time.');
promise_test(async t => {
await registerPassthroughAnimator();
const animation = InstantiateWorkletAnimation(t);;
const playbackRate = 2;
// Set playback rate while the animation is in 'idle' state.
animation.playbackRate = playbackRate;
animation.play();
const prevTimelineTime = document.timeline.currentTime;
await waitForNextFrame();
const currentTime = animation.currentTime;
const timelineTime = document.timeline.currentTime;
assert_times_equal(currentTime, (timelineTime - prevTimelineTime) * playbackRate,
'The current time should increase two times faster than timeline.');
}, 'The playback rate set before the animation started playing affects the ' +
'rate of progress of the current time');
promise_test(async t => {
await registerPassthroughAnimator();
const timing = { duration: 100,
easing: 'linear',
fill: 'none',
iterations: 1
};
const target = createDiv(t);
const keyframeEffect = new KeyframeEffect(target, { opacity: [0, 1] }, timing);
const animation = new WorkletAnimation('passthrough', keyframeEffect, document.timeline);
const playbackRate = 2;
animation.play();
animation.playbackRate = playbackRate;
await waitForNextFrame();
assert_times_equal(keyframeEffect.getComputedTiming().localTime, animation.currentTime,
'When playback rate is set on WorkletAnimation, the underlying effect\'s timing should be properly updated.');
assert_approx_equals(Number(getComputedStyle(target).opacity),
animation.currentTime / 100, 0.001,
'When playback rate is set on WorkletAnimation, the underlying effect should produce correct visual result.');
}, 'When playback rate is updated, the underlying effect is properly updated ' +
'with the current time of its WorkletAnimation and produces correct ' +
'visual result.');
</script>
</body>
\ No newline at end of file
......@@ -191,13 +191,13 @@ function waitForAnimationFramesWithDelay(minDelay) {
function waitForNextFrame() {
const timeAtStart = document.timeline.currentTime;
return new Promise(resolve => {
window.requestAnimationFrame(() => {
(function handleFrame() {
if (timeAtStart === document.timeline.currentTime) {
window.requestAnimationFrame(resolve);
window.requestAnimationFrame(handleFrame);
} else {
resolve();
}
});
}());
});
}
......
......@@ -10274,10 +10274,12 @@ interface WorkletAnimation
attribute @@toStringTag
getter currentTime
getter playState
getter playbackRate
getter timeline
method cancel
method constructor
method play
setter playbackRate
interface WritableStream
attribute @@toStringTag
getter locked
......
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