blob: 2cff5ac3c3f76f84ffc4f4403dde2f6bb3fab5f9 [file] [log] [blame]
// Copyright 2014 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 "components/scheduler/renderer/renderer_scheduler_impl.h"
#include "base/callback.h"
#include "base/test/simple_test_tick_clock.h"
#include "cc/output/begin_frame_args.h"
#include "cc/test/ordered_simple_task_runner.h"
#include "components/scheduler/base/test_time_source.h"
#include "components/scheduler/child/scheduler_tqm_delegate_for_test.h"
#include "components/scheduler/child/scheduler_tqm_delegate_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace scheduler {
namespace {
class FakeInputEvent : public blink::WebInputEvent {
public:
explicit FakeInputEvent(blink::WebInputEvent::Type event_type)
: WebInputEvent(sizeof(FakeInputEvent)) {
type = event_type;
}
FakeInputEvent(blink::WebInputEvent::Type event_type, int event_modifiers)
: WebInputEvent(sizeof(FakeInputEvent)) {
type = event_type;
modifiers = event_modifiers;
}
};
void AppendToVectorTestTask(std::vector<std::string>* vector,
std::string value) {
vector->push_back(value);
}
void AppendToVectorIdleTestTask(std::vector<std::string>* vector,
std::string value,
base::TimeTicks deadline) {
AppendToVectorTestTask(vector, value);
}
void NullTask() {
}
void AppendToVectorReentrantTask(base::SingleThreadTaskRunner* task_runner,
std::vector<int>* vector,
int* reentrant_count,
int max_reentrant_count) {
vector->push_back((*reentrant_count)++);
if (*reentrant_count < max_reentrant_count) {
task_runner->PostTask(
FROM_HERE,
base::Bind(AppendToVectorReentrantTask, base::Unretained(task_runner),
vector, reentrant_count, max_reentrant_count));
}
}
void IdleTestTask(int* run_count,
base::TimeTicks* deadline_out,
base::TimeTicks deadline) {
(*run_count)++;
*deadline_out = deadline;
}
int max_idle_task_reposts = 2;
void RepostingIdleTestTask(SingleThreadIdleTaskRunner* idle_task_runner,
int* run_count,
base::TimeTicks deadline) {
if ((*run_count + 1) < max_idle_task_reposts) {
idle_task_runner->PostIdleTask(
FROM_HERE, base::Bind(&RepostingIdleTestTask,
base::Unretained(idle_task_runner), run_count));
}
(*run_count)++;
}
void RepostingUpdateClockIdleTestTask(
SingleThreadIdleTaskRunner* idle_task_runner,
int* run_count,
base::SimpleTestTickClock* clock,
base::TimeDelta advance_time,
std::vector<base::TimeTicks>* deadlines,
base::TimeTicks deadline) {
if ((*run_count + 1) < max_idle_task_reposts) {
idle_task_runner->PostIdleTask(
FROM_HERE, base::Bind(&RepostingUpdateClockIdleTestTask,
base::Unretained(idle_task_runner), run_count,
clock, advance_time, deadlines));
}
deadlines->push_back(deadline);
(*run_count)++;
clock->Advance(advance_time);
}
void WillBeginFrameIdleTask(RendererScheduler* scheduler,
base::SimpleTestTickClock* clock,
base::TimeTicks deadline) {
scheduler->WillBeginFrame(cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL));
}
void UpdateClockToDeadlineIdleTestTask(base::SimpleTestTickClock* clock,
int* run_count,
base::TimeTicks deadline) {
clock->Advance(deadline - clock->NowTicks());
(*run_count)++;
}
void PostingYieldingTestTask(RendererSchedulerImpl* scheduler,
base::SingleThreadTaskRunner* task_runner,
bool simulate_input,
bool* should_yield_before,
bool* should_yield_after) {
*should_yield_before = scheduler->ShouldYieldForHighPriorityWork();
task_runner->PostTask(FROM_HERE, base::Bind(NullTask));
if (simulate_input) {
scheduler->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
}
*should_yield_after = scheduler->ShouldYieldForHighPriorityWork();
}
enum class SimulateInputType {
None,
TouchStart,
TouchEnd,
GestureScrollBegin,
GestureScrollEnd
};
void AnticipationTestTask(RendererSchedulerImpl* scheduler,
SimulateInputType simulate_input,
bool* is_anticipated_before,
bool* is_anticipated_after) {
*is_anticipated_before = scheduler->IsHighPriorityWorkAnticipated();
switch (simulate_input) {
case SimulateInputType::None:
break;
case SimulateInputType::TouchStart:
scheduler->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
break;
case SimulateInputType::TouchEnd:
scheduler->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchEnd),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
break;
case SimulateInputType::GestureScrollBegin:
scheduler->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollBegin),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
break;
case SimulateInputType::GestureScrollEnd:
scheduler->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollEnd),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
break;
}
*is_anticipated_after = scheduler->IsHighPriorityWorkAnticipated();
}
}; // namespace
class RendererSchedulerImplForTest : public RendererSchedulerImpl {
public:
using RendererSchedulerImpl::OnIdlePeriodEnded;
using RendererSchedulerImpl::OnIdlePeriodStarted;
RendererSchedulerImplForTest(
scoped_refptr<SchedulerTqmDelegate> main_task_runner)
: RendererSchedulerImpl(main_task_runner), update_policy_count_(0) {}
void UpdatePolicyLocked(UpdateType update_type) override {
update_policy_count_++;
RendererSchedulerImpl::UpdatePolicyLocked(update_type);
std::string use_case =
RendererScheduler::UseCaseToString(MainThreadOnly().current_use_case);
if (MainThreadOnly().touchstart_expected_soon) {
use_cases_.push_back(use_case + " scroll expected");
} else {
use_cases_.push_back(use_case);
}
}
void EnsureUrgentPolicyUpdatePostedOnMainThread() {
base::AutoLock lock(any_thread_lock_);
RendererSchedulerImpl::EnsureUrgentPolicyUpdatePostedOnMainThread(
FROM_HERE);
}
void ScheduleDelayedPolicyUpdate(base::TimeTicks now, base::TimeDelta delay) {
delayed_update_policy_runner_.SetDeadline(FROM_HERE, delay, now);
}
bool BeginMainFrameOnCriticalPath() {
base::AutoLock lock(any_thread_lock_);
return AnyThread().begin_main_frame_on_critical_path;
}
int update_policy_count_;
std::vector<std::string> use_cases_;
};
// Lets gtest print human readable Policy values.
::std::ostream& operator<<(::std::ostream& os,
const RendererScheduler::UseCase& use_case) {
return os << RendererScheduler::UseCaseToString(use_case);
}
class RendererSchedulerImplTest : public testing::Test {
public:
using UseCase = RendererSchedulerImpl::UseCase;
RendererSchedulerImplTest() : clock_(new base::SimpleTestTickClock()) {
clock_->Advance(base::TimeDelta::FromMicroseconds(5000));
}
RendererSchedulerImplTest(base::MessageLoop* message_loop)
: clock_(new base::SimpleTestTickClock()), message_loop_(message_loop) {
clock_->Advance(base::TimeDelta::FromMicroseconds(5000));
}
~RendererSchedulerImplTest() override {}
void SetUp() override {
if (message_loop_) {
main_task_runner_ = SchedulerTqmDelegateImpl::Create(
message_loop_.get(),
make_scoped_ptr(new TestTimeSource(clock_.get())));
} else {
mock_task_runner_ = make_scoped_refptr(
new cc::OrderedSimpleTaskRunner(clock_.get(), false));
main_task_runner_ = SchedulerTqmDelegateForTest::Create(
mock_task_runner_, make_scoped_ptr(new TestTimeSource(clock_.get())));
}
Initialize(
make_scoped_ptr(new RendererSchedulerImplForTest(main_task_runner_)));
}
void Initialize(scoped_ptr<RendererSchedulerImplForTest> scheduler) {
scheduler_ = scheduler.Pass();
default_task_runner_ = scheduler_->DefaultTaskRunner();
compositor_task_runner_ = scheduler_->CompositorTaskRunner();
loading_task_runner_ = scheduler_->LoadingTaskRunner();
idle_task_runner_ = scheduler_->IdleTaskRunner();
timer_task_runner_ = scheduler_->TimerTaskRunner();
}
void TearDown() override {
DCHECK(!mock_task_runner_.get() || !message_loop_.get());
scheduler_->Shutdown();
if (mock_task_runner_.get()) {
// Check that all tests stop posting tasks.
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
while (mock_task_runner_->RunUntilIdle()) {
}
} else {
message_loop_->RunUntilIdle();
}
scheduler_.reset();
}
void RunUntilIdle() {
// Only one of mock_task_runner_ or message_loop_ should be set.
DCHECK(!mock_task_runner_.get() || !message_loop_.get());
if (mock_task_runner_.get())
mock_task_runner_->RunUntilIdle();
else
message_loop_->RunUntilIdle();
}
void DoMainFrame() {
cc::BeginFrameArgs begin_frame_args = cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(16), cc::BeginFrameArgs::NORMAL);
begin_frame_args.on_critical_path = false;
scheduler_->WillBeginFrame(begin_frame_args);
scheduler_->DidCommitFrameToCompositor();
}
void ForceMainThreadScrollingUseCase() {
cc::BeginFrameArgs begin_frame_args = cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(16), cc::BeginFrameArgs::NORMAL);
begin_frame_args.on_critical_path = true;
scheduler_->WillBeginFrame(begin_frame_args);
}
void ForceTouchStartToBeExpectedSoon() {
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollEnd),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
clock_->Advance(priority_escalation_after_input_duration() * 2);
scheduler_->ForceUpdatePolicy();
}
void SimulateExpensiveTasks(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
// RunUntilIdle won't actually run all of the SimpleTestTickClock::Advance
// tasks unless we set AutoAdvanceNow to true :/
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
// Simulate a bunch of expensive tasks
for (int i = 0; i < 10; i++) {
task_runner->PostTask(FROM_HERE,
base::Bind(&base::SimpleTestTickClock::Advance,
base::Unretained(clock_.get()),
base::TimeDelta::FromMilliseconds(500)));
}
RunUntilIdle();
// Switch back to not auto-advancing because we want to be in control of
// when time advances.
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(false);
}
void WillBeginMainThreadGestureFrame() {
scheduler_->DidAnimateForInputOnCompositorThread();
cc::BeginFrameArgs begin_frame_args = cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(16), cc::BeginFrameArgs::NORMAL);
begin_frame_args.on_critical_path = true;
scheduler_->WillBeginFrame(begin_frame_args);
}
void SimulateMainThreadGestureCompositorTask(
base::TimeDelta begin_main_frame_duration) {
WillBeginMainThreadGestureFrame();
clock_->Advance(begin_main_frame_duration);
scheduler_->DidCommitFrameToCompositor();
}
void SimulateTimerTask(base::TimeDelta duration) {
clock_->Advance(duration);
simulate_timer_task_ran_ = true;
}
void EnableIdleTasks() { DoMainFrame(); }
UseCase CurrentUseCase() {
return scheduler_->MainThreadOnly().current_use_case;
}
UseCase ForceUpdatePolicyAndGetCurrentUseCase() {
scheduler_->ForceUpdatePolicy();
return scheduler_->MainThreadOnly().current_use_case;
}
bool TouchStartExpectedSoon() {
return scheduler_->MainThreadOnly().touchstart_expected_soon;
}
bool HaveSeenABeginMainframe() {
return scheduler_->MainThreadOnly().have_seen_a_begin_main_frame;
}
bool LoadingTasksSeemExpensive() {
return scheduler_->MainThreadOnly().loading_tasks_seem_expensive;
}
bool TimerTasksSeemExpensive() {
return scheduler_->MainThreadOnly().timer_tasks_seem_expensive;
}
base::TimeTicks EstimatedNextFrameBegin() {
return scheduler_->MainThreadOnly().estimated_next_frame_begin;
}
// Helper for posting several tasks of specific types. |task_descriptor| is a
// string with space delimited task identifiers. The first letter of each
// task identifier specifies the task type:
// - 'D': Default task
// - 'C': Compositor task
// - 'L': Loading task
// - 'I': Idle task
// - 'T': Timer task
void PostTestTasks(std::vector<std::string>* run_order,
const std::string& task_descriptor) {
std::istringstream stream(task_descriptor);
while (!stream.eof()) {
std::string task;
stream >> task;
switch (task[0]) {
case 'D':
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&AppendToVectorTestTask, run_order, task));
break;
case 'C':
compositor_task_runner_->PostTask(
FROM_HERE, base::Bind(&AppendToVectorTestTask, run_order, task));
break;
case 'L':
loading_task_runner_->PostTask(
FROM_HERE, base::Bind(&AppendToVectorTestTask, run_order, task));
break;
case 'I':
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&AppendToVectorIdleTestTask, run_order, task));
break;
case 'T':
timer_task_runner_->PostTask(
FROM_HERE, base::Bind(&AppendToVectorTestTask, run_order, task));
break;
default:
NOTREACHED();
}
}
}
protected:
static base::TimeDelta priority_escalation_after_input_duration() {
return base::TimeDelta::FromMilliseconds(
UserModel::kGestureEstimationLimitMillis);
}
static base::TimeDelta subsequent_input_expected_after_input_duration() {
return base::TimeDelta::FromMilliseconds(
UserModel::kExpectSubsequentGestureMillis);
}
static base::TimeDelta maximum_idle_period_duration() {
return base::TimeDelta::FromMilliseconds(
IdleHelper::kMaximumIdlePeriodMillis);
}
static base::TimeDelta end_idle_when_hidden_delay() {
return base::TimeDelta::FromMilliseconds(
RendererSchedulerImpl::kEndIdleWhenHiddenDelayMillis);
}
static base::TimeDelta idle_period_starvation_threshold() {
return base::TimeDelta::FromMilliseconds(
RendererSchedulerImpl::kIdlePeriodStarvationThresholdMillis);
}
static base::TimeDelta suspend_timers_when_backgrounded_delay() {
return base::TimeDelta::FromMilliseconds(
RendererSchedulerImpl::kSuspendTimersWhenBackgroundedDelayMillis);
}
template <typename E>
static void CallForEachEnumValue(E first,
E last,
const char* (*function)(E)) {
for (E val = first; val < last;
val = static_cast<E>(static_cast<int>(val) + 1)) {
(*function)(val);
}
}
static void CheckAllUseCaseToString() {
CallForEachEnumValue<RendererSchedulerImpl::UseCase>(
RendererSchedulerImpl::UseCase::FIRST_USE_CASE,
RendererSchedulerImpl::UseCase::USE_CASE_COUNT,
&RendererSchedulerImpl::UseCaseToString);
}
scoped_ptr<base::SimpleTestTickClock> clock_;
// Only one of mock_task_runner_ or message_loop_ will be set.
scoped_refptr<cc::OrderedSimpleTaskRunner> mock_task_runner_;
scoped_ptr<base::MessageLoop> message_loop_;
scoped_refptr<SchedulerTqmDelegate> main_task_runner_;
scoped_ptr<RendererSchedulerImplForTest> scheduler_;
scoped_refptr<base::SingleThreadTaskRunner> default_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> compositor_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner_;
scoped_refptr<SingleThreadIdleTaskRunner> idle_task_runner_;
scoped_refptr<base::SingleThreadTaskRunner> timer_task_runner_;
bool simulate_timer_task_ran_;
DISALLOW_COPY_AND_ASSIGN(RendererSchedulerImplTest);
};
TEST_F(RendererSchedulerImplTest, TestPostDefaultTask) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "D1 D2 D3 D4");
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("D1"), std::string("D2"),
std::string("D3"), std::string("D4")));
}
TEST_F(RendererSchedulerImplTest, TestPostDefaultAndCompositor) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "D1 C1");
RunUntilIdle();
EXPECT_THAT(run_order, testing::Contains("D1"));
EXPECT_THAT(run_order, testing::Contains("C1"));
}
TEST_F(RendererSchedulerImplTest, TestRentrantTask) {
int count = 0;
std::vector<int> run_order;
default_task_runner_->PostTask(
FROM_HERE, base::Bind(AppendToVectorReentrantTask, default_task_runner_,
&run_order, &count, 5));
RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre(0, 1, 2, 3, 4));
}
TEST_F(RendererSchedulerImplTest, TestPostIdleTask) {
int run_count = 0;
base::TimeTicks expected_deadline =
clock_->NowTicks() + base::TimeDelta::FromMilliseconds(2300);
base::TimeTicks deadline_in_task;
clock_->Advance(base::TimeDelta::FromMilliseconds(100));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
EXPECT_EQ(0, run_count); // Shouldn't run yet as no WillBeginFrame.
scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL));
RunUntilIdle();
EXPECT_EQ(0, run_count); // Shouldn't run as no DidCommitFrameToCompositor.
clock_->Advance(base::TimeDelta::FromMilliseconds(1200));
scheduler_->DidCommitFrameToCompositor();
RunUntilIdle();
EXPECT_EQ(0, run_count); // We missed the deadline.
scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL));
clock_->Advance(base::TimeDelta::FromMilliseconds(800));
scheduler_->DidCommitFrameToCompositor();
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(RendererSchedulerImplTest, TestRepostingIdleTask) {
int run_count = 0;
max_idle_task_reposts = 2;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&RepostingIdleTestTask, idle_task_runner_, &run_count));
EnableIdleTasks();
RunUntilIdle();
EXPECT_EQ(1, run_count);
// Reposted tasks shouldn't run until next idle period.
RunUntilIdle();
EXPECT_EQ(1, run_count);
EnableIdleTasks();
RunUntilIdle();
EXPECT_EQ(2, run_count);
}
TEST_F(RendererSchedulerImplTest, TestIdleTaskExceedsDeadline) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
int run_count = 0;
// Post two UpdateClockToDeadlineIdleTestTask tasks.
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&UpdateClockToDeadlineIdleTestTask, clock_.get(), &run_count));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&UpdateClockToDeadlineIdleTestTask, clock_.get(), &run_count));
EnableIdleTasks();
RunUntilIdle();
// Only the first idle task should execute since it's used up the deadline.
EXPECT_EQ(1, run_count);
EnableIdleTasks();
RunUntilIdle();
// Second task should be run on the next idle period.
EXPECT_EQ(2, run_count);
}
TEST_F(RendererSchedulerImplTest, TestPostIdleTaskAfterWakeup) {
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTaskAfterWakeup(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
EnableIdleTasks();
RunUntilIdle();
// Shouldn't run yet as no other task woke up the scheduler.
EXPECT_EQ(0, run_count);
idle_task_runner_->PostIdleTaskAfterWakeup(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
EnableIdleTasks();
RunUntilIdle();
// Another after wakeup idle task shouldn't wake the scheduler.
EXPECT_EQ(0, run_count);
default_task_runner_->PostTask(FROM_HERE, base::Bind(&NullTask));
RunUntilIdle();
EnableIdleTasks(); // Must start a new idle period before idle task runs.
RunUntilIdle();
// Execution of default task queue task should trigger execution of idle task.
EXPECT_EQ(2, run_count);
}
TEST_F(RendererSchedulerImplTest, TestPostIdleTaskAfterWakeupWhileAwake) {
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTaskAfterWakeup(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
default_task_runner_->PostTask(FROM_HERE, base::Bind(&NullTask));
RunUntilIdle();
EnableIdleTasks(); // Must start a new idle period before idle task runs.
RunUntilIdle();
// Should run as the scheduler was already awakened by the normal task.
EXPECT_EQ(1, run_count);
}
TEST_F(RendererSchedulerImplTest, TestPostIdleTaskWakesAfterWakeupIdleTask) {
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTaskAfterWakeup(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
EnableIdleTasks();
RunUntilIdle();
// Must start a new idle period before after-wakeup idle task runs.
EnableIdleTasks();
RunUntilIdle();
// Normal idle task should wake up after-wakeup idle task.
EXPECT_EQ(2, run_count);
}
TEST_F(RendererSchedulerImplTest, TestDelayedEndIdlePeriodCanceled) {
int run_count = 0;
base::TimeTicks deadline_in_task;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
// Trigger the beginning of an idle period for 1000ms.
scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL));
DoMainFrame();
// End the idle period early (after 500ms), and send a WillBeginFrame which
// specifies that the next idle period should end 1000ms from now.
clock_->Advance(base::TimeDelta::FromMilliseconds(500));
scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL));
RunUntilIdle();
EXPECT_EQ(0, run_count); // Not currently in an idle period.
// Trigger the start of the idle period before the task to end the previous
// idle period has been triggered.
clock_->Advance(base::TimeDelta::FromMilliseconds(400));
scheduler_->DidCommitFrameToCompositor();
// Post a task which simulates running until after the previous end idle
// period delayed task was scheduled for
scheduler_->DefaultTaskRunner()->PostTask(FROM_HERE, base::Bind(NullTask));
clock_->Advance(base::TimeDelta::FromMilliseconds(300));
RunUntilIdle();
EXPECT_EQ(1, run_count); // We should still be in the new idle period.
}
TEST_F(RendererSchedulerImplTest, TestDefaultPolicy) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2");
EnableIdleTasks();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1"),
std::string("C1"), std::string("D2"),
std::string("C2"), std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
}
TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_CompositorHandlesInput) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2");
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
EnableIdleTasks();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1"),
std::string("C1"), std::string("D2"),
std::string("C2"), std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase());
}
TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_MainThreadHandlesInput) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2");
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
EnableIdleTasks();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1"),
std::string("C1"), std::string("D2"),
std::string("C2"), std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase());
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart));
}
TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_DidAnimateForInput) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
scheduler_->DidAnimateForInputOnCompositorThread();
EnableIdleTasks();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("D1"), std::string("C1"),
std::string("D2"), std::string("C2"),
std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase());
}
TEST_F(
RendererSchedulerImplTest,
TestCompositorPolicy_ExpensiveTimersDontRunWhenMainThreadOnCriticalPath) {
std::vector<std::string> run_order;
SimulateExpensiveTasks(timer_task_runner_);
// Timers should now be disabled during main thread user user interactions.
PostTestTasks(&run_order, "C1 T1");
// Trigger main_thread_gesture UseCase
WillBeginMainThreadGestureFrame();
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase());
EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1")));
clock_->Advance(subsequent_input_expected_after_input_duration() * 2);
run_order.clear();
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
EXPECT_THAT(run_order, testing::ElementsAre(std::string("T1")));
}
TEST_F(RendererSchedulerImplTest, Navigation_ResetsTaskCostEstimations) {
std::vector<std::string> run_order;
SimulateExpensiveTasks(timer_task_runner_);
scheduler_->OnNavigationStarted();
PostTestTasks(&run_order, "C1 T1");
WillBeginMainThreadGestureFrame();
scheduler_->DidCommitFrameToCompositor(); // Starts Idle Period
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("T1")));
}
TEST_F(RendererSchedulerImplTest,
TestCompositorPolicy_TimersAlwaysRun_MainThreadNotOnCriticalPath) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "C1 T1");
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
scheduler_->DidAnimateForInputOnCompositorThread();
cc::BeginFrameArgs begin_frame_args1 = cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL);
begin_frame_args1.on_critical_path = false;
scheduler_->WillBeginFrame(begin_frame_args1);
scheduler_->DidCommitFrameToCompositor(); // Starts Idle Period
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("T1")));
// End the idle period.
clock_->Advance(base::TimeDelta::FromMilliseconds(500));
scheduler_->DidAnimateForInputOnCompositorThread();
cc::BeginFrameArgs begin_frame_args2 = cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL);
begin_frame_args2.on_critical_path = false;
scheduler_->WillBeginFrame(begin_frame_args2);
run_order.clear();
PostTestTasks(&run_order, "C1 T1");
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("T1")));
}
TEST_F(RendererSchedulerImplTest, TestTouchstartPolicy_Compositor) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 D1 C1 D2 C2 T1 T2");
// Observation of touchstart should defer execution of timer, idle and loading
// tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
EnableIdleTasks();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2")));
// Animation or meta events like TapDown/FlingCancel shouldn't affect the
// priority.
run_order.clear();
scheduler_->DidAnimateForInputOnCompositorThread();
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingCancel),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureTapDown),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
// Action events like ScrollBegin will kick us back into compositor priority,
// allowing service of the timer, loading and idle queues.
run_order.clear();
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollBegin),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("T1"),
std::string("T2")));
}
TEST_F(RendererSchedulerImplTest, TestTouchstartPolicy_MainThread) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 D1 C1 D2 C2 T1 T2");
// Observation of touchstart should defer execution of timer, idle and loading
// tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchStart));
EnableIdleTasks();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2")));
// Meta events like TapDown/FlingCancel shouldn't affect the priority.
run_order.clear();
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingCancel),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingCancel));
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureTapDown),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureTapDown));
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
// Action events like ScrollBegin will kick us back into compositor priority,
// allowing service of the timer, loading and idle queues.
run_order.clear();
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollBegin),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollBegin));
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("T1"),
std::string("T2")));
}
// TODO(alexclarke): Reenable once we've reinstaed the Loading UseCase.
TEST_F(RendererSchedulerImplTest, DISABLED_LoadingUseCase) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 T1 L1 D2 C2 T2 L2");
scheduler_->OnNavigationStarted();
EnableIdleTasks();
RunUntilIdle();
// In loading policy, loading tasks are prioritized other others.
std::string loading_policy_expected[] = {
std::string("D1"), std::string("L1"), std::string("D2"),
std::string("L2"), std::string("C1"), std::string("T1"),
std::string("C2"), std::string("T2"), std::string("I1")};
EXPECT_THAT(run_order, testing::ElementsAreArray(loading_policy_expected));
EXPECT_EQ(RendererScheduler::UseCase::LOADING, CurrentUseCase());
// Advance 15s and try again, the loading policy should have ended and the
// task order should return to the NONE use case where loading tasks are no
// longer prioritized.
clock_->Advance(base::TimeDelta::FromMilliseconds(150000));
run_order.clear();
PostTestTasks(&run_order, "I1 D1 C1 T1 L1 D2 C2 T2 L2");
EnableIdleTasks();
RunUntilIdle();
std::string default_order_expected[] = {
std::string("D1"), std::string("C1"), std::string("T1"),
std::string("L1"), std::string("D2"), std::string("C2"),
std::string("T2"), std::string("L2"), std::string("I1")};
EXPECT_THAT(run_order, testing::ElementsAreArray(default_order_expected));
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
}
TEST_F(RendererSchedulerImplTest,
EventConsumedOnCompositorThread_IgnoresMouseMove_WhenMouseUp) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::MouseMove),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
// Note compositor tasks are not prioritized.
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("D1"), std::string("C1"),
std::string("D2"), std::string("C2"),
std::string("I1")));
}
TEST_F(RendererSchedulerImplTest,
EventForwardedToMainThread_IgnoresMouseMove_WhenMouseUp) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::MouseMove),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
RunUntilIdle();
// Note compositor tasks are not prioritized.
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("D1"), std::string("C1"),
std::string("D2"), std::string("C2"),
std::string("I1")));
}
TEST_F(RendererSchedulerImplTest,
EventConsumedOnCompositorThread_MouseMove_WhenMouseDown) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::MouseMove,
blink::WebInputEvent::LeftButtonDown),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
// Note compositor tasks are prioritized.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2"),
std::string("I1")));
}
TEST_F(RendererSchedulerImplTest,
EventForwardedToMainThread_MouseMove_WhenMouseDown) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::MouseMove,
blink::WebInputEvent::LeftButtonDown),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
RunUntilIdle();
// Note compositor tasks are prioritized.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2"),
std::string("I1")));
scheduler_->DidHandleInputEventOnMainThread(FakeInputEvent(
blink::WebInputEvent::MouseMove, blink::WebInputEvent::LeftButtonDown));
}
TEST_F(RendererSchedulerImplTest, EventConsumedOnCompositorThread_MouseWheel) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::MouseWheel),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
// Note compositor tasks are prioritized.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2"),
std::string("I1")));
}
TEST_F(RendererSchedulerImplTest, EventForwardedToMainThread_MouseWheel) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::MouseWheel),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
RunUntilIdle();
// Note compositor tasks are prioritized.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2"),
std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase());
}
TEST_F(RendererSchedulerImplTest,
EventConsumedOnCompositorThread_IgnoresKeyboardEvents) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::KeyDown),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
// Note compositor tasks are not prioritized.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("D1"), std::string("C1"),
std::string("D2"), std::string("C2"),
std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
}
TEST_F(RendererSchedulerImplTest,
EventForwardedToMainThread_IgnoresKeyboardEvents) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "I1 D1 C1 D2 C2");
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::KeyDown),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
RunUntilIdle();
// Note compositor tasks are not prioritized.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("D1"), std::string("C1"),
std::string("D2"), std::string("C2"),
std::string("I1")));
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
// Note compositor tasks are not prioritized.
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::KeyDown));
}
TEST_F(RendererSchedulerImplTest,
TestMainthreadScrollingUseCaseDoesNotStarveDefaultTasks) {
ForceMainThreadScrollingUseCase();
scheduler_->DidCommitFrameToCompositor(); // Enable Idle tasks.
std::vector<std::string> run_order;
PostTestTasks(&run_order, "D1 C1");
for (int i = 0; i < 20; i++) {
compositor_task_runner_->PostTask(FROM_HERE, base::Bind(&NullTask));
}
PostTestTasks(&run_order, "C2");
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
// Ensure that the default D1 task gets to run at some point before the final
// C2 compositor task.
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("D1"),
std::string("C2")));
}
TEST_F(RendererSchedulerImplTest,
TestCompositorPolicyEnds_CompositorHandlesInput) {
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
EXPECT_EQ(UseCase::COMPOSITOR_GESTURE,
ForceUpdatePolicyAndGetCurrentUseCase());
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
}
TEST_F(RendererSchedulerImplTest,
TestCompositorPolicyEnds_MainThreadHandlesInput) {
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart));
EXPECT_EQ(UseCase::COMPOSITOR_GESTURE,
ForceUpdatePolicyAndGetCurrentUseCase());
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
}
TEST_F(RendererSchedulerImplTest, TestTouchstartPolicyEndsAfterTimeout) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 D1 C1 D2 C2");
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2")));
run_order.clear();
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
// Don't post any compositor tasks to simulate a very long running event
// handler.
PostTestTasks(&run_order, "D1 D2");
// Touchstart policy mode should have ended now that the clock has advanced.
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1"),
std::string("D2")));
}
TEST_F(RendererSchedulerImplTest,
TestTouchstartPolicyEndsAfterConsecutiveTouchmoves) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "L1 D1 C1 D2 C2");
// Observation of touchstart should defer execution of idle and loading tasks.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("C2"),
std::string("D1"), std::string("D2")));
// Receiving the first touchmove will not affect scheduler priority.
run_order.clear();
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchMove),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
// Receiving the second touchmove will kick us back into compositor priority.
run_order.clear();
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchMove),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre(std::string("L1")));
}
TEST_F(RendererSchedulerImplTest, TestIsHighPriorityWorkAnticipated) {
bool is_anticipated_before = false;
bool is_anticipated_after = false;
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::None, &is_anticipated_before,
&is_anticipated_after));
RunUntilIdle();
// In its default state, without input receipt, the scheduler should indicate
// that no high-priority is anticipated.
EXPECT_FALSE(is_anticipated_before);
EXPECT_FALSE(is_anticipated_after);
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::TouchStart,
&is_anticipated_before, &is_anticipated_after));
bool dummy;
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::TouchEnd, &dummy, &dummy));
default_task_runner_->PostTask(
FROM_HERE,
base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::GestureScrollBegin, &dummy, &dummy));
default_task_runner_->PostTask(
FROM_HERE,
base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::GestureScrollEnd, &dummy, &dummy));
RunUntilIdle();
// When input is received, the scheduler should indicate that high-priority
// work is anticipated.
EXPECT_FALSE(is_anticipated_before);
EXPECT_TRUE(is_anticipated_after);
clock_->Advance(priority_escalation_after_input_duration() * 2);
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::None, &is_anticipated_before,
&is_anticipated_after));
RunUntilIdle();
// Without additional input, the scheduler should go into NONE
// use case but with scrolling expected where high-priority work is still
// anticipated.
EXPECT_EQ(UseCase::NONE, CurrentUseCase());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_TRUE(is_anticipated_before);
EXPECT_TRUE(is_anticipated_after);
clock_->Advance(subsequent_input_expected_after_input_duration() * 2);
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&AnticipationTestTask, scheduler_.get(),
SimulateInputType::None, &is_anticipated_before,
&is_anticipated_after));
RunUntilIdle();
// Eventually the scheduler should go into the default use case where
// high-priority work is no longer anticipated.
EXPECT_EQ(UseCase::NONE, CurrentUseCase());
EXPECT_FALSE(TouchStartExpectedSoon());
EXPECT_FALSE(is_anticipated_before);
EXPECT_FALSE(is_anticipated_after);
}
TEST_F(RendererSchedulerImplTest, TestShouldYield) {
bool should_yield_before = false;
bool should_yield_after = false;
ForceMainThreadScrollingUseCase();
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&PostingYieldingTestTask, scheduler_.get(),
default_task_runner_, false, &should_yield_before,
&should_yield_after));
RunUntilIdle();
// Posting to default runner shouldn't cause yielding.
EXPECT_FALSE(should_yield_before);
EXPECT_FALSE(should_yield_after);
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&PostingYieldingTestTask, scheduler_.get(),
compositor_task_runner_, false,
&should_yield_before, &should_yield_after));
RunUntilIdle();
// Posting while not mainthread scrolling shouldn't cause yielding.
EXPECT_FALSE(should_yield_before);
EXPECT_FALSE(should_yield_after);
default_task_runner_->PostTask(
FROM_HERE, base::Bind(&PostingYieldingTestTask, scheduler_.get(),
compositor_task_runner_, true, &should_yield_before,
&should_yield_after));
RunUntilIdle();
// We should be able to switch to compositor priority mid-task.
EXPECT_FALSE(should_yield_before);
EXPECT_TRUE(should_yield_after);
}
TEST_F(RendererSchedulerImplTest, TestShouldYield_TouchStart) {
// Receiving a touchstart should immediately trigger yielding, even if
// there's no immediately pending work in the compositor queue.
EXPECT_FALSE(scheduler_->ShouldYieldForHighPriorityWork());
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
EXPECT_TRUE(scheduler_->ShouldYieldForHighPriorityWork());
RunUntilIdle();
}
TEST_F(RendererSchedulerImplTest, SlowMainThreadInputEvent) {
EXPECT_EQ(UseCase::NONE, CurrentUseCase());
// An input event should bump us into input priority.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
RunUntilIdle();
EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, CurrentUseCase());
// Simulate the input event being queued for a very long time. The compositor
// task we post here represents the enqueued input task.
clock_->Advance(priority_escalation_after_input_duration() * 2);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart));
RunUntilIdle();
// Even though we exceeded the input priority escalation period, we should
// still be in compositor priority since the input remains queued.
EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, CurrentUseCase());
// After the escalation period ends we should go back into normal mode.
clock_->Advance(priority_escalation_after_input_duration() * 2);
RunUntilIdle();
EXPECT_EQ(UseCase::NONE, CurrentUseCase());
}
class RendererSchedulerImplWithMockSchedulerTest
: public RendererSchedulerImplTest {
public:
void SetUp() override {
mock_task_runner_ = make_scoped_refptr(
new cc::OrderedSimpleTaskRunner(clock_.get(), false));
main_task_runner_ = SchedulerTqmDelegateForTest::Create(
mock_task_runner_, make_scoped_ptr(new TestTimeSource(clock_.get())));
mock_scheduler_ = new RendererSchedulerImplForTest(main_task_runner_);
Initialize(make_scoped_ptr(mock_scheduler_));
}
protected:
RendererSchedulerImplForTest* mock_scheduler_;
};
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
OnlyOnePendingUrgentPolicyUpdatey) {
mock_scheduler_->EnsureUrgentPolicyUpdatePostedOnMainThread();
mock_scheduler_->EnsureUrgentPolicyUpdatePostedOnMainThread();
mock_scheduler_->EnsureUrgentPolicyUpdatePostedOnMainThread();
mock_scheduler_->EnsureUrgentPolicyUpdatePostedOnMainThread();
RunUntilIdle();
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
}
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
OnePendingDelayedAndOneUrgentUpdatePolicy) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
mock_scheduler_->ScheduleDelayedPolicyUpdate(
clock_->NowTicks(), base::TimeDelta::FromMilliseconds(1));
mock_scheduler_->EnsureUrgentPolicyUpdatePostedOnMainThread();
RunUntilIdle();
// We expect both the urgent and the delayed updates to run.
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
}
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
OneUrgentAndOnePendingDelayedUpdatePolicy) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
mock_scheduler_->EnsureUrgentPolicyUpdatePostedOnMainThread();
mock_scheduler_->ScheduleDelayedPolicyUpdate(
clock_->NowTicks(), base::TimeDelta::FromMilliseconds(1));
RunUntilIdle();
// We expect both the urgent and the delayed updates to run.
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
}
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
UpdatePolicyCountTriggeredByOneInputEvent) {
// We expect DidHandleInputEventOnCompositorThread to post an urgent policy
// update.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
EXPECT_EQ(0, mock_scheduler_->update_policy_count_);
mock_task_runner_->RunPendingTasks();
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchStart));
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
RunUntilIdle();
// We finally expect a delayed policy update 100ms later.
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
}
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
UpdatePolicyCountTriggeredByThreeInputEvents) {
// We expect DidHandleInputEventOnCompositorThread to post an urgent policy
// update.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
EXPECT_EQ(0, mock_scheduler_->update_policy_count_);
mock_task_runner_->RunPendingTasks();
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchStart));
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
// The second call to DidHandleInputEventOnCompositorThread should not post a
// policy update because we are already in compositor priority.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchMove),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
mock_task_runner_->RunPendingTasks();
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
// We expect DidHandleInputEvent to trigger a policy update.
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchMove));
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
// The third call to DidHandleInputEventOnCompositorThread should post a
// policy update because the awaiting_touch_start_response_ flag changed.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchMove),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
mock_task_runner_->RunPendingTasks();
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
// We expect DidHandleInputEvent to trigger a policy update.
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchMove));
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
RunUntilIdle();
// We finally expect a delayed policy update.
EXPECT_EQ(3, mock_scheduler_->update_policy_count_);
}
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
UpdatePolicyCountTriggeredByTwoInputEventsWithALongSeparatingDelay) {
// We expect DidHandleInputEventOnCompositorThread to post an urgent policy
// update.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
EXPECT_EQ(0, mock_scheduler_->update_policy_count_);
mock_task_runner_->RunPendingTasks();
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchStart));
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
RunUntilIdle();
// We expect a delayed policy update.
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
// We expect the second call to DidHandleInputEventOnCompositorThread to post
// an urgent policy update because we are no longer in compositor priority.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchMove),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
mock_task_runner_->RunPendingTasks();
EXPECT_EQ(3, mock_scheduler_->update_policy_count_);
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchMove));
EXPECT_EQ(3, mock_scheduler_->update_policy_count_);
clock_->Advance(base::TimeDelta::FromMilliseconds(1000));
RunUntilIdle();
// We finally expect a delayed policy update.
EXPECT_EQ(4, mock_scheduler_->update_policy_count_);
}
TEST_F(RendererSchedulerImplWithMockSchedulerTest,
EnsureUpdatePolicyNotTriggeredTooOften) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
EXPECT_EQ(0, mock_scheduler_->update_policy_count_);
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollBegin),
RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD);
// We expect the first call to IsHighPriorityWorkAnticipated to be called
// after recieving an input event (but before the UpdateTask was processed) to
// call UpdatePolicy.
EXPECT_EQ(1, mock_scheduler_->update_policy_count_);
scheduler_->IsHighPriorityWorkAnticipated();
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
// Subsequent calls should not call UpdatePolicy.
scheduler_->IsHighPriorityWorkAnticipated();
scheduler_->IsHighPriorityWorkAnticipated();
scheduler_->IsHighPriorityWorkAnticipated();
scheduler_->ShouldYieldForHighPriorityWork();
scheduler_->ShouldYieldForHighPriorityWork();
scheduler_->ShouldYieldForHighPriorityWork();
scheduler_->ShouldYieldForHighPriorityWork();
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::TouchStart));
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureScrollBegin));
EXPECT_EQ(2, mock_scheduler_->update_policy_count_);
// We expect both the urgent and the delayed updates to run in addition to the
// earlier updated cause by IsHighPriorityWorkAnticipated, a final update
// transitions from 'not_scrolling scroll expected' to 'not_scrolling'.
RunUntilIdle();
EXPECT_THAT(mock_scheduler_->use_cases_,
testing::ElementsAre(
std::string("none"), std::string("compositor_gesture"),
std::string("compositor_gesture"),
std::string("none scroll expected"), std::string("none")));
}
class RendererSchedulerImplWithMessageLoopTest
: public RendererSchedulerImplTest {
public:
RendererSchedulerImplWithMessageLoopTest()
: RendererSchedulerImplTest(new base::MessageLoop()) {}
~RendererSchedulerImplWithMessageLoopTest() override {}
void PostFromNestedRunloop(std::vector<
std::pair<SingleThreadIdleTaskRunner::IdleTask, bool>>* tasks) {
base::MessageLoop::ScopedNestableTaskAllower allow(message_loop_.get());
for (std::pair<SingleThreadIdleTaskRunner::IdleTask, bool>& pair : *tasks) {
if (pair.second) {
idle_task_runner_->PostIdleTask(FROM_HERE, pair.first);
} else {
idle_task_runner_->PostNonNestableIdleTask(FROM_HERE, pair.first);
}
}
EnableIdleTasks();
message_loop_->RunUntilIdle();
}
private:
DISALLOW_COPY_AND_ASSIGN(RendererSchedulerImplWithMessageLoopTest);
};
TEST_F(RendererSchedulerImplWithMessageLoopTest,
NonNestableIdleTaskDoesntExecuteInNestedLoop) {
std::vector<std::string> order;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&AppendToVectorIdleTestTask, &order, std::string("1")));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&AppendToVectorIdleTestTask, &order, std::string("2")));
std::vector<std::pair<SingleThreadIdleTaskRunner::IdleTask, bool>>
tasks_to_post_from_nested_loop;
tasks_to_post_from_nested_loop.push_back(std::make_pair(
base::Bind(&AppendToVectorIdleTestTask, &order, std::string("3")),
false));
tasks_to_post_from_nested_loop.push_back(std::make_pair(
base::Bind(&AppendToVectorIdleTestTask, &order, std::string("4")), true));
tasks_to_post_from_nested_loop.push_back(std::make_pair(
base::Bind(&AppendToVectorIdleTestTask, &order, std::string("5")), true));
default_task_runner_->PostTask(
FROM_HERE,
base::Bind(
&RendererSchedulerImplWithMessageLoopTest::PostFromNestedRunloop,
base::Unretained(this),
base::Unretained(&tasks_to_post_from_nested_loop)));
EnableIdleTasks();
RunUntilIdle();
// Note we expect task 3 to run last because it's non-nestable.
EXPECT_THAT(order, testing::ElementsAre(std::string("1"), std::string("2"),
std::string("4"), std::string("5"),
std::string("3")));
}
TEST_F(RendererSchedulerImplTest, TestLongIdlePeriod) {
base::TimeTicks expected_deadline =
clock_->NowTicks() + maximum_idle_period_duration();
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
EXPECT_EQ(0, run_count); // Shouldn't run yet as no idle period.
scheduler_->BeginFrameNotExpectedSoon();
RunUntilIdle();
EXPECT_EQ(1, run_count); // Should have run in a long idle time.
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(RendererSchedulerImplTest, TestLongIdlePeriodWithPendingDelayedTask) {
base::TimeDelta pending_task_delay = base::TimeDelta::FromMilliseconds(30);
base::TimeTicks expected_deadline = clock_->NowTicks() + pending_task_delay;
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
default_task_runner_->PostDelayedTask(FROM_HERE, base::Bind(&NullTask),
pending_task_delay);
scheduler_->BeginFrameNotExpectedSoon();
RunUntilIdle();
EXPECT_EQ(1, run_count); // Should have run in a long idle time.
EXPECT_EQ(expected_deadline, deadline_in_task);
}
TEST_F(RendererSchedulerImplTest,
TestLongIdlePeriodWithLatePendingDelayedTask) {
base::TimeDelta pending_task_delay = base::TimeDelta::FromMilliseconds(10);
base::TimeTicks deadline_in_task;
int run_count = 0;
default_task_runner_->PostDelayedTask(FROM_HERE, base::Bind(&NullTask),
pending_task_delay);
// Advance clock until after delayed task was meant to be run.
clock_->Advance(base::TimeDelta::FromMilliseconds(20));
// Post an idle task and BeginFrameNotExpectedSoon to initiate a long idle
// period. Since there is a late pending delayed task this shouldn't actually
// start an idle period.
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
scheduler_->BeginFrameNotExpectedSoon();
RunUntilIdle();
EXPECT_EQ(0, run_count);
// After the delayed task has been run we should trigger an idle period.
clock_->Advance(maximum_idle_period_duration());
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
TEST_F(RendererSchedulerImplTest, TestLongIdlePeriodRepeating) {
mock_task_runner_->SetAutoAdvanceNowToPendingTasks(true);
std::vector<base::TimeTicks> actual_deadlines;
int run_count = 0;
max_idle_task_reposts = 3;
base::TimeTicks clock_before(clock_->NowTicks());
base::TimeDelta idle_task_runtime(base::TimeDelta::FromMilliseconds(10));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&RepostingUpdateClockIdleTestTask,
idle_task_runner_, &run_count, clock_.get(),
idle_task_runtime, &actual_deadlines));
scheduler_->BeginFrameNotExpectedSoon();
RunUntilIdle();
EXPECT_EQ(3, run_count);
EXPECT_THAT(
actual_deadlines,
testing::ElementsAre(
clock_before + maximum_idle_period_duration(),
clock_before + idle_task_runtime + maximum_idle_period_duration(),
clock_before + (2 * idle_task_runtime) +
maximum_idle_period_duration()));
// Check that idle tasks don't run after the idle period ends with a
// new BeginMainFrame.
max_idle_task_reposts = 5;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&RepostingUpdateClockIdleTestTask,
idle_task_runner_, &run_count, clock_.get(),
idle_task_runtime, &actual_deadlines));
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&WillBeginFrameIdleTask,
base::Unretained(scheduler_.get()), clock_.get()));
RunUntilIdle();
EXPECT_EQ(4, run_count);
}
TEST_F(RendererSchedulerImplTest, TestLongIdlePeriodDoesNotWakeScheduler) {
base::TimeTicks deadline_in_task;
int run_count = 0;
// Start a long idle period and get the time it should end.
scheduler_->BeginFrameNotExpectedSoon();
// The scheduler should not run the initiate_next_long_idle_period task if
// there are no idle tasks and no other task woke up the scheduler, thus
// the idle period deadline shouldn't update at the end of the current long
// idle period.
base::TimeTicks idle_period_deadline =
scheduler_->CurrentIdleTaskDeadlineForTesting();
clock_->Advance(maximum_idle_period_duration());
RunUntilIdle();
base::TimeTicks new_idle_period_deadline =
scheduler_->CurrentIdleTaskDeadlineForTesting();
EXPECT_EQ(idle_period_deadline, new_idle_period_deadline);
// Posting a after-wakeup idle task also shouldn't wake the scheduler or
// initiate the next long idle period.
idle_task_runner_->PostIdleTaskAfterWakeup(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
RunUntilIdle();
new_idle_period_deadline = scheduler_->CurrentIdleTaskDeadlineForTesting();
EXPECT_EQ(idle_period_deadline, new_idle_period_deadline);
EXPECT_EQ(0, run_count);
// Running a normal task should initiate a new long idle period though.
default_task_runner_->PostTask(FROM_HERE, base::Bind(&NullTask));
RunUntilIdle();
new_idle_period_deadline = scheduler_->CurrentIdleTaskDeadlineForTesting();
EXPECT_EQ(idle_period_deadline + maximum_idle_period_duration(),
new_idle_period_deadline);
EXPECT_EQ(1, run_count);
}
TEST_F(RendererSchedulerImplTest, TestLongIdlePeriodInTouchStartPolicy) {
base::TimeTicks deadline_in_task;
int run_count = 0;
idle_task_runner_->PostIdleTask(
FROM_HERE, base::Bind(&IdleTestTask, &run_count, &deadline_in_task));
// Observation of touchstart should defer the start of the long idle period.
scheduler_->DidHandleInputEventOnCompositorThread(
FakeInputEvent(blink::WebInputEvent::TouchStart),
RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR);
scheduler_->BeginFrameNotExpectedSoon();
RunUntilIdle();
EXPECT_EQ(0, run_count);
// The long idle period should start after the touchstart policy has finished.
clock_->Advance(priority_escalation_after_input_duration());
RunUntilIdle();
EXPECT_EQ(1, run_count);
}
void TestCanExceedIdleDeadlineIfRequiredTask(RendererScheduler* scheduler,
bool* can_exceed_idle_deadline_out,
int* run_count,
base::TimeTicks deadline) {
*can_exceed_idle_deadline_out = scheduler->CanExceedIdleDeadlineIfRequired();
(*run_count)++;
}
TEST_F(RendererSchedulerImplTest, CanExceedIdleDeadlineIfRequired) {
int run_count = 0;
bool can_exceed_idle_deadline = false;
// Should return false if not in an idle period.
EXPECT_FALSE(scheduler_->CanExceedIdleDeadlineIfRequired());
// Should return false for short idle periods.
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&TestCanExceedIdleDeadlineIfRequiredTask, scheduler_.get(),
&can_exceed_idle_deadline, &run_count));
EnableIdleTasks();
RunUntilIdle();
EXPECT_EQ(1, run_count);
EXPECT_FALSE(can_exceed_idle_deadline);
// Should return false for a long idle period which is shortened due to a
// pending delayed task.
default_task_runner_->PostDelayedTask(FROM_HERE, base::Bind(&NullTask),
base::TimeDelta::FromMilliseconds(10));
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&TestCanExceedIdleDeadlineIfRequiredTask, scheduler_.get(),
&can_exceed_idle_deadline, &run_count));
scheduler_->BeginFrameNotExpectedSoon();
RunUntilIdle();
EXPECT_EQ(2, run_count);
EXPECT_FALSE(can_exceed_idle_deadline);
// Next long idle period will be for the maximum time, so
// CanExceedIdleDeadlineIfRequired should return true.
clock_->Advance(maximum_idle_period_duration());
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&TestCanExceedIdleDeadlineIfRequiredTask, scheduler_.get(),
&can_exceed_idle_deadline, &run_count));
RunUntilIdle();
EXPECT_EQ(3, run_count);
EXPECT_TRUE(can_exceed_idle_deadline);
// Next long idle period will be for the maximum time, so
// CanExceedIdleDeadlineIfRequired should return true.
scheduler_->WillBeginFrame(cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL));
EXPECT_FALSE(scheduler_->CanExceedIdleDeadlineIfRequired());
}
TEST_F(RendererSchedulerImplTest, TestRendererHiddenIdlePeriod) {
int run_count = 0;
max_idle_task_reposts = 2;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&RepostingIdleTestTask, idle_task_runner_, &run_count));
// Renderer should start in visible state.
RunUntilIdle();
EXPECT_EQ(0, run_count);
// When we hide the renderer it should start a max deadline idle period, which
// will run an idle task and then immediately start a new idle period, which
// runs the second idle task.
scheduler_->SetAllRenderWidgetsHidden(true);
RunUntilIdle();
EXPECT_EQ(2, run_count);
// Advance time by amount of time by the maximum amount of time we execute
// idle tasks when hidden (plus some slack) - idle period should have ended.
max_idle_task_reposts = 3;
idle_task_runner_->PostIdleTask(
FROM_HERE,
base::Bind(&RepostingIdleTestTask, idle_task_runner_, &run_count));
clock_->Advance(end_idle_when_hidden_delay() +
base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
EXPECT_EQ(2, run_count);
}
TEST_F(RendererSchedulerImplTest, TimerQueueEnabledByDefault) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "T1 T2");
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("T1"), std::string("T2")));
}
TEST_F(RendererSchedulerImplTest, SuspendAndResumeTimerQueue) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "T1 T2");
scheduler_->SuspendTimerQueue();
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
scheduler_->ResumeTimerQueue();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("T1"), std::string("T2")));
}
TEST_F(RendererSchedulerImplTest, MultipleSuspendsNeedMultipleResumes) {
std::vector<std::string> run_order;
PostTestTasks(&run_order, "T1 T2");
scheduler_->SuspendTimerQueue();
scheduler_->SuspendTimerQueue();
scheduler_->SuspendTimerQueue();
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
scheduler_->ResumeTimerQueue();
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
scheduler_->ResumeTimerQueue();
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
scheduler_->ResumeTimerQueue();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("T1"), std::string("T2")));
}
TEST_F(RendererSchedulerImplTest, UseCaseToString) {
CheckAllUseCaseToString();
}
TEST_F(RendererSchedulerImplTest, MismatchedDidHandleInputEventOnMainThread) {
// This should not DCHECK because there was no corresponding compositor side
// call to DidHandleInputEventOnCompositorThread with
// INPUT_EVENT_ACK_STATE_NOT_CONSUMED. There are legitimate reasons for the
// compositor to not be there and we don't want to make debugging impossible.
scheduler_->DidHandleInputEventOnMainThread(
FakeInputEvent(blink::WebInputEvent::GestureFlingStart));
}
TEST_F(RendererSchedulerImplTest, BeginMainFrameOnCriticalPath) {
ASSERT_FALSE(scheduler_->BeginMainFrameOnCriticalPath());
cc::BeginFrameArgs begin_frame_args = cc::BeginFrameArgs::Create(
BEGINFRAME_FROM_HERE, clock_->NowTicks(), base::TimeTicks(),
base::TimeDelta::FromMilliseconds(1000), cc::BeginFrameArgs::NORMAL);
scheduler_->WillBeginFrame(begin_frame_args);
ASSERT_TRUE(scheduler_->BeginMainFrameOnCriticalPath());
begin_frame_args.on_critical_path = false;
scheduler_->WillBeginFrame(begin_frame_args);
ASSERT_FALSE(scheduler_->BeginMainFrameOnCriticalPath());
}
TEST_F(RendererSchedulerImplTest, ShutdownPreventsPostingOfNewTasks) {
scheduler_->Shutdown();
std::vector<std::string> run_order;
PostTestTasks(&run_order, "D1 C1");
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
}
TEST_F(RendererSchedulerImplTest, TestRendererBackgroundedTimerSuspension) {
scheduler_->SetTimerQueueSuspensionWhenBackgroundedEnabled(true);
std::vector<std::string> run_order;
PostTestTasks(&run_order, "T1 T2");
// The background signal will not immediately suspend the timer queue.
scheduler_->OnRendererBackgrounded();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("T1"), std::string("T2")));
run_order.clear();
PostTestTasks(&run_order, "T3");
RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre(std::string("T3")));
// Advance the time until after the scheduled timer queue suspension.
run_order.clear();
clock_->Advance(suspend_timers_when_backgrounded_delay() +
base::TimeDelta::FromMilliseconds(10));
RunUntilIdle();
ASSERT_TRUE(run_order.empty());
// Timer tasks should be suspended until the foregrounded signal.
PostTestTasks(&run_order, "T4 T5");
RunUntilIdle();
EXPECT_TRUE(run_order.empty());
scheduler_->OnRendererForegrounded();
RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("T4"), std::string("T5")));
// Subsequent timer tasks should fire as usual.
run_order.clear();
PostTestTasks(&run_order, "T6");
RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre(std::string("T6")));
}
TEST_F(RendererSchedulerImplTest,
ExpensiveLoadingTasksNotBlockedTillFirstBeginMainFrame) {
std::vector<std::string> run_order;
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
SimulateExpensiveTasks(loading_task_runner_);
ForceTouchStartToBeExpectedSoon();
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
EXPECT_FALSE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1")));
// Emit a BeginMainFrame, and the loading task should get blocked.
DoMainFrame();
run_order.clear();
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1")));
}
TEST_F(RendererSchedulerImplTest,
ExpensiveLoadingTasksNotBlockedIfNoTouchHandler) {
std::vector<std::string> run_order;
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(false);
DoMainFrame();
SimulateExpensiveTasks(loading_task_runner_);
ForceTouchStartToBeExpectedSoon();
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_FALSE(TouchStartExpectedSoon());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1")));
}
TEST_F(RendererSchedulerImplTest,
ExpensiveLoadingTasksNotBlockedIfNavigationExpected) {
std::vector<std::string> run_order;
DoMainFrame();
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
SimulateExpensiveTasks(loading_task_runner_);
ForceTouchStartToBeExpectedSoon();
scheduler_->AddPendingNavigation();
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1")));
// After the nagigation has been cancelled, the expensive loading tasks should
// get blocked.
scheduler_->RemovePendingNavigation();
run_order.clear();
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1")));
}
TEST_F(
RendererSchedulerImplTest,
ExpensiveLoadingTasksNotBlockedIfNavigationExpected_MultipleNavigations) {
std::vector<std::string> run_order;
DoMainFrame();
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
SimulateExpensiveTasks(loading_task_runner_);
ForceTouchStartToBeExpectedSoon();
scheduler_->AddPendingNavigation();
scheduler_->AddPendingNavigation();
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1")));
run_order.clear();
scheduler_->RemovePendingNavigation();
// Navigation task expected ref count non-zero so expensive tasks still not
// blocked.
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("L1"), std::string("D1")));
run_order.clear();
scheduler_->RemovePendingNavigation();
// Navigation task expected ref count is now zero, the expensive loading tasks
// should get blocked.
PostTestTasks(&run_order, "L1 D1");
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::NONE, CurrentUseCase());
EXPECT_TRUE(HaveSeenABeginMainframe());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(TouchStartExpectedSoon());
EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1")));
}
TEST_F(RendererSchedulerImplTest,
ExpensiveLoadingTasksNotBlockedDuringMainThreadGestures) {
std::vector<std::string> run_order;
SimulateExpensiveTasks(loading_task_runner_);
// Loading tasks should not be disabled during main thread user user
// interactions.
PostTestTasks(&run_order, "C1 L1");
// Trigger main_thread_gesture UseCase
WillBeginMainThreadGestureFrame();
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase());
EXPECT_TRUE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_THAT(run_order,
testing::ElementsAre(std::string("C1"), std::string("L1")));
}
TEST_F(RendererSchedulerImplTest, ModeratelyExpensiveTimer_NotBlocked) {
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
for (int i = 0; i < 20; i++) {
simulate_timer_task_ran_ = false;
compositor_task_runner_->PostTask(
FROM_HERE,
base::Bind(
&RendererSchedulerImplTest::SimulateMainThreadGestureCompositorTask,
base::Unretained(this), base::TimeDelta::FromMilliseconds(4)));
timer_task_runner_->PostTask(
FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateTimerTask,
base::Unretained(this),
base::TimeDelta::FromMilliseconds(10)));
RunUntilIdle();
EXPECT_TRUE(simulate_timer_task_ran_);
EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE,
CurrentUseCase());
EXPECT_FALSE(LoadingTasksSeemExpensive());
EXPECT_FALSE(TimerTasksSeemExpensive());
base::TimeDelta time_till_next_frame =
EstimatedNextFrameBegin() - clock_->NowTicks();
if (time_till_next_frame > base::TimeDelta())
clock_->Advance(time_till_next_frame);
}
}
TEST_F(RendererSchedulerImplTest, ExpensiveTimer_Blocked) {
scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true);
for (int i = 0; i < 20; i++) {
simulate_timer_task_ran_ = false;
compositor_task_runner_->PostTask(
FROM_HERE,
base::Bind(
&RendererSchedulerImplTest::SimulateMainThreadGestureCompositorTask,
base::Unretained(this), base::TimeDelta::FromMilliseconds(8)));
timer_task_runner_->PostTask(
FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateTimerTask,
base::Unretained(this),
base::TimeDelta::FromMilliseconds(10)));
RunUntilIdle();
EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE,
CurrentUseCase());
EXPECT_FALSE(LoadingTasksSeemExpensive());
if (i == 0) {
EXPECT_FALSE(TimerTasksSeemExpensive());
EXPECT_TRUE(simulate_timer_task_ran_);
} else {
EXPECT_TRUE(TimerTasksSeemExpensive());
EXPECT_FALSE(simulate_timer_task_ran_);
}
base::TimeDelta time_till_next_frame =
EstimatedNextFrameBegin() - clock_->NowTicks();
if (time_till_next_frame > base::TimeDelta())
clock_->Advance(time_till_next_frame);
}
}
} // namespace scheduler