// 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 #include "base/callback.h" #include "base/macros.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* vector, std::string value) { vector->push_back(value); } void AppendToVectorIdleTestTask(std::vector* vector, std::string value, base::TimeTicks deadline) { AppendToVectorTestTask(vector, value); } void NullTask() { } void AppendToVectorReentrantTask(base::SingleThreadTaskRunner* task_runner, std::vector* 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* 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::TouchMove), 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; using RendererSchedulerImpl::EstimateLongestJankFreeTaskDuration; RendererSchedulerImplForTest( scoped_refptr 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 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 scheduler) { scheduler_ = std::move(scheduler); 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 DoMainFrameOnCriticalPath() { 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::GestureScrollUpdate), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); 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& 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); } enum class TouchEventPolicy { SEND_TOUCH_START, DONT_SEND_TOUCH_START, }; void SimulateCompositorGestureStart(TouchEventPolicy touch_event_policy) { if (touch_event_policy == TouchEventPolicy::SEND_TOUCH_START) { scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchStart), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); } scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollBegin), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollUpdate), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); } void SimulateMainThreadGestureWithCancelledScroll() { scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchStart), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollBegin), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); } void SimulateMainThreadGestureStart(TouchEventPolicy touch_event_policy, blink::WebInputEvent::Type gesture_type) { if (touch_event_policy == TouchEventPolicy::SEND_TOUCH_START) { scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchStart), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::TouchStart)); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::TouchMove)); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::TouchMove)); } scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(gesture_type), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnMainThread(FakeInputEvent(gesture_type)); } void SimulateMainThreadGestureCompositorTask( base::TimeDelta begin_main_frame_duration) { scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); clock_->Advance(begin_main_frame_duration); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::TouchMove)); scheduler_->DidCommitFrameToCompositor(); } void SimulateMainThreadCompositorTask( base::TimeDelta begin_main_frame_duration) { clock_->Advance(begin_main_frame_duration); scheduler_->DidCommitFrameToCompositor(); simulate_compositor_task_ran_ = true; } bool SimulatedCompositorTaskPending() const { return !simulate_compositor_task_ran_; } 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; } int NavigationTaskExpectedCount() { return scheduler_->MainThreadOnly().navigation_task_expected_count; } // 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* 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); } static base::TimeDelta rails_response_time() { return base::TimeDelta::FromMilliseconds( RendererSchedulerImpl::kRailsResponseTimeMillis); } template static void CallForEachEnumValue(E first, E last, const char* (*function)(E)) { for (E val = first; val < last; val = static_cast(static_cast(val) + 1)) { (*function)(val); } } static void CheckAllUseCaseToString() { CallForEachEnumValue( RendererSchedulerImpl::UseCase::FIRST_USE_CASE, RendererSchedulerImpl::UseCase::USE_CASE_COUNT, &RendererSchedulerImpl::UseCaseToString); } scoped_ptr clock_; // Only one of mock_task_runner_ or message_loop_ will be set. scoped_refptr mock_task_runner_; scoped_ptr message_loop_; scoped_refptr main_task_runner_; scoped_ptr scheduler_; scoped_refptr default_task_runner_; scoped_refptr compositor_task_runner_; scoped_refptr loading_task_runner_; scoped_refptr idle_task_runner_; scoped_refptr timer_task_runner_; bool simulate_timer_task_ran_; bool simulate_compositor_task_ran_; DISALLOW_COPY_AND_ASSIGN(RendererSchedulerImplTest); }; TEST_F(RendererSchedulerImplTest, TestPostDefaultTask) { std::vector 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 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 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 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_WithTouchHandler) { std::vector run_order; PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); EnableIdleTasks(); SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("L1"), std::string("D1"), std::string("D2"), std::string("I1"), std::string("C1"), std::string("C2"))); EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_MainThreadHandlesInput_WithCancelledScroll) { std::vector run_order; PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); EnableIdleTasks(); SimulateMainThreadGestureWithCancelledScroll(); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("C2"), std::string("L1"), std::string("D1"), std::string("D2"), std::string("I1"))); EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_CompositorHandlesInput_LongGestureDuration) { scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); EnableIdleTasks(); SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); base::TimeTicks loop_end_time = clock_->NowTicks() + base::TimeDelta::FromMilliseconds( UserModel::kMedianGestureDurationMillis * 2); // The UseCase::COMPOSITOR_GESTURE usecase initially deprioritizes compositor // tasks (see TestCompositorPolicy_CompositorHandlesInput_WithTouchHandler) // but if the gesture is long enough, compositor tasks get prioritized again. while (clock_->NowTicks() < loop_end_time) { scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchMove), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); clock_->Advance(base::TimeDelta::FromMilliseconds(16)); RunUntilIdle(); } std::vector run_order; PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("C2"), std::string("L1"), std::string("D1"), std::string("D2"))); EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_CompositorHandlesInput_WithoutTouchHandler) { std::vector run_order; PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); EnableIdleTasks(); SimulateCompositorGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("L1"), std::string("D1"), std::string("D2"), std::string("I1"), std::string("C1"), std::string("C2"))); EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_MainThreadHandlesInput_WithTouchHandler) { std::vector run_order; PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); EnableIdleTasks(); SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("C2"), std::string("L1"), std::string("D1"), std::string("D2"), std::string("I1"))); EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart)); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_MainThreadHandlesInput_WithoutTouchHandler) { std::vector run_order; PostTestTasks(&run_order, "L1 I1 D1 C1 D2 C2"); EnableIdleTasks(); SimulateMainThreadGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("C2"), std::string("L1"), std::string("D1"), std::string("D2"), std::string("I1"))); EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::GestureFlingStart)); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicy_DidAnimateForInput) { std::vector run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); scheduler_->DidAnimateForInputOnCompositorThread(); // Note DidAnimateForInputOnCompositorThread does not by itself trigger a // policy update. EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); EnableIdleTasks(); RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"), std::string("D2"), std::string("I1"), std::string("C1"), std::string("C2"))); EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()); } TEST_F(RendererSchedulerImplTest, Navigation_ResetsTaskCostEstimations) { std::vector run_order; SimulateExpensiveTasks(timer_task_runner_); scheduler_->OnNavigationStarted(); PostTestTasks(&run_order, "C1 T1"); SimulateMainThreadGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); scheduler_->DidCommitFrameToCompositor(); // Starts Idle Period RunUntilIdle(); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("T1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveTimersDoRunWhenMainThreadScrolling) { std::vector run_order; scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); SimulateExpensiveTasks(timer_task_runner_); DoMainFrame(); SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); PostTestTasks(&run_order, "C1 T1"); RunUntilIdle(); EXPECT_FALSE(TouchStartExpectedSoon()); EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("T1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveTimersDoRunWhenMainThreadScrolling_AndOnCriticalPath) { std::vector run_order; scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); SimulateExpensiveTasks(timer_task_runner_); DoMainFrameOnCriticalPath(); SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); PostTestTasks(&run_order, "C1 T1"); RunUntilIdle(); EXPECT_FALSE(TouchStartExpectedSoon()); EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("C1"), std::string("T1"))); } TEST_F(RendererSchedulerImplTest, TestTouchstartPolicy_Compositor) { std::vector 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 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 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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 run_order; PostTestTasks(&run_order, "I1 D1 C1 D2 C2"); EnableIdleTasks(); 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) { SimulateMainThreadGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); EnableIdleTasks(); std::vector 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) { SimulateCompositorGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START); EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestCompositorPolicyEnds_MainThreadHandlesInput) { SimulateMainThreadGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); EXPECT_EQ(UseCase::MAIN_THREAD_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); clock_->Advance(base::TimeDelta::FromMilliseconds(1000)); EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); } TEST_F(RendererSchedulerImplTest, TestTouchstartPolicyEndsAfterTimeout) { std::vector 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 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; 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::MAIN_THREAD_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 main thread gesture since the input remains queued. EXPECT_EQ(UseCase::MAIN_THREAD_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_); SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); // 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_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollEnd), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); 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 scroll expected"), 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>* tasks) { base::MessageLoop::ScopedNestableTaskAllower allow(message_loop_.get()); for (std::pair& 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 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> 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 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 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 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 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 run_order; PostTestTasks(&run_order, "D1 C1"); RunUntilIdle(); EXPECT_TRUE(run_order.empty()); } TEST_F(RendererSchedulerImplTest, TestRendererBackgroundedTimerSuspension) { scheduler_->SetTimerQueueSuspensionWhenBackgroundedEnabled(true); std::vector 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 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 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, ExpensiveTimerTaskBlocked_UseCase_NONE_PreviousCompositorGesture) { std::vector run_order; scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); DoMainFrame(); SimulateExpensiveTasks(timer_task_runner_); ForceTouchStartToBeExpectedSoon(); PostTestTasks(&run_order, "T1 D1"); RunUntilIdle(); EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_TRUE(HaveSeenABeginMainframe()); EXPECT_FALSE(LoadingTasksSeemExpensive()); EXPECT_TRUE(TimerTasksSeemExpensive()); EXPECT_TRUE(TouchStartExpectedSoon()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveTimerTaskNotBlocked_UseCase_NONE_PreviousMainThreadGesture) { std::vector run_order; scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); DoMainFrame(); SimulateExpensiveTasks(timer_task_runner_); SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); EXPECT_EQ(UseCase::MAIN_THREAD_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchEnd), RendererScheduler::InputEventState::EVENT_FORWARDED_TO_MAIN_THREAD); scheduler_->DidHandleInputEventOnMainThread( FakeInputEvent(blink::WebInputEvent::TouchEnd)); clock_->Advance(priority_escalation_after_input_duration() * 2); EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); PostTestTasks(&run_order, "T1 D1"); RunUntilIdle(); EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_TRUE(HaveSeenABeginMainframe()); EXPECT_FALSE(LoadingTasksSeemExpensive()); EXPECT_TRUE(TimerTasksSeemExpensive()); EXPECT_TRUE(TouchStartExpectedSoon()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("T1"), std::string("D1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveTimerTaskBlocked_UseCase_COMPOSITOR_GESTURE) { std::vector run_order; scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); DoMainFrame(); SimulateExpensiveTasks(timer_task_runner_); ForceTouchStartToBeExpectedSoon(); scheduler_->DidAnimateForInputOnCompositorThread(); PostTestTasks(&run_order, "T1 D1"); RunUntilIdle(); EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_TRUE(HaveSeenABeginMainframe()); EXPECT_FALSE(LoadingTasksSeemExpensive()); EXPECT_TRUE(TimerTasksSeemExpensive()); EXPECT_TRUE(TouchStartExpectedSoon()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveTimerTaskNotBlockedIfDissalowed_UseCase_COMPOSITOR_GESTURE) { std::vector run_order; scheduler_->SetExpensiveTaskBlockingAllowed(false); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); DoMainFrame(); SimulateExpensiveTasks(timer_task_runner_); ForceTouchStartToBeExpectedSoon(); scheduler_->DidAnimateForInputOnCompositorThread(); PostTestTasks(&run_order, "T1 D1"); RunUntilIdle(); EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_TRUE(HaveSeenABeginMainframe()); EXPECT_FALSE(LoadingTasksSeemExpensive()); EXPECT_TRUE(TimerTasksSeemExpensive()); EXPECT_TRUE(TouchStartExpectedSoon()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("T1"), std::string("D1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveTimerTaskBlocked_EvenIfBeginMainFrameNotExpectedSoon) { std::vector run_order; scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); DoMainFrame(); SimulateExpensiveTasks(timer_task_runner_); ForceTouchStartToBeExpectedSoon(); scheduler_->BeginFrameNotExpectedSoon(); PostTestTasks(&run_order, "T1 D1"); RunUntilIdle(); EXPECT_EQ(UseCase::NONE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_TRUE(HaveSeenABeginMainframe()); EXPECT_FALSE(LoadingTasksSeemExpensive()); EXPECT_TRUE(TimerTasksSeemExpensive()); EXPECT_TRUE(TouchStartExpectedSoon()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveLoadingTasksNotBlockedIfNavigationExpected) { std::vector 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_EQ(1, NavigationTaskExpectedCount()); 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_EQ(0, NavigationTaskExpectedCount()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"))); } TEST_F( RendererSchedulerImplTest, ExpensiveLoadingTasksNotBlockedIfNavigationExpected_MultipleNavigations) { std::vector 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_EQ(2, NavigationTaskExpectedCount()); 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_EQ(1, NavigationTaskExpectedCount()); 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_EQ(0, NavigationTaskExpectedCount()); EXPECT_THAT(run_order, testing::ElementsAre(std::string("D1"))); } TEST_F(RendererSchedulerImplTest, ExpensiveLoadingTasksNotBlockedDuringMainThreadGestures) { std::vector run_order; SimulateExpensiveTasks(loading_task_runner_); // Loading tasks should not be disabled during main thread user interactions. PostTestTasks(&run_order, "C1 L1"); // Trigger main_thread_gesture UseCase SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::GestureScrollBegin); 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); SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::TouchMove); RunUntilIdle(); for (int i = 0; i < 20; i++) { simulate_timer_task_ran_ = false; 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); 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(4))); RunUntilIdle(); EXPECT_TRUE(simulate_timer_task_ran_) << " i = " << i; EXPECT_EQ(RendererScheduler::UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()) << " i = " << i; EXPECT_FALSE(LoadingTasksSeemExpensive()) << " i = " << i; EXPECT_FALSE(TimerTasksSeemExpensive()) << " i = " << i; 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, FourtyMsTimer_NotBlocked_CompositorScrolling) { scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); RunUntilIdle(); for (int i = 0; i < 20; i++) { simulate_timer_task_ran_ = false; 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_->DidAnimateForInputOnCompositorThread(); compositor_task_runner_->PostTask( FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateMainThreadCompositorTask, base::Unretained(this), base::TimeDelta::FromMilliseconds(8))); timer_task_runner_->PostTask( FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateTimerTask, base::Unretained(this), base::TimeDelta::FromMilliseconds(40))); RunUntilIdle(); EXPECT_TRUE(simulate_timer_task_ran_) << " i = " << i; EXPECT_EQ(RendererScheduler::UseCase::COMPOSITOR_GESTURE, CurrentUseCase()) << " i = " << i; EXPECT_FALSE(LoadingTasksSeemExpensive()) << " i = " << i; EXPECT_FALSE(TimerTasksSeemExpensive()) << " i = " << i; 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_NotBlocked_UseCase_MAIN_THREAD_GESTURE) { scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); SimulateMainThreadGestureStart(TouchEventPolicy::SEND_TOUCH_START, blink::WebInputEvent::TouchMove); RunUntilIdle(); for (int i = 0; i < 20; i++) { simulate_timer_task_ran_ = false; 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); 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()) << " i = " << i; EXPECT_FALSE(LoadingTasksSeemExpensive()) << " i = " << i; if (i == 0) { EXPECT_FALSE(TimerTasksSeemExpensive()) << " i = " << i; } else { EXPECT_TRUE(TimerTasksSeemExpensive()) << " i = " << i; } EXPECT_TRUE(simulate_timer_task_ran_) << " i = " << i; 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, EstimateLongestJankFreeTaskDuration_UseCase_NONE) { EXPECT_EQ(UseCase::NONE, CurrentUseCase()); EXPECT_EQ(rails_response_time(), scheduler_->EstimateLongestJankFreeTaskDuration()); } TEST_F(RendererSchedulerImplTest, EstimateLongestJankFreeTaskDuration_UseCase_COMPOSITOR_GESTURE) { SimulateCompositorGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START); EXPECT_EQ(UseCase::COMPOSITOR_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_EQ(rails_response_time(), scheduler_->EstimateLongestJankFreeTaskDuration()); } // TODO(alexclarke): Reenable once we've reinstaed the Loading UseCase. TEST_F(RendererSchedulerImplTest, DISABLED_EstimateLongestJankFreeTaskDuration_UseCase_) { scheduler_->OnNavigationStarted(); EXPECT_EQ(UseCase::LOADING, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_EQ(rails_response_time(), scheduler_->EstimateLongestJankFreeTaskDuration()); } TEST_F(RendererSchedulerImplTest, EstimateLongestJankFreeTaskDuration_UseCase_MAIN_THREAD_GESTURE) { 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); compositor_task_runner_->PostTask( FROM_HERE, base::Bind( &RendererSchedulerImplTest::SimulateMainThreadGestureCompositorTask, base::Unretained(this), base::TimeDelta::FromMilliseconds(5))); RunUntilIdle(); EXPECT_EQ(UseCase::MAIN_THREAD_GESTURE, CurrentUseCase()); // 16ms frame - 5ms compositor work = 11ms for other stuff. EXPECT_EQ(base::TimeDelta::FromMilliseconds(11), scheduler_->EstimateLongestJankFreeTaskDuration()); } TEST_F(RendererSchedulerImplTest, EstimateLongestJankFreeTaskDuration_UseCase_SYNCHRONIZED_GESTURE) { SimulateCompositorGestureStart(TouchEventPolicy::DONT_SEND_TOUCH_START); 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); compositor_task_runner_->PostTask( FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateMainThreadCompositorTask, base::Unretained(this), base::TimeDelta::FromMilliseconds(5))); RunUntilIdle(); EXPECT_EQ(UseCase::SYNCHRONIZED_GESTURE, CurrentUseCase()); // 16ms frame - 5ms compositor work = 11ms for other stuff. EXPECT_EQ(base::TimeDelta::FromMilliseconds(11), scheduler_->EstimateLongestJankFreeTaskDuration()); } class WebViewSchedulerImplForTest : public WebViewSchedulerImpl { public: WebViewSchedulerImplForTest(RendererSchedulerImpl* scheduler) : WebViewSchedulerImpl(nullptr, scheduler, false) {} ~WebViewSchedulerImplForTest() override {} void AddConsoleWarning(const std::string& message) override { console_warnings_.push_back(message); } const std::vector& console_warnings() const { return console_warnings_; } private: std::vector console_warnings_; DISALLOW_COPY_AND_ASSIGN(WebViewSchedulerImplForTest); }; TEST_F(RendererSchedulerImplTest, BlockedTimerNotification) { // Make sure we see one (and just one) console warning about an expensive // timer being deferred. WebViewSchedulerImplForTest web_view_scheduler(scheduler_.get()); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); DoMainFrame(); SimulateExpensiveTasks(timer_task_runner_); SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); ForceTouchStartToBeExpectedSoon(); std::vector run_order; PostTestTasks(&run_order, "T1 T2"); RunUntilIdle(); EXPECT_EQ(0u, run_order.size()); EXPECT_EQ(1u, web_view_scheduler.console_warnings().size()); EXPECT_NE(std::string::npos, web_view_scheduler.console_warnings()[0].find("crbug.com/574343")); } namespace { void SlowCountingTask(size_t* count, base::SimpleTestTickClock* clock, int task_duration, scoped_refptr timer_queue) { clock->Advance(base::TimeDelta::FromMilliseconds(task_duration)); if (++(*count) < 500) { timer_queue->PostTask(FROM_HERE, base::Bind(SlowCountingTask, count, clock, task_duration, timer_queue)); } } } TEST_F(RendererSchedulerImplTest, SYNCHRONIZED_GESTURE_TimerTaskThrottling_task_expensive) { SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); size_t count = 0; // With the compositor task taking 10ms, there is not enough time to run this // 7ms timer task in the 16ms frame. scheduler_->TimerTaskRunner()->PostTask( FROM_HERE, base::Bind(SlowCountingTask, &count, clock_.get(), 7, scheduler_->TimerTaskRunner())); for (int i = 0; i < 1000; i++) { 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); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollUpdate), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); simulate_compositor_task_ran_ = false; compositor_task_runner_->PostTask( FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateMainThreadCompositorTask, base::Unretained(this), base::TimeDelta::FromMilliseconds(10))); mock_task_runner_->RunTasksWhile( base::Bind(&RendererSchedulerImplTest::SimulatedCompositorTaskPending, base::Unretained(this))); EXPECT_EQ(UseCase::SYNCHRONIZED_GESTURE, CurrentUseCase()) << "i = " << i; EXPECT_TRUE(scheduler_->TimerTaskRunner()->IsQueueEnabled()) << "i = " << i; } // Task is throttled but not completely blocked. EXPECT_EQ(13u, count); } TEST_F(RendererSchedulerImplTest, SYNCHRONIZED_GESTURE_TimerTaskThrottling_task_not_expensive) { SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); size_t count = 0; // With the compositor task taking 10ms, there is enough time to run this 6ms // timer task in the 16ms frame. scheduler_->TimerTaskRunner()->PostTask( FROM_HERE, base::Bind(SlowCountingTask, &count, clock_.get(), 6, scheduler_->TimerTaskRunner())); for (int i = 0; i < 1000; i++) { 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); scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollUpdate), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); simulate_compositor_task_ran_ = false; compositor_task_runner_->PostTask( FROM_HERE, base::Bind(&RendererSchedulerImplTest::SimulateMainThreadCompositorTask, base::Unretained(this), base::TimeDelta::FromMilliseconds(10))); mock_task_runner_->RunTasksWhile( base::Bind(&RendererSchedulerImplTest::SimulatedCompositorTaskPending, base::Unretained(this))); EXPECT_EQ(UseCase::SYNCHRONIZED_GESTURE, CurrentUseCase()) << "i = " << i; EXPECT_TRUE(scheduler_->TimerTaskRunner()->IsQueueEnabled()) << "i = " << i; } // Task is not throttled. EXPECT_EQ(500u, count); } TEST_F(RendererSchedulerImplTest, ExpensiveTimerTaskBlocked_SYNCHRONIZED_GESTURE_TouchStartExpected) { SimulateCompositorGestureStart(TouchEventPolicy::SEND_TOUCH_START); SimulateExpensiveTasks(timer_task_runner_); scheduler_->SetHasVisibleRenderWidgetWithTouchHandler(true); ForceTouchStartToBeExpectedSoon(); // Bump us into SYNCHRONIZED_GESTURE. scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::GestureScrollUpdate), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); 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); EXPECT_EQ(UseCase::SYNCHRONIZED_GESTURE, ForceUpdatePolicyAndGetCurrentUseCase()); EXPECT_TRUE(TimerTasksSeemExpensive()); EXPECT_TRUE(TouchStartExpectedSoon()); EXPECT_FALSE(scheduler_->TimerTaskRunner()->IsQueueEnabled()); } TEST_F(RendererSchedulerImplTest, DenyLongIdleDuringTouchStart) { scheduler_->DidHandleInputEventOnCompositorThread( FakeInputEvent(blink::WebInputEvent::TouchStart), RendererScheduler::InputEventState::EVENT_CONSUMED_BY_COMPOSITOR); EXPECT_EQ(UseCase::TOUCHSTART, ForceUpdatePolicyAndGetCurrentUseCase()); // First check that long idle is denied during the TOUCHSTART use case. IdleHelper::Delegate* idle_delegate = scheduler_.get(); base::TimeTicks now; base::TimeDelta next_time_to_check; EXPECT_FALSE(idle_delegate->CanEnterLongIdlePeriod(now, &next_time_to_check)); EXPECT_GE(next_time_to_check, base::TimeDelta()); // Check again at a time past the TOUCHSTART expiration. We should still get a // non-negative delay to when to check again. now += base::TimeDelta::FromMilliseconds(500); EXPECT_FALSE(idle_delegate->CanEnterLongIdlePeriod(now, &next_time_to_check)); EXPECT_GE(next_time_to_check, base::TimeDelta()); } } // namespace scheduler