Reland "Initial implementation of cc::ScrollTimeline"
This is a reland of 31974d24b5de701ff9c290b32e4dd543abec8146
Original change's description:
> Initial implementation of cc::ScrollTimeline
>
> This creates a compositor side concept of a ScrollTimeline, and adds
> plumbing so that blink WorkletAnimations created with ScrollTimelines
> will create a cc::ScrollTimeline on the compositor side. This means
> that scroll-driven AnimationWorklets are now possible.
>
> There are a number of caveats to this initial implementation:
> * We never update the cc::ScrollTimeline, so if the scroller
> changes on the blink side we won't know.
> * We assume that the scroller is composited. If it isn't composited
> during animation play() we will log a warning and fail to start,
> but if it is composited at that time and later becomes non-composited
> we will likely crash.
>
> Bug: 776952
>
> Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
> Change-Id: Id0342c62209b387130a69edca4ff3682748bd8a3
> Reviewed-on: https://chromium-review.googlesource.com/738437
> Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
> Reviewed-by: Robert Flack <flackr@chromium.org>
> Reviewed-by: dstockwell <dstockwell@chromium.org>
> Reviewed-by: Majid Valipour <majidvp@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#515645}
Bug: 776952
Change-Id: I372d8fd48c62a63b5c6b10688d3829e4ac2c3be1
Cq-Include-Trybots: master.tryserver.blink:linux_trusty_blink_rel
Reviewed-on: https://chromium-review.googlesource.com/764578
Reviewed-by: Robert Flack <flackr@chromium.org>
Reviewed-by: Eric Willigers <ericwilligers@chromium.org>
Reviewed-by: dstockwell <dstockwell@chromium.org>
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#515985}
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 6c78ebe6..83dba38 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -690,6 +690,7 @@
"animation/element_animations_unittest.cc",
"animation/keyframed_animation_curve_unittest.cc",
"animation/scroll_offset_animation_curve_unittest.cc",
+ "animation/scroll_timeline_unittest.cc",
"animation/transform_operations_unittest.cc",
"animation/worklet_animation_player_unittest.cc",
diff --git a/cc/animation/BUILD.gn b/cc/animation/BUILD.gn
index d8b4fe0..16fe4a33 100644
--- a/cc/animation/BUILD.gn
+++ b/cc/animation/BUILD.gn
@@ -36,6 +36,8 @@
"scroll_offset_animations.h",
"scroll_offset_animations_impl.cc",
"scroll_offset_animations_impl.h",
+ "scroll_timeline.cc",
+ "scroll_timeline.h",
"timing_function.cc",
"timing_function.h",
"transform_operation.cc",
diff --git a/cc/animation/DEPS b/cc/animation/DEPS
index e4afe36..a59045d 100644
--- a/cc/animation/DEPS
+++ b/cc/animation/DEPS
@@ -10,5 +10,7 @@
"+cc/trees/mutator_host.h",
"+cc/trees/mutator_host_client.h",
"+cc/trees/property_animation_state.h",
+ "+cc/trees/property_tree.h",
+ "+cc/trees/scroll_node.h",
"+cc/trees/target_property.h",
]
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index 405b2ab5..debdf30 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -20,6 +20,7 @@
#include "cc/animation/scroll_offset_animation_curve.h"
#include "cc/animation/scroll_offset_animations.h"
#include "cc/animation/scroll_offset_animations_impl.h"
+#include "cc/animation/scroll_timeline.h"
#include "cc/animation/timing_function.h"
#include "cc/animation/worklet_animation_player.h"
#include "ui/gfx/geometry/box_f.h"
@@ -292,7 +293,8 @@
return true;
}
-bool AnimationHost::TickAnimations(base::TimeTicks monotonic_time) {
+bool AnimationHost::TickAnimations(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) {
TRACE_EVENT0("cc", "AnimationHost::TickAnimations");
bool did_animate = false;
@@ -308,14 +310,15 @@
// trees similar to other animations. However our final goal is to only call
// it once, ideally after activation, and only when the input
// to an active timeline has changed. http://crbug.com/767210
- mutator_->Mutate(CollectAnimatorsState(monotonic_time));
+ mutator_->Mutate(CollectAnimatorsState(monotonic_time, scroll_tree));
did_animate = true;
}
return did_animate;
}
-void AnimationHost::TickScrollAnimations(base::TimeTicks monotonic_time) {
+void AnimationHost::TickScrollAnimations(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) {
// TODO(majidvp) For now the logic simply assumes all AnimationWorklet
// animations depend on scroll offset but this is inefficient. We need a more
// fine-grained approach based on invalidating individual ScrollTimelines and
@@ -325,11 +328,12 @@
// TODO(majidvp): We need to return a boolean here so that LTHI knows
// whether it needs to schedule another frame.
if (mutator_)
- mutator_->Mutate(CollectAnimatorsState(monotonic_time));
+ mutator_->Mutate(CollectAnimatorsState(monotonic_time, scroll_tree));
}
std::unique_ptr<MutatorInputState> AnimationHost::CollectAnimatorsState(
- base::TimeTicks timeline_time) {
+ base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) {
TRACE_EVENT0("cc", "AnimationHost::CollectAnimatorsState");
std::unique_ptr<MutatorInputState> result =
base::MakeUnique<MutatorInputState>();
@@ -340,10 +344,9 @@
WorkletAnimationPlayer* worklet_player =
static_cast<WorkletAnimationPlayer*>(player.get());
- // TODO(majidvp): Do not assume timeline time to be the worklet player's
- // current time but instead ask the player to provide it.
MutatorInputState::AnimationState state{
- worklet_player->id(), worklet_player->name(), timeline_time};
+ worklet_player->id(), worklet_player->name(),
+ worklet_player->CurrentTime(monotonic_time, scroll_tree)};
result->animations.push_back(std::move(state));
}
diff --git a/cc/animation/animation_host.h b/cc/animation/animation_host.h
index 2b5f01c..cfd8416 100644
--- a/cc/animation/animation_host.h
+++ b/cc/animation/animation_host.h
@@ -100,8 +100,10 @@
bool NeedsTickAnimations() const override;
bool ActivateAnimations() override;
- bool TickAnimations(base::TimeTicks monotonic_time) override;
- void TickScrollAnimations(base::TimeTicks monotonic_time) override;
+ bool TickAnimations(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) override;
+ void TickScrollAnimations(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) override;
bool UpdateAnimationState(bool start_ready_animations,
MutatorEvents* events) override;
@@ -198,7 +200,8 @@
// Return the animator state representing all ticking worklet animations.
std::unique_ptr<MutatorInputState> CollectAnimatorsState(
- base::TimeTicks timeline_time);
+ base::TimeTicks timeline_time,
+ const ScrollTree& scroll_tree);
ElementToAnimationsMap element_to_animations_map_;
PlayersList ticking_players_;
diff --git a/cc/animation/scroll_timeline.cc b/cc/animation/scroll_timeline.cc
new file mode 100644
index 0000000..a880877
--- /dev/null
+++ b/cc/animation/scroll_timeline.cc
@@ -0,0 +1,60 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/animation/scroll_timeline.h"
+
+#include "cc/trees/property_tree.h"
+#include "cc/trees/scroll_node.h"
+#include "ui/gfx/geometry/scroll_offset.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace cc {
+
+ScrollTimeline::ScrollTimeline(ElementId scroller_id,
+ ScrollDirection orientation,
+ double time_range)
+ : scroller_id_(scroller_id),
+ orientation_(orientation),
+ time_range_(time_range) {}
+
+std::unique_ptr<ScrollTimeline> ScrollTimeline::CreateImplInstance() const {
+ return std::make_unique<ScrollTimeline>(scroller_id_, orientation_,
+ time_range_);
+}
+
+double ScrollTimeline::CurrentTime(const ScrollTree& scroll_tree) const {
+ // If the scroller isn't in the ScrollTree, the element either no longer
+ // exists or is not currently scrollable. By the spec, return an unresolved
+ // time value.
+ if (!scroll_tree.FindNodeFromElementId(scroller_id_))
+ return std::numeric_limits<double>::quiet_NaN();
+
+ gfx::ScrollOffset offset = scroll_tree.current_scroll_offset(scroller_id_);
+ DCHECK_GE(offset.x(), 0);
+ DCHECK_GE(offset.y(), 0);
+
+ gfx::ScrollOffset scroll_dimensions = scroll_tree.MaxScrollOffset(
+ scroll_tree.FindNodeFromElementId(scroller_id_)->id);
+
+ double current_offset = (orientation_ == Vertical) ? offset.y() : offset.x();
+ double max_offset = (orientation_ == Vertical) ? scroll_dimensions.y()
+ : scroll_dimensions.x();
+
+ // 3. If current scroll offset is less than startScrollOffset, return an
+ // unresolved time value if fill is none or forwards, or 0 otherwise.
+ // TODO(smcgruer): Implement |startScrollOffset| and |fill|.
+
+ // 4. If current scroll offset is greater than or equal to endScrollOffset,
+ // return an unresolved time value if fill is none or backwards, or the
+ // effective time range otherwise.
+ // TODO(smcgruer): Implement |endScrollOffset| and |fill|.
+
+ // 5. Return the result of evaluating the following expression:
+ // ((current scroll offset - startScrollOffset) /
+ // (endScrollOffset - startScrollOffset)) * effective time range
+
+ return (std::abs(current_offset) / max_offset) * time_range_;
+}
+
+} // namespace cc
diff --git a/cc/animation/scroll_timeline.h b/cc/animation/scroll_timeline.h
new file mode 100644
index 0000000..7389d4df
--- /dev/null
+++ b/cc/animation/scroll_timeline.h
@@ -0,0 +1,61 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CC_ANIMATION_SCROLL_TIMELINE_H_
+#define CC_ANIMATION_SCROLL_TIMELINE_H_
+
+#include "cc/animation/animation_export.h"
+#include "cc/trees/element_id.h"
+
+namespace cc {
+
+class ScrollTree;
+
+// A ScrollTimeline is an animation timeline that bases its current time on the
+// progress of scrolling in some scroll container.
+//
+// This is the compositor-side representation of the web concept expressed in
+// https://wicg.github.io/scroll-animations/#scrolltimeline-interface. There are
+// differences between this class and the web definition of a ScrollTimeline.
+// For example the compositor does not know (or care) about 'writing modes', so
+// this class only tracks whether the ScrollTimeline orientation is horizontal
+// or vertical. Blink is expected to resolve any such 'web' requirements and
+// construct/update the compositor ScrollTimeline accordingly.
+class CC_ANIMATION_EXPORT ScrollTimeline {
+ public:
+ enum ScrollDirection { Horizontal, Vertical };
+
+ ScrollTimeline(ElementId scroller_id,
+ ScrollDirection orientation,
+ double time_range);
+ virtual ~ScrollTimeline() {}
+
+ // Create a copy of this ScrollTimeline intended for the impl thread in the
+ // compositor.
+ std::unique_ptr<ScrollTimeline> CreateImplInstance() const;
+
+ // Calculate the current time of the ScrollTimeline. This is either a double
+ // value or std::numeric_limits<double>::quiet_NaN() if the current time is
+ // unresolved.
+ virtual double CurrentTime(const ScrollTree& scroll_tree) const;
+
+ private:
+ // The scroller which this ScrollTimeline is based on. It is expected that
+ // this scroller will exist in the scroll property tree, or otherwise calling
+ // CurrentTime will fail.
+ ElementId scroller_id_;
+
+ // The orientation of the ScrollTimeline indicates which axis of the scroller
+ // it should base its current time on - either the horizontal or vertical.
+ ScrollDirection orientation_;
+
+ // A ScrollTimeline maps from the scroll offset in the scroller to a time
+ // value based on a 'time range'. See the implementation of CurrentTime or the
+ // spec for details.
+ double time_range_;
+};
+
+} // namespace cc
+
+#endif // CC_ANIMATION_SCROLL_TIMELINE_H_
diff --git a/cc/animation/scroll_timeline_unittest.cc b/cc/animation/scroll_timeline_unittest.cc
new file mode 100644
index 0000000..4b410137
--- /dev/null
+++ b/cc/animation/scroll_timeline_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "cc/animation/scroll_timeline.h"
+#include "cc/trees/property_tree.h"
+#include "cc/trees/scroll_node.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/scroll_offset.h"
+
+namespace cc {
+
+class ScrollTimelineTest : public ::testing::Test {
+ public:
+ ScrollTimelineTest()
+ : scroller_id_(1), container_size_(100, 100), content_size_(500, 500) {
+ // For simplicity we make the property_tree main thread; this avoids the
+ // need to deal with the synced scroll offset code.
+ property_trees_.is_main_thread = true;
+ property_trees_.is_active = true;
+
+ // We add a single node that is scrolling a 550x1100 contents inside a
+ // 50x100 container.
+ ScrollNode node;
+ node.scrollable = true;
+ node.bounds = content_size_;
+ node.container_bounds = container_size_;
+
+ int node_id = property_trees_.scroll_tree.Insert(node, 0);
+ property_trees_.element_id_to_scroll_node_index[scroller_id_] = node_id;
+ }
+
+ ScrollTree& scroll_tree() { return property_trees_.scroll_tree; }
+ ElementId scroller_id() const { return scroller_id_; }
+ gfx::Size container_size() const { return container_size_; }
+ gfx::Size content_size() const { return content_size_; }
+
+ private:
+ PropertyTrees property_trees_;
+ ElementId scroller_id_;
+ gfx::Size container_size_;
+ gfx::Size content_size_;
+};
+
+TEST_F(ScrollTimelineTest, BasicCurrentTimeCalculations) {
+ // For simplicity, we set the time range such that the current time maps
+ // directly to the scroll offset. We have a square scroller/contents, so can
+ // just compute one edge and use it for vertical/horizontal.
+ double time_range = content_size().height() - container_size().height();
+
+ ScrollTimeline vertical_timeline(scroller_id(), ScrollTimeline::Vertical,
+ time_range);
+ ScrollTimeline horizontal_timeline(scroller_id(), ScrollTimeline::Horizontal,
+ time_range);
+
+ // Unscrolled, both timelines should read a current time of 0.
+ scroll_tree().SetScrollOffset(scroller_id(), gfx::ScrollOffset());
+ EXPECT_FLOAT_EQ(0, vertical_timeline.CurrentTime(scroll_tree()));
+ EXPECT_FLOAT_EQ(0, horizontal_timeline.CurrentTime(scroll_tree()));
+
+ // Now do some scrolling and make sure that the ScrollTimelines update.
+ scroll_tree().SetScrollOffset(scroller_id(), gfx::ScrollOffset(75, 50));
+
+ // As noted above, we have mapped the time range such that current time should
+ // just be the scroll offset.
+ EXPECT_FLOAT_EQ(50, vertical_timeline.CurrentTime(scroll_tree()));
+ EXPECT_FLOAT_EQ(75, horizontal_timeline.CurrentTime(scroll_tree()));
+}
+
+TEST_F(ScrollTimelineTest, CurrentTimeIsAdjustedForTimeRange) {
+ // Here we set a time range to 100, which gives the current time the form of
+ // 'percentage scrolled'.
+ ScrollTimeline timeline(scroller_id(), ScrollTimeline::Vertical, 100);
+
+ double halfwayY = (content_size().height() - container_size().height()) / 2.;
+ scroll_tree().SetScrollOffset(scroller_id(), gfx::ScrollOffset(0, halfwayY));
+
+ EXPECT_FLOAT_EQ(50, timeline.CurrentTime(scroll_tree()));
+}
+
+} // namespace cc
diff --git a/cc/animation/worklet_animation_player.cc b/cc/animation/worklet_animation_player.cc
index 95ed7405..ce7454c 100644
--- a/cc/animation/worklet_animation_player.cc
+++ b/cc/animation/worklet_animation_player.cc
@@ -5,23 +5,36 @@
#include "cc/animation/worklet_animation_player.h"
#include "base/memory/ptr_util.h"
+#include "cc/animation/scroll_timeline.h"
namespace cc {
-WorkletAnimationPlayer::WorkletAnimationPlayer(int id, const std::string& name)
- : AnimationPlayer(id), name_(name) {}
+WorkletAnimationPlayer::WorkletAnimationPlayer(
+ int id,
+ const std::string& name,
+ std::unique_ptr<ScrollTimeline> scroll_timeline)
+ : AnimationPlayer(id),
+ name_(name),
+ scroll_timeline_(std::move(scroll_timeline)) {}
WorkletAnimationPlayer::~WorkletAnimationPlayer() {}
scoped_refptr<WorkletAnimationPlayer> WorkletAnimationPlayer::Create(
int id,
- const std::string& name) {
- return WrapRefCounted(new WorkletAnimationPlayer(id, name));
+ const std::string& name,
+ std::unique_ptr<ScrollTimeline> scroll_timeline) {
+ return WrapRefCounted(
+ new WorkletAnimationPlayer(id, name, std::move(scroll_timeline)));
}
scoped_refptr<AnimationPlayer> WorkletAnimationPlayer::CreateImplInstance()
const {
- return WrapRefCounted(new WorkletAnimationPlayer(id(), name()));
+ std::unique_ptr<ScrollTimeline> impl_timeline;
+ if (scroll_timeline_)
+ impl_timeline = scroll_timeline_->CreateImplInstance();
+
+ return WrapRefCounted(
+ new WorkletAnimationPlayer(id(), name(), std::move(impl_timeline)));
}
void WorkletAnimationPlayer::SetLocalTime(base::TimeDelta local_time) {
@@ -33,6 +46,19 @@
animation_ticker_->Tick(monotonic_time, this);
}
+// TODO(crbug.com/780151): The current time returned should be an offset against
+// the animation's start time and based on the playback rate, not just the
+// timeline time directly.
+double WorkletAnimationPlayer::CurrentTime(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) {
+ if (scroll_timeline_) {
+ return scroll_timeline_->CurrentTime(scroll_tree);
+ }
+
+ // TODO(crbug.com/783333): Support DocumentTimeline's originTime concept.
+ return (monotonic_time - base::TimeTicks()).InMillisecondsF();
+}
+
base::TimeTicks WorkletAnimationPlayer::GetTimeForAnimation(
const Animation& animation) const {
// Animation player local time is equivalent to animation active time. So
diff --git a/cc/animation/worklet_animation_player.h b/cc/animation/worklet_animation_player.h
index 33c5641..6859e1f5 100644
--- a/cc/animation/worklet_animation_player.h
+++ b/cc/animation/worklet_animation_player.h
@@ -12,6 +12,8 @@
namespace cc {
+class ScrollTimeline;
+
// A WorkletAnimationPlayer is an animations player that allows its animation
// timing to be controlled by an animator instance that is running in a
// AnimationWorkletGlobalScope.
@@ -19,17 +21,30 @@
: public AnimationPlayer,
AnimationTicker::AnimationTimeProvider {
public:
- WorkletAnimationPlayer(int id, const std::string& name);
- static scoped_refptr<WorkletAnimationPlayer> Create(int id,
- const std::string& name);
+ WorkletAnimationPlayer(int id,
+ const std::string& name,
+ std::unique_ptr<ScrollTimeline> scroll_timeline);
+ static scoped_refptr<WorkletAnimationPlayer> Create(
+ int id,
+ const std::string& name,
+ std::unique_ptr<ScrollTimeline> scroll_timeline);
scoped_refptr<AnimationPlayer> CreateImplInstance() const override;
const std::string& name() const { return name_; }
+ const ScrollTimeline* scroll_timeline() const {
+ return scroll_timeline_.get();
+ }
+
void SetLocalTime(base::TimeDelta local_time);
bool IsWorkletAnimationPlayer() const override;
void Tick(base::TimeTicks monotonic_time) override;
+ // Returns the current time to be passed into the underlying AnimationWorklet.
+ // The current time is based on the timeline associated with the animation.
+ double CurrentTime(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree);
+
// AnimationTicker::AnimationTimeProvider:
base::TimeTicks GetTimeForAnimation(
const Animation& animation) const override;
@@ -40,6 +55,15 @@
~WorkletAnimationPlayer() override;
std::string name_;
+
+ // The ScrollTimeline associated with the underlying animation. If null, the
+ // animation is based on a DocumentTimeline.
+ //
+ // TODO(crbug.com/780148): A WorkletAnimationPlayer should own an
+ // AnimationTimeline which must exist but can either be a DocumentTimeline,
+ // ScrollTimeline, or some other future implementation.
+ std::unique_ptr<ScrollTimeline> scroll_timeline_;
+
base::TimeDelta local_time_;
};
diff --git a/cc/animation/worklet_animation_player_unittest.cc b/cc/animation/worklet_animation_player_unittest.cc
index 940f2df0..100d9b8 100644
--- a/cc/animation/worklet_animation_player_unittest.cc
+++ b/cc/animation/worklet_animation_player_unittest.cc
@@ -1,11 +1,14 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+
#include "cc/animation/worklet_animation_player.h"
+#include "cc/animation/scroll_timeline.h"
#include "cc/test/animation_test_common.h"
#include "cc/test/animation_timelines_test_common.h"
#include "cc/test/mock_layer_tree_mutator.h"
+#include "cc/trees/property_tree.h"
#include "testing/gmock/include/gmock/gmock.h"
using ::testing::Mock;
@@ -24,6 +27,13 @@
int worklet_player_id_ = 11;
};
+class MockScrollTimeline : public ScrollTimeline {
+ public:
+ MockScrollTimeline()
+ : ScrollTimeline(ElementId(), ScrollTimeline::Vertical, 0) {}
+ MOCK_CONST_METHOD1(CurrentTime, double(const ScrollTree&));
+};
+
TEST_F(WorkletAnimationPlayerTest, LocalTimeIsUsedWithAnimations) {
client_.RegisterElement(element_id_, ElementListType::ACTIVE);
client_impl_.RegisterElement(element_id_, ElementListType::PENDING);
@@ -37,7 +47,7 @@
start_opacity + (end_opacity - start_opacity) / 2;
scoped_refptr<WorkletAnimationPlayer> worklet_player_ =
- WorkletAnimationPlayer::Create(worklet_player_id_, "test_name");
+ WorkletAnimationPlayer::Create(worklet_player_id_, "test_name", nullptr);
worklet_player_->AttachElement(element_id_);
host_->AddAnimationTimeline(timeline_);
@@ -88,7 +98,7 @@
const double duration = 1.;
scoped_refptr<WorkletAnimationPlayer> worklet_player_ =
- WorkletAnimationPlayer::Create(worklet_player_id_, "test_name");
+ WorkletAnimationPlayer::Create(worklet_player_id_, "test_name", nullptr);
worklet_player_->AttachElement(element_id_);
host_->AddAnimationTimeline(timeline_);
@@ -109,6 +119,18 @@
Mock::VerifyAndClearExpectations(mock_mutator);
}
+TEST_F(WorkletAnimationPlayerTest, CurrentTimeCorrectlyUsesScrolltimeline) {
+ auto scroll_timeline = std::make_unique<MockScrollTimeline>();
+ EXPECT_CALL(*scroll_timeline, CurrentTime(_)).WillOnce(Return(1234));
+ scoped_refptr<WorkletAnimationPlayer> worklet_player =
+ WorkletAnimationPlayer::Create(worklet_player_id_, "test_name",
+ std::move(scroll_timeline));
+
+ ScrollTree scroll_tree;
+ EXPECT_EQ(1234,
+ worklet_player->CurrentTime(base::TimeTicks::Now(), scroll_tree));
+}
+
} // namespace
} // namespace cc
diff --git a/cc/test/animation_timelines_test_common.cc b/cc/test/animation_timelines_test_common.cc
index 26f203d..f0a483c9 100644
--- a/cc/test/animation_timelines_test_common.cc
+++ b/cc/test/animation_timelines_test_common.cc
@@ -13,6 +13,7 @@
#include "cc/animation/element_animations.h"
#include "cc/base/filter_operation.h"
#include "cc/base/filter_operations.h"
+#include "cc/trees/property_tree.h"
#include "ui/gfx/transform.h"
namespace cc {
@@ -430,13 +431,15 @@
unsigned expect_events) {
std::unique_ptr<MutatorEvents> events = host_->CreateEvents();
- host_impl_->TickAnimations(time);
+ // TODO(smcgruer): Construct a proper ScrollTree for the tests.
+ ScrollTree scroll_tree;
+ host_impl_->TickAnimations(time, scroll_tree);
host_impl_->UpdateAnimationState(true, events.get());
auto* animation_events = static_cast<const AnimationEvents*>(events.get());
EXPECT_EQ(expect_events, animation_events->events_.size());
- host_->TickAnimations(time);
+ host_->TickAnimations(time, scroll_tree);
host_->UpdateAnimationState(true, nullptr);
host_->SetAnimationEvents(std::move(events));
}
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index e22d27e..c6c8332 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -252,9 +252,11 @@
test_hooks_->DidSetVisibleOnImplTree(this, visible);
}
- bool AnimateLayers(base::TimeTicks monotonic_time) override {
+ bool AnimateLayers(base::TimeTicks monotonic_time,
+ bool is_active_tree) override {
test_hooks_->WillAnimateLayers(this, monotonic_time);
- bool result = LayerTreeHostImpl::AnimateLayers(monotonic_time);
+ bool result =
+ LayerTreeHostImpl::AnimateLayers(monotonic_time, is_active_tree);
test_hooks_->AnimateLayers(this, monotonic_time);
return result;
}
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index a93bc46..202d2b8 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -893,7 +893,8 @@
void LayerTreeHost::AnimateLayers(base::TimeTicks monotonic_time) {
std::unique_ptr<MutatorEvents> events = mutator_host_->CreateEvents();
- if (mutator_host_->TickAnimations(monotonic_time))
+ if (mutator_host_->TickAnimations(monotonic_time,
+ property_trees()->scroll_tree))
mutator_host_->UpdateAnimationState(true, events.get());
if (!events->IsEmpty())
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 560cab7..2535028 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -503,7 +503,7 @@
}
did_animate |= AnimatePageScale(monotonic_time);
- did_animate |= AnimateLayers(monotonic_time);
+ did_animate |= AnimateLayers(monotonic_time, active_tree);
did_animate |= AnimateScrollbars(monotonic_time);
did_animate |= AnimateBrowserControls(monotonic_time);
@@ -3692,7 +3692,9 @@
}
// Run animations which need to respond to updated scroll offset.
- mutator_host_->TickScrollAnimations(CurrentBeginFrameArgs().frame_time);
+ mutator_host_->TickScrollAnimations(
+ CurrentBeginFrameArgs().frame_time,
+ active_tree_->property_trees()->scroll_tree);
return scroll_result;
}
@@ -3974,8 +3976,13 @@
return animated;
}
-bool LayerTreeHostImpl::AnimateLayers(base::TimeTicks monotonic_time) {
- const bool animated = mutator_host_->TickAnimations(monotonic_time);
+bool LayerTreeHostImpl::AnimateLayers(base::TimeTicks monotonic_time,
+ bool is_active_tree) {
+ const ScrollTree& scroll_tree =
+ is_active_tree ? active_tree_->property_trees()->scroll_tree
+ : pending_tree_->property_trees()->scroll_tree;
+ const bool animated =
+ mutator_host_->TickAnimations(monotonic_time, scroll_tree);
// TODO(crbug.com/551134): Only do this if the animations are on the active
// tree, or if they are on the pending tree waiting for some future time to
@@ -4438,7 +4445,8 @@
property_trees->scroll_tree.OnScrollOffsetAnimated(
element_id, scroll_node_index, scroll_offset, tree);
// Run animations which need to respond to updated scroll offset.
- mutator_host_->TickScrollAnimations(CurrentBeginFrameArgs().frame_time);
+ mutator_host_->TickScrollAnimations(CurrentBeginFrameArgs().frame_time,
+ property_trees->scroll_tree);
}
void LayerTreeHostImpl::SetNeedUpdateGpuRasterizationStatus() {
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 2d4dd4e..42679680 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -654,7 +654,8 @@
scoped_refptr<base::SequencedTaskRunner> image_worker_task_runner);
// Virtual for testing.
- virtual bool AnimateLayers(base::TimeTicks monotonic_time);
+ virtual bool AnimateLayers(base::TimeTicks monotonic_time,
+ bool is_active_tree);
bool is_likely_to_require_a_draw() const {
return is_likely_to_require_a_draw_;
diff --git a/cc/trees/layer_tree_mutator.h b/cc/trees/layer_tree_mutator.h
index 607334e..053f353 100644
--- a/cc/trees/layer_tree_mutator.h
+++ b/cc/trees/layer_tree_mutator.h
@@ -25,8 +25,8 @@
int animation_player_id = 0;
// Name associated with worklet animation player.
std::string name;
- // Worklet animation player's current time.
- base::TimeTicks current_time;
+ // Worklet animation player's current time, from its associated timeline.
+ double current_time = 0;
};
MutatorInputState();
diff --git a/cc/trees/mutator_host.h b/cc/trees/mutator_host.h
index 19a6747e..580d095 100644
--- a/cc/trees/mutator_host.h
+++ b/cc/trees/mutator_host.h
@@ -25,6 +25,7 @@
class MutatorEvents;
class MutatorHostClient;
class LayerTreeMutator;
+class ScrollTree;
// A MutatorHost owns all the animation and mutation effects.
// There is just one MutatorHost for LayerTreeHost on main renderer thread
@@ -58,9 +59,13 @@
virtual bool NeedsTickAnimations() const = 0;
virtual bool ActivateAnimations() = 0;
- virtual bool TickAnimations(base::TimeTicks monotonic_time) = 0;
+ // TODO(smcgruer): Once we only tick scroll-based animations on scroll, we
+ // don't need to pass the scroll tree in here.
+ virtual bool TickAnimations(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) = 0;
// Tick animations that depends on scroll offset.
- virtual void TickScrollAnimations(base::TimeTicks monotonic_time) = 0;
+ virtual void TickScrollAnimations(base::TimeTicks monotonic_time,
+ const ScrollTree& scroll_tree) = 0;
virtual bool UpdateAnimationState(bool start_ready_animations,
MutatorEvents* events) = 0;
diff --git a/third_party/WebKit/LayoutTests/virtual/threaded/fast/animationworklet/animation-worklet-scroll-timeline-expected.html b/third_party/WebKit/LayoutTests/virtual/threaded/fast/animationworklet/animation-worklet-scroll-timeline-expected.html
new file mode 100644
index 0000000..44e38599
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/threaded/fast/animationworklet/animation-worklet-scroll-timeline-expected.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<style>
+#box {
+ width: 100px;
+ height: 100px;
+ background-color: #00ff00;
+ transform: translate(0, 100px);
+ opacity: 0.5;
+ will-change: transform; /* force compositing */
+}
+
+#covered {
+ width: 100px;
+ height: 100px;
+ background-color: #ff8080;
+}
+
+#scroller {
+ overflow: auto;
+ height: 100px;
+ width: 100px;
+ will-change: transform; /* force compositing */
+}
+
+#contents {
+ height: 1000px;
+ width: 100%;
+}
+</style>
+
+<div id="box"></div>
+<div id="covered"></div>
+<div id="scroller">
+ <div id="contents"></div>
+</div>
+
+<script>
+window.addEventListener('load', function() {
+ // Move the scroller to halfway.
+ const scroller = document.getElementById("scroller");
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ scroller.scrollTop = 0.5 * maxScroll;
+});
+</script>
diff --git a/third_party/WebKit/LayoutTests/virtual/threaded/fast/animationworklet/animation-worklet-scroll-timeline.html b/third_party/WebKit/LayoutTests/virtual/threaded/fast/animationworklet/animation-worklet-scroll-timeline.html
new file mode 100644
index 0000000..26e19f3f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/virtual/threaded/fast/animationworklet/animation-worklet-scroll-timeline.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<style>
+#box {
+ width: 100px;
+ height: 100px;
+ background-color: #00ff00;
+ /*
+ * Force compositing.
+ * TODO(majidvp): Should not be needed when http://crbug.com/776533 is fixed.
+ */
+ will-change: transform;
+}
+
+#covered {
+ width: 100px;
+ height: 100px;
+ background-color: #ff8080;
+}
+
+#scroller {
+ overflow: auto;
+ height: 100px;
+ width: 100px;
+ /*
+ * Force compositing.
+ * TODO(crbug.com/776533): Shouldn't be needed - we should fallback to main if scroller isn't composited.
+ */
+ will-change: transform;
+}
+
+#contents {
+ height: 1000px;
+ width: 100%;
+}
+</style>
+
+<div id="box"></div>
+<div id="covered"></div>
+<div id="scroller">
+ <div id="contents"></div>
+</div>
+
+<script id="visual_update" type="text/worklet">
+registerAnimator("test_animator", class {
+ animate(currentTime, effect) {
+ effect.localTime = currentTime;
+ }
+});
+</script>
+
+<script src="resources/animation-worklet-tests.js"></script>
+<script>
+if (window.testRunner) {
+ testRunner.waitUntilDone();
+}
+
+runInAnimationWorklet(
+ document.getElementById('visual_update').textContent
+).then(()=>{
+ const box = document.getElementById('box');
+ const effect = new KeyframeEffect(box,
+ [
+ { transform: 'translateY(0)', opacity: 1},
+ { transform: 'translateY(200px)', opacity: 0}
+ ], {
+ duration: 1000,
+ }
+ );
+
+ const scroller = document.getElementById('scroller');
+ const timeline = new ScrollTimeline({ scrollSource: scroller, timeRange: 1000, orientation: 'block' });
+ const animation = new WorkletAnimation('test_animator', [effect], timeline, {});
+ animation.play();
+
+ // Move the scroller to the halfway point.
+ const maxScroll = scroller.scrollHeight - scroller.clientHeight;
+ scroller.scrollTop = 0.5 * maxScroll;
+
+ if (window.testRunner) {
+ waitTwoAnimationFrames(_ => {
+ testRunner.notifyDone();
+ });
+ }
+});
+</script>
diff --git a/third_party/WebKit/Source/core/animation/ScrollTimeline.h b/third_party/WebKit/Source/core/animation/ScrollTimeline.h
index fb090e1..ca43808f 100644
--- a/third_party/WebKit/Source/core/animation/ScrollTimeline.h
+++ b/third_party/WebKit/Source/core/animation/ScrollTimeline.h
@@ -44,6 +44,8 @@
String orientation();
void timeRange(DoubleOrScrollTimelineAutoKeyword&);
+ ScrollDirection GetOrientation() const { return orientation_; }
+
void Trace(blink::Visitor*) override;
private:
diff --git a/third_party/WebKit/Source/core/animation/WorkletAnimationBase.h b/third_party/WebKit/Source/core/animation/WorkletAnimationBase.h
index 359da32..bd5ca03 100644
--- a/third_party/WebKit/Source/core/animation/WorkletAnimationBase.h
+++ b/third_party/WebKit/Source/core/animation/WorkletAnimationBase.h
@@ -7,6 +7,7 @@
#include "core/CoreExport.h"
#include "platform/bindings/ScriptWrappable.h"
+#include "platform/wtf/Forward.h"
namespace blink {
@@ -17,12 +18,13 @@
virtual ~WorkletAnimationBase() {}
// Attempts to start the animation on the compositor side, returning true if
- // it succeeds or false otherwise.
+ // it succeeds or false otherwise. If false is returned and failure_message
+ // was non-null, failure_message may be filled with an error description.
//
// On a false return it may still be possible to start the animation on the
// compositor later (e.g. if an incompatible property is removed from the
// element), so the caller should try again next main frame.
- virtual bool StartOnCompositor() = 0;
+ virtual bool StartOnCompositor(String* failure_message) = 0;
virtual Document* GetDocument() const = 0;
};
diff --git a/third_party/WebKit/Source/core/animation/WorkletAnimationController.cpp b/third_party/WebKit/Source/core/animation/WorkletAnimationController.cpp
index f913924..065cda1 100644
--- a/third_party/WebKit/Source/core/animation/WorkletAnimationController.cpp
+++ b/third_party/WebKit/Source/core/animation/WorkletAnimationController.cpp
@@ -7,10 +7,13 @@
#include "core/animation/WorkletAnimationBase.h"
#include "core/dom/Document.h"
#include "core/frame/LocalFrameView.h"
+#include "core/inspector/ConsoleMessage.h"
+#include "platform/wtf/text/WTFString.h"
namespace blink {
-WorkletAnimationController::WorkletAnimationController() = default;
+WorkletAnimationController::WorkletAnimationController(Document* document)
+ : document_(document) {}
WorkletAnimationController::~WorkletAnimationController() = default;
@@ -43,16 +46,20 @@
HeapHashSet<Member<WorkletAnimationBase>> animations;
animations.swap(pending_animations_);
for (const auto& animation : animations) {
- if (animation->StartOnCompositor()) {
+ String failure_message;
+ if (animation->StartOnCompositor(&failure_message)) {
compositor_animations_.insert(animation);
+ } else {
+ document_->AddConsoleMessage(ConsoleMessage::Create(
+ kOtherMessageSource, kWarningMessageLevel, failure_message));
}
- // TODO(smcgruer): On failure, warn user. Perhaps fire cancel event?
}
}
void WorkletAnimationController::Trace(blink::Visitor* visitor) {
visitor->Trace(pending_animations_);
visitor->Trace(compositor_animations_);
+ visitor->Trace(document_);
}
} // namespace blink
diff --git a/third_party/WebKit/Source/core/animation/WorkletAnimationController.h b/third_party/WebKit/Source/core/animation/WorkletAnimationController.h
index ff6b3971..d0e6d92 100644
--- a/third_party/WebKit/Source/core/animation/WorkletAnimationController.h
+++ b/third_party/WebKit/Source/core/animation/WorkletAnimationController.h
@@ -12,6 +12,7 @@
namespace blink {
+class Document;
class WorkletAnimationBase;
// Handles AnimationWorklet animations on the main-thread.
@@ -27,7 +28,7 @@
class CORE_EXPORT WorkletAnimationController
: public GarbageCollectedFinalized<WorkletAnimationController> {
public:
- WorkletAnimationController();
+ WorkletAnimationController(Document*);
virtual ~WorkletAnimationController();
void AttachAnimation(WorkletAnimationBase&);
@@ -40,6 +41,8 @@
private:
HeapHashSet<Member<WorkletAnimationBase>> pending_animations_;
HeapHashSet<Member<WorkletAnimationBase>> compositor_animations_;
+
+ Member<Document> document_;
};
} // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/Document.cpp b/third_party/WebKit/Source/core/dom/Document.cpp
index c610404..6a13dda 100644
--- a/third_party/WebKit/Source/core/dom/Document.cpp
+++ b/third_party/WebKit/Source/core/dom/Document.cpp
@@ -644,7 +644,7 @@
&Document::ElementDataCacheClearTimerFired),
timeline_(DocumentTimeline::Create(this)),
pending_animations_(new PendingAnimations(*this)),
- worklet_animation_controller_(new WorkletAnimationController),
+ worklet_animation_controller_(new WorkletAnimationController(this)),
template_document_host_(nullptr),
did_associate_form_controls_timer_(
GetTaskRunner(TaskType::kUnspecedLoading),
diff --git a/third_party/WebKit/Source/modules/animationworklet/AnimationWorkletGlobalScopeTest.cpp b/third_party/WebKit/Source/modules/animationworklet/AnimationWorkletGlobalScopeTest.cpp
index 3d53b44..7579f45 100644
--- a/third_party/WebKit/Source/modules/animationworklet/AnimationWorkletGlobalScopeTest.cpp
+++ b/third_party/WebKit/Source/modules/animationworklet/AnimationWorkletGlobalScopeTest.cpp
@@ -184,6 +184,7 @@
CompositorMutatorInputState::AnimationState test_animation_state;
test_animation_state.animation_player_id = 1;
test_animation_state.name = "test";
+ test_animation_state.current_time = 5000;
state.animations = {test_animation_state};
std::unique_ptr<CompositorMutatorOutputState> output =
@@ -235,6 +236,7 @@
CompositorMutatorInputState::AnimationState test_animation_state;
test_animation_state.animation_player_id = 1;
test_animation_state.name = "test";
+ test_animation_state.current_time = 5000;
state.animations = {test_animation_state};
std::unique_ptr<CompositorMutatorOutputState> output =
@@ -290,7 +292,7 @@
&AnimationWorkletGlobalScopeTest::RunConstructAndAnimateTestOnWorklet);
}
-TEST_F(AnimationWorkletGlobalScopeTest, AnimtionOutput) {
+TEST_F(AnimationWorkletGlobalScopeTest, AnimationOutput) {
RunTestOnWorkletThread(
&AnimationWorkletGlobalScopeTest::RunAnimateOutputTestOnWorklet);
}
diff --git a/third_party/WebKit/Source/modules/animationworklet/Animator.cpp b/third_party/WebKit/Source/modules/animationworklet/Animator.cpp
index 94a6fea..ff060ae 100644
--- a/third_party/WebKit/Source/modules/animationworklet/Animator.cpp
+++ b/third_party/WebKit/Source/modules/animationworklet/Animator.cpp
@@ -45,9 +45,6 @@
if (IsUndefinedOrNull(instance) || IsUndefinedOrNull(animate))
return false;
- // Update local time
- current_time_ = WTF::TimeTicks(input.current_time);
-
ScriptState::Scope scope(script_state);
v8::TryCatch block(isolate);
block.SetVerbose(true);
@@ -58,8 +55,7 @@
ToV8(effect_, script_state->GetContext()->Global(), isolate);
v8::Local<v8::Value> v8_current_time =
- ToV8((current_time_ - WTF::TimeTicks()).InMillisecondsF(),
- script_state->GetContext()->Global(), isolate);
+ ToV8(input.current_time, script_state->GetContext()->Global(), isolate);
v8::Local<v8::Value> argv[] = {v8_current_time, v8_effect};
diff --git a/third_party/WebKit/Source/modules/animationworklet/Animator.h b/third_party/WebKit/Source/modules/animationworklet/Animator.h
index ff553c1..7fd1c62 100644
--- a/third_party/WebKit/Source/modules/animationworklet/Animator.h
+++ b/third_party/WebKit/Source/modules/animationworklet/Animator.h
@@ -47,7 +47,6 @@
TraceWrapperV8Reference<v8::Object> instance_;
bool did_animate_ = false;
- WTF::TimeTicks current_time_;
Member<EffectProxy> effect_;
};
diff --git a/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.cpp b/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.cpp
index 3429604..11749126 100644
--- a/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.cpp
+++ b/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.cpp
@@ -10,6 +10,7 @@
#include "core/animation/Timing.h"
#include "core/dom/Node.h"
#include "core/dom/NodeComputedStyle.h"
+#include "core/layout/LayoutBox.h"
#include "platform/wtf/text/WTFString.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositorSupport.h"
@@ -54,6 +55,70 @@
}
return true;
}
+
+bool CheckElementComposited(const Element& target) {
+ return target.GetLayoutObject() &&
+ target.GetLayoutObject()->GetCompositingState() ==
+ kPaintsIntoOwnBacking;
+}
+
+CompositorElementId GetCompositorScrollElementId(const Element& element) {
+ DCHECK(element.GetLayoutObject());
+ DCHECK(element.GetLayoutObject()->HasLayer());
+ return CompositorElementIdFromUniqueObjectId(
+ element.GetLayoutObject()->UniqueId(),
+ CompositorElementIdNamespace::kScroll);
+}
+
+// Convert the blink concept of a ScrollTimeline orientation into the cc one.
+//
+// The compositor does not know about writing modes, so we have to convert the
+// web concepts of 'block' and 'inline' direction into absolute vertical or
+// horizontal directions.
+//
+// TODO(smcgruer): If the writing mode of a scroller changes, we have to update
+// any related cc::ScrollTimeline somehow.
+CompositorScrollTimeline::ScrollDirection ConvertOrientation(
+ ScrollTimeline::ScrollDirection orientation,
+ bool is_horizontal_writing_mode) {
+ switch (orientation) {
+ case ScrollTimeline::Block:
+ return is_horizontal_writing_mode ? CompositorScrollTimeline::Vertical
+ : CompositorScrollTimeline::Horizontal;
+ case ScrollTimeline::Inline:
+ return is_horizontal_writing_mode ? CompositorScrollTimeline::Horizontal
+ : CompositorScrollTimeline::Vertical;
+ default:
+ NOTREACHED();
+ return CompositorScrollTimeline::Vertical;
+ }
+}
+
+// Converts a blink::ScrollTimeline into a cc::ScrollTimeline.
+//
+// If the timeline cannot be converted, returns nullptr.
+std::unique_ptr<CompositorScrollTimeline> ToCompositorScrollTimeline(
+ const DocumentTimelineOrScrollTimeline& timeline) {
+ if (!timeline.IsScrollTimeline())
+ return nullptr;
+
+ ScrollTimeline* scroll_timeline = timeline.GetAsScrollTimeline();
+ Element* scroll_source = scroll_timeline->scrollSource();
+ CompositorElementId element_id = GetCompositorScrollElementId(*scroll_source);
+
+ DoubleOrScrollTimelineAutoKeyword time_range;
+ scroll_timeline->timeRange(time_range);
+ // TODO(smcgruer): Handle 'auto' time range value.
+ DCHECK(time_range.IsDouble());
+
+ LayoutBox* box = scroll_source->GetLayoutBox();
+ DCHECK(box);
+ CompositorScrollTimeline::ScrollDirection orientation = ConvertOrientation(
+ scroll_timeline->GetOrientation(), box->IsHorizontalWritingMode());
+
+ return std::make_unique<CompositorScrollTimeline>(element_id, orientation,
+ time_range.GetAsDouble());
+}
} // namespace
WorkletAnimation* WorkletAnimation::Create(
@@ -78,9 +143,6 @@
Document& document = effects.at(0)->Target()->GetDocument();
WorkletAnimation* animation = new WorkletAnimation(
animator_name, document, effects, timeline, std::move(options));
- if (CompositorAnimationTimeline* compositor_timeline =
- document.Timeline().CompositorTimeline())
- compositor_timeline->PlayerAttached(*animation);
return animation;
}
@@ -100,10 +162,6 @@
DCHECK(IsMainThread());
DCHECK(Platform::Current()->IsThreadedAnimationEnabled());
DCHECK(Platform::Current()->CompositorSupport());
-
- compositor_player_ =
- CompositorAnimationPlayer::CreateWorkletPlayer(animator_name_);
- compositor_player_->SetAnimationDelegate(this);
}
String WorkletAnimation::playState() {
@@ -127,39 +185,71 @@
}
}
-bool WorkletAnimation::StartOnCompositor() {
+bool WorkletAnimation::StartOnCompositor(String* failure_message) {
DCHECK(IsMainThread());
- Element& target = *effects_.at(0)->Target();
+ KeyframeEffectReadOnly* target_effect = effects_.at(0);
+ Element& target = *target_effect->Target();
- // TODO(smcgruer): Creating a WorkletAnimaiton should be a hint to blink
+ // CheckCanStartAnimationOnCompositor requires that the property-specific
+ // keyframe groups have been created. To ensure this we manually snapshot the
+ // frames in the target effect.
+ // TODO(smcgruer): This shouldn't be necessary - Animation doesn't do this.
+ ToKeyframeEffectModelBase(target_effect->Model())
+ ->SnapshotAllCompositorKeyframes(target, target.ComputedStyleRef(),
+ target.ParentComputedStyle());
+
+ if (!CheckElementComposited(target)) {
+ if (failure_message)
+ *failure_message = "The target element is not composited.";
+ return false;
+ }
+
+ if (timeline_.IsScrollTimeline() &&
+ !CheckElementComposited(
+ *timeline_.GetAsScrollTimeline()->scrollSource())) {
+ if (failure_message)
+ *failure_message = "The ScrollTimeline scrollSource is not composited.";
+ return false;
+ }
+
+ double playback_rate = 1;
+ CompositorAnimations::FailureCode failure_code =
+ target_effect->CheckCanStartAnimationOnCompositor(playback_rate);
+
+ if (!failure_code.Ok()) {
+ play_state_ = Animation::kIdle;
+ if (failure_message)
+ *failure_message = failure_code.reason;
+ return false;
+ }
+
+ if (!compositor_player_) {
+ compositor_player_ = CompositorAnimationPlayer::CreateWorkletPlayer(
+ animator_name_, ToCompositorScrollTimeline(timeline_));
+ compositor_player_->SetAnimationDelegate(this);
+ }
+
+ // Register ourselves on the compositor timeline. This will cause our cc-side
+ // animation player to be registered.
+ if (CompositorAnimationTimeline* compositor_timeline =
+ document_->Timeline().CompositorTimeline())
+ compositor_timeline->PlayerAttached(*this);
+
+ // TODO(smcgruer): Creating a WorkletAnimation should be a hint to blink
// compositing that the animated element should be promoted. Otherwise this
// fails. http://crbug.com/776533
CompositorAnimations::AttachCompositedLayers(target,
compositor_player_.get());
- double animation_playback_rate = 1;
- ToKeyframeEffectModelBase(effects_.at(0)->Model())
- ->SnapshotAllCompositorKeyframes(target, target.ComputedStyleRef(),
- target.ParentComputedStyle());
+ double start_time = std::numeric_limits<double>::quiet_NaN();
+ double time_offset = 0;
+ int group = 0;
- bool success =
- effects_.at(0)
- ->CheckCanStartAnimationOnCompositor(animation_playback_rate)
- .Ok();
-
- if (success) {
- double start_time = std::numeric_limits<double>::quiet_NaN();
- double time_offset = 0;
- int group = 0;
-
- // TODO(smcgruer): We need to start all of the effects, not just the first.
- effects_.at(0)->StartAnimationOnCompositor(group, start_time, time_offset,
- animation_playback_rate,
- compositor_player_.get());
- }
-
- play_state_ = success ? Animation::kRunning : Animation::kIdle;
- return success;
+ // TODO(smcgruer): We need to start all of the effects, not just the first.
+ effects_.at(0)->StartAnimationOnCompositor(
+ group, start_time, time_offset, playback_rate, compositor_player_.get());
+ play_state_ = Animation::kRunning;
+ return true;
}
void WorkletAnimation::Dispose() {
@@ -168,8 +258,10 @@
if (CompositorAnimationTimeline* compositor_timeline =
document_->Timeline().CompositorTimeline())
compositor_timeline->PlayerDestroyed(*this);
- compositor_player_->SetAnimationDelegate(nullptr);
- compositor_player_ = nullptr;
+ if (compositor_player_) {
+ compositor_player_->SetAnimationDelegate(nullptr);
+ compositor_player_ = nullptr;
+ }
}
void WorkletAnimation::Trace(blink::Visitor* visitor) {
diff --git a/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.h b/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.h
index f554ec9..cb986b9 100644
--- a/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.h
+++ b/third_party/WebKit/Source/modules/animationworklet/WorkletAnimation.h
@@ -49,7 +49,7 @@
void cancel();
// WorkletAnimationBase implementation.
- bool StartOnCompositor() override;
+ bool StartOnCompositor(String* failure_message) override;
// CompositorAnimationPlayerClient implementation.
CompositorAnimationPlayer* CompositorPlayer() const override {
diff --git a/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.cpp b/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.cpp
index 47c621d8..280cdb54 100644
--- a/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.cpp
+++ b/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.cpp
@@ -18,11 +18,14 @@
}
std::unique_ptr<CompositorAnimationPlayer>
-CompositorAnimationPlayer::CreateWorkletPlayer(const String& name) {
+CompositorAnimationPlayer::CreateWorkletPlayer(
+ const String& name,
+ std::unique_ptr<CompositorScrollTimeline> scroll_timeline) {
return std::make_unique<CompositorAnimationPlayer>(
cc::WorkletAnimationPlayer::Create(
cc::AnimationIdProvider::NextPlayerId(),
- std::string(name.Ascii().data(), name.length())));
+ std::string(name.Ascii().data(), name.length()),
+ std::move(scroll_timeline)));
}
CompositorAnimationPlayer::CompositorAnimationPlayer(
diff --git a/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.h b/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.h
index f427286..e18ae1a2 100644
--- a/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.h
+++ b/third_party/WebKit/Source/platform/animation/CompositorAnimationPlayer.h
@@ -9,6 +9,7 @@
#include "base/memory/scoped_refptr.h"
#include "cc/animation/animation_delegate.h"
#include "cc/animation/animation_player.h"
+#include "cc/animation/scroll_timeline.h"
#include "cc/animation/worklet_animation_player.h"
#include "platform/PlatformExport.h"
#include "platform/graphics/CompositorElementId.h"
@@ -21,6 +22,8 @@
namespace blink {
+using CompositorScrollTimeline = cc::ScrollTimeline;
+
class CompositorAnimation;
class CompositorAnimationDelegate;
@@ -31,7 +34,8 @@
public:
static std::unique_ptr<CompositorAnimationPlayer> Create();
static std::unique_ptr<CompositorAnimationPlayer> CreateWorkletPlayer(
- const String& name);
+ const String& name,
+ std::unique_ptr<CompositorScrollTimeline>);
explicit CompositorAnimationPlayer(scoped_refptr<cc::AnimationPlayer>);
~CompositorAnimationPlayer();