Commit 0aab4e59 authored by Xiyuan Xia's avatar Xiyuan Xia Committed by Commit Bot

Add AnimationThroughputReporter

Add AnimationThroughputReporter to report throughput for layer
animations. To use it, create an instance with relevant LayerAniamtor
and a report callback when setting up animations. The callback will
be invoked after animation finishes if there is enough data and no
animation is aborted.

Bug: 1021774
Change-Id: Ib3029671660c58c29308f7ab2f02052f6ae94b29
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2055691Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Reviewed-by: default avatarMitsuru Oshima <oshima@chromium.org>
Reviewed-by: default avatarJun Mukai <mukai@chromium.org>
Commit-Queue: Xiyuan Xia <xiyuan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#770649}
parent 3affc108
# Graphics metrics: Definitions
We need to have a metric to understand the smoothness of a particular
interaction (e.g. scroll, animation, etc.). We also need to understand the
latency of such interactions (e.g. touch-on-screen to
scroll-displayed-on-screen), and the throughput during the interaction.
[TOC]
We define these metrics as follows:
## Responsiveness / Latency
Responsiveness is a measure of how quickly the web-page responds to an event.
Latency is defined as the time between when an event happens, (e.g. moving a
touch-point on screen) and when the screen is updated directly in response to
that event [1]. For example, the event could be a moving touch-point on the
touchscreen, and the update would be scrolled content in response to that
(may only require the compositor frame update). If a rAF callback was
registered, the event would be the one that caused the current script execution
(e.g. a click event which triggered rAF), and the update would be the displayed
frame after the rAF callback is run and the content from the main-thread has
been presented on screen.
## Throughput
The ratio between the number of times the screen is updated for a particular
interaction (e.g. scroll, animation, etc.), and the number of times the screen
was expected to be updated (see examples below). On a 60Hz display, there would
ideally be 60 frames produced during a scroll or an animation.
## DroppedFrames / SkippedFrames
The ratio between the number of dropped/skipped frames for a particular
interaction, and the number of times the screen was expected to be updated. This
is the other part data of Throughput so it is a "lower-is-better" metric and
works better with current out perf tools.
## Smoothness / Jank
Smoothness is a measure of how consistent the throughput is. Jank during an
interaction is defined as a change in the throughput for consecutive frames.
To explain this further:
Consider the following presented frames:
**f1**&nbsp;&nbsp;
**f2**&nbsp;&nbsp;
**f3**&nbsp;&nbsp;
**f4**&nbsp;&nbsp;
**f5**&nbsp;&nbsp;
**f6**&nbsp;&nbsp;
**f7**&nbsp;&nbsp;
**f8**&nbsp;&nbsp;
**f9**
Each highlighted **fn** indicates a frame that contained response from the
renderer[2]. So in the above example, there were no janks, and throughput was
100%: i.e. all the presented frames included updated content.
Considering the following frames:
**f1**&nbsp;&nbsp;
**f2**&nbsp;&nbsp;
f3&nbsp;&nbsp;
**f4**&nbsp;&nbsp;
f5&nbsp;&nbsp;
**f6**&nbsp;&nbsp;
**f7**&nbsp;&nbsp;
**f8**&nbsp;&nbsp;
**f9**
In this case, frames `f3` and `f5` did not include any updates (either
display-compositor was unable to submit a new frame, or the frame submitted by
the display compositor did not include any updates from the renderer).
Therefore, the throughput during this interaction is 78%.
To explain the jank, during the first two frames `[f1, f2]`, the throughput is
100%. Because of the subsequently missed frame `f3`, the throughput changes for
`[f2, f4]` drops to 67%. The throughput for `[f4, f6]` is also 67%. For
subsequent frames, the throughput goes back up to 100%. Therefore, there was a
single jank.
Consider the following two sequences:
**f1**&nbsp;&nbsp;
**f2**&nbsp;&nbsp;
**f3**&nbsp;&nbsp;
**f4**&nbsp;&nbsp;
f5&nbsp;&nbsp;
f6&nbsp;&nbsp;
f7&nbsp;&nbsp;
f8&nbsp;&nbsp;
**f9**
**f1**&nbsp;&nbsp;
f2&nbsp;&nbsp;
**f3**&nbsp;&nbsp;
f4&nbsp;&nbsp;
**f5**&nbsp;&nbsp;
f6&nbsp;&nbsp;
**f7**&nbsp;&nbsp;
f8&nbsp;&nbsp;
**f9**
In both cases, throughput is 55%, since only 5 out of 9 frames are displayed.
In the first sequence, there is a jank (`[f1, f2][f2, f3][f3, f4]` has 100%
throughput, but `[f4, f9]` has a throughput of 33%). However, in the second
sequence, there are no janks, since `[f1, f3]` `[f3, f5]` `[f5, f7]` `[f7, f9]`
all have 67% throughput.
[1]: Indirect updates in response to an event, e.g. an update from a
setTimeout() callback from an event-handler would not be associated with that
event.
[2]: Note that the response could be either an update to the content, or a
notification that no update is expected for that frame. For example, for a 30fps
animation in this frame-sequence, only frames `f1` `f3` `f5` `f7` `f9` will have
actual updates from the animation, and frames `f2` `f4` `f6` `f8` should still
have notification from the client that no update is expected.
...@@ -17,6 +17,8 @@ jumbo_component("compositor") { ...@@ -17,6 +17,8 @@ jumbo_component("compositor") {
"animation_metrics_recorder.cc", "animation_metrics_recorder.cc",
"animation_metrics_recorder.h", "animation_metrics_recorder.h",
"animation_metrics_reporter.h", "animation_metrics_reporter.h",
"animation_throughput_reporter.cc",
"animation_throughput_reporter.h",
"callback_layer_animation_observer.cc", "callback_layer_animation_observer.cc",
"callback_layer_animation_observer.h", "callback_layer_animation_observer.h",
"canvas_painter.cc", "canvas_painter.cc",
...@@ -220,6 +222,7 @@ jumbo_static_library("test_support") { ...@@ -220,6 +222,7 @@ jumbo_static_library("test_support") {
test("compositor_unittests") { test("compositor_unittests") {
sources = [ sources = [
"animation_throughput_reporter_unittest.cc",
"callback_layer_animation_observer_unittest.cc", "callback_layer_animation_observer_unittest.cc",
"canvas_painter_unittest.cc", "canvas_painter_unittest.cc",
"compositor_lock_unittest.cc", "compositor_lock_unittest.cc",
......
// Copyright 2020 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 "ui/compositor/animation_throughput_reporter.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "cc/animation/animation.h"
#include "ui/compositor/callback_layer_animation_observer.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_delegate.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/throughput_tracker.h"
namespace ui {
class AnimationThroughputReporter::AnimationTracker
: public CallbackLayerAnimationObserver {
public:
AnimationTracker(LayerAnimator* animator, ReportCallback report_callback)
: CallbackLayerAnimationObserver(
base::BindRepeating(&AnimationTracker::OnAnimationEnded,
base::Unretained(this))),
animator_(animator),
report_callback_(std::move(report_callback)) {
DCHECK(report_callback_);
}
AnimationTracker(const AnimationTracker& other) = delete;
AnimationTracker& operator=(const AnimationTracker& other) = delete;
~AnimationTracker() override = default;
// Whether there are attached animation sequences to track.
bool IsTrackingAnimation() const { return !attached_sequences().empty(); }
void set_should_delete(bool should_delete) { should_delete_ = should_delete; }
private:
// CallbackLayerAnimationObserver:
void OnAnimatorAttachedToTimeline() override { MaybeStartTracking(); }
void OnAnimatorDetachedFromTimeline() override {
// Gives up tracking when detached from the timeline.
should_start_tracking_ = false;
if (throughput_tracker_)
throughput_tracker_.reset();
// OnAnimationEnded would not happen after detached from the timeline.
// So do the clean up here.
if (should_delete_)
delete this;
}
void OnLayerAnimationStarted(LayerAnimationSequence* sequence) override {
CallbackLayerAnimationObserver::OnLayerAnimationStarted(sequence);
should_start_tracking_ = true;
MaybeStartTracking();
// Make sure SetActive() is called so that OnAnimationEnded callback will be
// invoked when all attached layer animation sequences finish.
if (!active())
SetActive();
}
void MaybeStartTracking() {
// No tracking if no layer animation sequence is started.
if (!should_start_tracking_)
return;
// No tracking if |animator_| is not attached to a timeline. Layer animation
// sequence would not tick without a timeline.
if (!AnimationThroughputReporter::IsAnimatorAttachedToTimeline(
animator_.get())) {
return;
}
ui::Compositor* compositor =
AnimationThroughputReporter::GetCompositor(animator_.get());
throughput_tracker_ = compositor->RequestNewThroughputTracker();
throughput_tracker_->Start(report_callback_);
}
// Invoked when all animation sequences finish.
bool OnAnimationEnded(const CallbackLayerAnimationObserver& self) {
// |tracking_started_| could reset when detached from animation timeline.
// E.g. underlying Layer is moved from one Compositor to another. No report
// for such case.
if (throughput_tracker_) {
if (self.aborted_count())
throughput_tracker_->Cancel();
else
throughput_tracker_->Stop();
}
should_start_tracking_ = false;
return should_delete_;
}
// Whether this class should delete itself on animation ended.
bool should_delete_ = false;
scoped_refptr<LayerAnimator> animator_;
base::Optional<ThroughputTracker> throughput_tracker_;
// Whether |throughput_tracker_| should be started.
bool should_start_tracking_ = false;
AnimationThroughputReporter::ReportCallback report_callback_;
};
AnimationThroughputReporter::AnimationThroughputReporter(
LayerAnimator* animator,
ReportCallback report_callback)
: animator_(animator),
animation_tracker_(
std::make_unique<AnimationTracker>(animator_,
std::move(report_callback))) {
animator_->AddObserver(animation_tracker_.get());
}
AnimationThroughputReporter::~AnimationThroughputReporter() {
// Directly remove |animation_tracker_| from |LayerAnimator::observers_|
// rather than calling LayerAnimator::RemoveObserver(), to avoid removing it
// from the scheduled animation sequences.
animator_->observers_.RemoveObserver(animation_tracker_.get());
// |animation_tracker_| deletes itself when its tracked animations finish.
if (animation_tracker_->IsTrackingAnimation())
animation_tracker_.release()->set_should_delete(true);
}
// static
Compositor* AnimationThroughputReporter::GetCompositor(
LayerAnimator* animator) {
return animator->delegate()->GetLayer()->GetCompositor();
}
// static
bool AnimationThroughputReporter::IsAnimatorAttachedToTimeline(
LayerAnimator* animator) {
return animator->animation_->animation_timeline();
}
} // namespace ui
// Copyright 2020 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 UI_COMPOSITOR_ANIMATION_THROUGHPUT_REPORTER_H_
#define UI_COMPOSITOR_ANIMATION_THROUGHPUT_REPORTER_H_
#include <memory>
#include "base/callback_forward.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "ui/compositor/compositor_export.h"
namespace ui {
class Compositor;
class LayerAnimator;
// Reports cc::FrameSequenceMetrics::ThroughputData of layer animations.
//
// Throughput is defined as the ratio between number frames presented (actual
// screen updates) for the animations and the number frames expected.
// DroppedFrames/SkippedFrames is the other part of the story. It is a ratio
// between dropped/skipped frames over the expected frames.
//
// See also docs/speed/graphics_metrics_definitions.md.
//
// cc::FrameSequenceMetrics::ThroughputData contains the numbers of produced
// frames and expected frames and could be used to calculate the two metrics.
//
// All layer animation sequences created after its construction are consider
// as part of the animation being tracked. Graphics throughput tracking is
// stopped when all relevant layer animation sequences finish. The report
// callback is invoked on the next frame presentation if there is enough data
// and none of the layer animation sequences is aborted.
class COMPOSITOR_EXPORT AnimationThroughputReporter {
public:
using ReportCallback =
base::RepeatingCallback<void(cc::FrameSequenceMetrics::ThroughputData)>;
AnimationThroughputReporter(LayerAnimator* animator,
ReportCallback report_callback);
AnimationThroughputReporter(const AnimationThroughputReporter&) = delete;
AnimationThroughputReporter& operator=(const AnimationThroughputReporter&) =
delete;
~AnimationThroughputReporter();
private:
// Tracks when layer animation sequences are scheduled and finished.
class AnimationTracker;
// Returns the relevant compositor of |animator|. Note it could return
// nullptr if |animator| has not attached to an animation timeline.
// Listed here to access LayerAnimator's protected delegate().
static Compositor* GetCompositor(LayerAnimator* animator);
// Whether |animator_| is attached to a timeline.
// List here to access LayerAnimation's private |anmation_| member.
static bool IsAnimatorAttachedToTimeline(LayerAnimator* animator);
LayerAnimator* const animator_;
std::unique_ptr<AnimationTracker> animation_tracker_;
};
} // namespace ui
#endif // UI_COMPOSITOR_ANIMATION_THROUGHPUT_REPORTER_H_
// Copyright 2020 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 "ui/compositor/animation_throughput_reporter.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "cc/metrics/frame_sequence_metrics.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor/test/test_compositor_host.h"
#include "ui/compositor/test/test_context_factories.h"
#include "ui/gfx/geometry/rect.h"
namespace ui {
class AnimationThroughputReporterTest : public testing::Test {
public:
AnimationThroughputReporterTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
AnimationThroughputReporterTest(const AnimationThroughputReporterTest&) =
delete;
AnimationThroughputReporterTest& operator=(
const AnimationThroughputReporterTest&) = delete;
~AnimationThroughputReporterTest() override = default;
// testing::Test:
void SetUp() override {
context_factories_ = std::make_unique<TestContextFactories>(false);
const gfx::Rect bounds(100, 100);
host_.reset(TestCompositorHost::Create(
bounds, context_factories_->GetContextFactory()));
host_->Show();
compositor()->SetRootLayer(&root_);
frame_generation_timer_.Start(
FROM_HERE, base::TimeDelta::FromMilliseconds(50), this,
&AnimationThroughputReporterTest::GenerateOneFrame);
}
void TearDown() override {
frame_generation_timer_.Stop();
host_.reset();
context_factories_.reset();
}
void GenerateOneFrame() { compositor()->ScheduleFullRedraw(); }
Compositor* compositor() { return host_->GetCompositor(); }
Layer* root_layer() { return &root_; }
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestContextFactories> context_factories_;
std::unique_ptr<TestCompositorHost> host_;
Layer root_;
// A timer to generate continuous compositor frames to trigger throughput
// data being transferred back.
base::RepeatingTimer frame_generation_timer_;
};
// Tests animation throughput collection with implicit animation scenario.
TEST_F(AnimationThroughputReporterTest, ImplicitAnimation) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
base::RunLoop run_loop;
{
LayerAnimator* animator = layer.GetAnimator();
AnimationThroughputReporter reporter(
animator, base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) {
run_loop.Quit();
}));
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
layer.SetOpacity(1.0f);
}
run_loop.Run();
}
// Tests animation throughput collection with implicit animation setup before
// Layer is attached to a compositor.
TEST_F(AnimationThroughputReporterTest, ImplicitAnimationLateAttach) {
Layer layer;
layer.SetOpacity(0.5f);
base::RunLoop run_loop;
{
LayerAnimator* animator = layer.GetAnimator();
AnimationThroughputReporter reporter(
animator, base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) {
run_loop.Quit();
}));
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
layer.SetOpacity(1.0f);
}
// Attach to root after animation setup.
root_layer()->Add(&layer);
run_loop.Run();
}
// Tests animation throughput collection with explicitly created animation
// sequence scenario.
TEST_F(AnimationThroughputReporterTest, ExplicitAnimation) {
Layer layer;
layer.SetOpacity(0.5f);
root_layer()->Add(&layer);
base::RunLoop run_loop;
{
LayerAnimator* animator = layer.GetAnimator();
AnimationThroughputReporter reporter(
animator, base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) {
run_loop.Quit();
}));
animator->ScheduleAnimation(
new LayerAnimationSequence(LayerAnimationElement::CreateOpacityElement(
1.0f, base::TimeDelta::FromMilliseconds(50))));
}
run_loop.Run();
}
// Tests animation throughput collection for a persisted animator of a Layer.
TEST_F(AnimationThroughputReporterTest, PersistedAnimation) {
auto layer = std::make_unique<Layer>();
layer->SetOpacity(0.5f);
root_layer()->Add(layer.get());
// Set a persisted animator to |layer|.
LayerAnimator* animator =
new LayerAnimator(base::TimeDelta::FromMilliseconds(50));
layer->SetAnimator(animator);
std::unique_ptr<base::RunLoop> run_loop = std::make_unique<base::RunLoop>();
// |reporter| keeps reporting as long as it is alive.
AnimationThroughputReporter reporter(
animator,
base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) { run_loop->Quit(); }));
// Report data for animation of opacity goes to 1.
layer->SetOpacity(1.0f);
run_loop->Run();
// Report data for animation of opacity goes to 0.5.
run_loop = std::make_unique<base::RunLoop>();
layer->SetOpacity(0.5f);
run_loop->Run();
}
// Tests animation throughput not reported when animation is aborted.
TEST_F(AnimationThroughputReporterTest, AbortedAnimation) {
auto layer = std::make_unique<Layer>();
layer->SetOpacity(0.5f);
root_layer()->Add(layer.get());
{
LayerAnimator* animator = layer->GetAnimator();
AnimationThroughputReporter reporter(
animator, base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) {
ADD_FAILURE() << "No report for aborted animations.";
}));
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
layer->SetOpacity(1.0f);
}
// Delete |layer| to abort on-going animations.
layer.reset();
// Wait a bit to ensure that report does not happen.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(100));
run_loop.Run();
}
// Tests animation throughput not reported when detached from timeline.
TEST_F(AnimationThroughputReporterTest, NoReportOnDetach) {
auto layer = std::make_unique<Layer>();
layer->SetOpacity(0.5f);
root_layer()->Add(layer.get());
{
LayerAnimator* animator = layer->GetAnimator();
AnimationThroughputReporter reporter(
animator, base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) {
ADD_FAILURE() << "No report for aborted animations.";
}));
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
layer->SetOpacity(1.0f);
}
// Detach from the root and attach to a root.
root_layer()->Remove(layer.get());
root_layer()->Add(layer.get());
// Wait a bit to ensure that report does not happen.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(100));
run_loop.Run();
}
// Tests animation throughput not reported and no leak when animation is stopped
// without being attached to a root.
TEST_F(AnimationThroughputReporterTest, EndDetachedNoReportNoLeak) {
auto layer = std::make_unique<Layer>();
layer->SetOpacity(0.5f);
LayerAnimator* animator = layer->GetAnimator();
// Schedule an animation without being attached to a root.
{
AnimationThroughputReporter reporter(
animator, base::BindLambdaForTesting(
[&](cc::FrameSequenceMetrics::ThroughputData) {
ADD_FAILURE() << "No report for aborted animations.";
}));
ScopedLayerAnimationSettings settings(animator);
settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(50));
layer->SetOpacity(1.0f);
}
// End the animation without being attached to a root.
animator->StopAnimating();
// Wait a bit to ensure that report does not happen.
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromMilliseconds(100));
run_loop.Run();
// AnimationTracker in |reporter| should not leak in asan.
}
} // namespace ui
...@@ -73,6 +73,12 @@ class COMPOSITOR_EXPORT LayerAnimationObserver { ...@@ -73,6 +73,12 @@ class COMPOSITOR_EXPORT LayerAnimationObserver {
// Called when |this| is removed to |sequence|'s observer list. // Called when |this| is removed to |sequence|'s observer list.
virtual void OnDetachedFromSequence(LayerAnimationSequence* sequence); virtual void OnDetachedFromSequence(LayerAnimationSequence* sequence);
// Called when the relevant animator attaches to an animation timeline.
virtual void OnAnimatorAttachedToTimeline() {}
// Called when the relevant animator detaches from an animation timeline.
virtual void OnAnimatorDetachedFromTimeline() {}
// Detaches this observer from all sequences it is currently observing. // Detaches this observer from all sequences it is currently observing.
void StopObserving(); void StopObserving();
......
...@@ -249,11 +249,17 @@ void LayerAnimationSequence::OnAnimatorAttached( ...@@ -249,11 +249,17 @@ void LayerAnimationSequence::OnAnimatorAttached(
LayerAnimationDelegate* delegate) { LayerAnimationDelegate* delegate) {
for (auto& element : elements_) for (auto& element : elements_)
element->OnAnimatorAttached(delegate); element->OnAnimatorAttached(delegate);
for (LayerAnimationObserver& observer : observers_)
observer.OnAnimatorAttachedToTimeline();
} }
void LayerAnimationSequence::OnAnimatorDetached() { void LayerAnimationSequence::OnAnimatorDetached() {
for (auto& element : elements_) for (auto& element : elements_)
element->OnAnimatorDetached(); element->OnAnimatorDetached();
for (LayerAnimationObserver& observer : observers_)
observer.OnAnimatorDetachedFromTimeline();
} }
void LayerAnimationSequence::SetAnimationMetricsReporter( void LayerAnimationSequence::SetAnimationMetricsReporter(
......
...@@ -258,6 +258,7 @@ class COMPOSITOR_EXPORT LayerAnimator : public base::RefCounted<LayerAnimator>, ...@@ -258,6 +258,7 @@ class COMPOSITOR_EXPORT LayerAnimator : public base::RefCounted<LayerAnimator>,
friend class base::RefCounted<LayerAnimator>; friend class base::RefCounted<LayerAnimator>;
friend class ScopedLayerAnimationSettings; friend class ScopedLayerAnimationSettings;
friend class LayerAnimatorTestController; friend class LayerAnimatorTestController;
friend class AnimationThroughputReporter;
FRIEND_TEST_ALL_PREFIXES(LayerAnimatorTest, AnimatorStartedCorrectly); FRIEND_TEST_ALL_PREFIXES(LayerAnimatorTest, AnimatorStartedCorrectly);
FRIEND_TEST_ALL_PREFIXES(LayerAnimatorTest, FRIEND_TEST_ALL_PREFIXES(LayerAnimatorTest,
AnimatorRemovedFromCollectionWhenLayerIsDestroyed); AnimatorRemovedFromCollectionWhenLayerIsDestroyed);
......
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