blob: 1d66fc7f7b046697f9156612d00af7849ce654f9 [file] [log] [blame]
// Copyright 2015 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 "platform/scheduler/main_thread/page_scheduler_impl.h"
#include <memory>
#include "base/callback.h"
#include "base/location.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_tick_clock.h"
#include "components/viz/test/ordered_simple_task_runner.h"
#include "platform/scheduler/child/task_runner_impl.h"
#include "platform/scheduler/main_thread/frame_scheduler_impl.h"
#include "platform/scheduler/main_thread/main_thread_scheduler.h"
#include "platform/scheduler/test/task_queue_manager_for_test.h"
#include "platform/testing/runtime_enabled_features_test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
using VirtualTimePolicy = blink::PageScheduler::VirtualTimePolicy;
namespace blink {
namespace scheduler {
// To avoid symbol collisions in jumbo builds.
namespace page_scheduler_impl_unittest {
class PageSchedulerImplTest : public testing::Test {
public:
PageSchedulerImplTest() = default;
~PageSchedulerImplTest() override = default;
void SetUp() override {
clock_.Advance(base::TimeDelta::FromMicroseconds(5000));
mock_task_runner_ =
base::MakeRefCounted<cc::OrderedSimpleTaskRunner>(&clock_, true);
scheduler_.reset(new RendererSchedulerImpl(
TaskQueueManagerForTest::Create(nullptr, mock_task_runner_, &clock_),
base::nullopt));
page_scheduler_.reset(new PageSchedulerImpl(
nullptr, scheduler_.get(), DisableBackgroundTimerThrottling()));
frame_scheduler_ = page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
}
void TearDown() override {
frame_scheduler_.reset();
page_scheduler_.reset();
scheduler_->Shutdown();
scheduler_.reset();
}
virtual bool DisableBackgroundTimerThrottling() const { return false; }
protected:
static scoped_refptr<TaskQueue> ThrottleableTaskQueueForScheduler(
FrameSchedulerImpl* scheduler) {
return scheduler->ThrottleableTaskQueue();
}
scoped_refptr<base::SingleThreadTaskRunner> ThrottleableTaskRunner() {
return TaskRunnerImpl::Create(ThrottleableTaskQueue(),
TaskType::kInternalTest);
}
scoped_refptr<base::SingleThreadTaskRunner> LoadingTaskRunner() {
return TaskRunnerImpl::Create(LoadingTaskQueue(), TaskType::kInternalTest);
}
scoped_refptr<TaskQueue> ThrottleableTaskQueue() {
return frame_scheduler_->ThrottleableTaskQueue();
}
scoped_refptr<TaskQueue> LoadingTaskQueue() {
return frame_scheduler_->LoadingTaskQueue();
}
base::SimpleTestTickClock clock_;
scoped_refptr<cc::OrderedSimpleTaskRunner> mock_task_runner_;
std::unique_ptr<RendererSchedulerImpl> scheduler_;
std::unique_ptr<PageSchedulerImpl> page_scheduler_;
std::unique_ptr<FrameSchedulerImpl> frame_scheduler_;
};
TEST_F(PageSchedulerImplTest, TestDestructionOfFrameSchedulersBefore) {
std::unique_ptr<blink::FrameScheduler> frame1(
page_scheduler_->CreateFrameScheduler(
nullptr, FrameScheduler::FrameType::kSubframe));
std::unique_ptr<blink::FrameScheduler> frame2(
page_scheduler_->CreateFrameScheduler(
nullptr, FrameScheduler::FrameType::kSubframe));
}
TEST_F(PageSchedulerImplTest, TestDestructionOfFrameSchedulersAfter) {
std::unique_ptr<blink::FrameScheduler> frame1(
page_scheduler_->CreateFrameScheduler(
nullptr, FrameScheduler::FrameType::kSubframe));
std::unique_ptr<blink::FrameScheduler> frame2(
page_scheduler_->CreateFrameScheduler(
nullptr, FrameScheduler::FrameType::kSubframe));
page_scheduler_.reset();
}
namespace {
void RunRepeatingTask(scoped_refptr<TaskQueue>, int* run_count);
base::OnceClosure MakeRepeatingTask(scoped_refptr<TaskQueue> task_queue,
int* run_count) {
return base::BindOnce(&RunRepeatingTask, std::move(task_queue),
base::Unretained(run_count));
}
void RunRepeatingTask(scoped_refptr<TaskQueue> task_queue, int* run_count) {
++*run_count;
TaskQueue* task_queue_ptr = task_queue.get();
task_queue_ptr->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(std::move(task_queue_ptr), run_count),
base::TimeDelta::FromMilliseconds(1));
}
} // namespace
TEST_F(PageSchedulerImplTest, RepeatingTimer_PageInForeground) {
page_scheduler_->SetPageVisible(true);
int run_count = 0;
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(ThrottleableTaskQueue(), &run_count),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count);
}
TEST_F(PageSchedulerImplTest, RepeatingTimer_PageInBackgroundThenForeground) {
page_scheduler_->SetPageVisible(false);
int run_count = 0;
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(ThrottleableTaskQueue(), &run_count),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1, run_count);
// Make sure there's no delay in throttling being removed for pages that have
// become visible.
page_scheduler_->SetPageVisible(true);
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1001, run_count); // Note we end up running 1001 here because the
// task was posted while throttled with a delay of 1ms so the first task was
// due to run before the 1s period started.
}
TEST_F(PageSchedulerImplTest, RepeatingLoadingTask_PageInBackground) {
page_scheduler_->SetPageVisible(false);
int run_count = 0;
LoadingTaskQueue()->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(LoadingTaskQueue(), &run_count),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count); // Loading tasks should not be throttled
}
TEST_F(PageSchedulerImplTest, RepeatingTimers_OneBackgroundOneForeground) {
std::unique_ptr<PageSchedulerImpl> page_scheduler2(
new PageSchedulerImpl(nullptr, scheduler_.get(), false));
std::unique_ptr<FrameSchedulerImpl> frame_scheduler2 =
page_scheduler2->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
page_scheduler_->SetPageVisible(true);
page_scheduler2->SetPageVisible(false);
int run_count1 = 0;
int run_count2 = 0;
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(ThrottleableTaskQueue(), &run_count1),
base::TimeDelta::FromMilliseconds(1));
ThrottleableTaskQueueForScheduler(frame_scheduler2.get())
->PostDelayedTask(FROM_HERE,
MakeRepeatingTask(ThrottleableTaskQueueForScheduler(
frame_scheduler2.get()),
&run_count2),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count1);
EXPECT_EQ(1, run_count2);
}
namespace {
void RunVirtualTimeRecorderTask(
base::SimpleTestTickClock* clock,
RendererSchedulerImpl* scheduler,
std::vector<base::TimeTicks>* out_real_times,
std::vector<base::TimeTicks>* out_virtual_times) {
out_real_times->push_back(clock->NowTicks());
out_virtual_times->push_back(scheduler->GetVirtualTimeDomain()->Now());
}
base::OnceClosure MakeVirtualTimeRecorderTask(
base::SimpleTestTickClock* clock,
RendererSchedulerImpl* scheduler,
std::vector<base::TimeTicks>* out_real_times,
std::vector<base::TimeTicks>* out_virtual_times) {
return WTF::Bind(&RunVirtualTimeRecorderTask, WTF::Unretained(clock),
WTF::Unretained(scheduler), WTF::Unretained(out_real_times),
WTF::Unretained(out_virtual_times));
}
} // namespace
TEST_F(PageSchedulerImplTest, VirtualTime_TimerFastForwarding) {
std::vector<base::TimeTicks> real_times;
std::vector<base::TimeTicks> virtual_times;
page_scheduler_->EnableVirtualTime();
base::TimeTicks initial_real_time = scheduler_->tick_clock()->NowTicks();
base::TimeTicks initial_virtual_time =
scheduler_->GetVirtualTimeDomain()->Now();
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(2));
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(20));
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(200));
mock_task_runner_->RunUntilIdle();
EXPECT_THAT(real_times, ElementsAre(initial_real_time, initial_real_time,
initial_real_time));
EXPECT_THAT(
virtual_times,
ElementsAre(
initial_virtual_time + base::TimeDelta::FromMilliseconds(2),
initial_virtual_time + base::TimeDelta::FromMilliseconds(20),
initial_virtual_time + base::TimeDelta::FromMilliseconds(200)));
}
TEST_F(PageSchedulerImplTest, VirtualTime_LoadingTaskFastForwarding) {
std::vector<base::TimeTicks> real_times;
std::vector<base::TimeTicks> virtual_times;
page_scheduler_->EnableVirtualTime();
base::TimeTicks initial_real_time = scheduler_->tick_clock()->NowTicks();
base::TimeTicks initial_virtual_time =
scheduler_->GetVirtualTimeDomain()->Now();
LoadingTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(2));
LoadingTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(20));
LoadingTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(200));
mock_task_runner_->RunUntilIdle();
EXPECT_THAT(real_times, ElementsAre(initial_real_time, initial_real_time,
initial_real_time));
EXPECT_THAT(
virtual_times,
ElementsAre(
initial_virtual_time + base::TimeDelta::FromMilliseconds(2),
initial_virtual_time + base::TimeDelta::FromMilliseconds(20),
initial_virtual_time + base::TimeDelta::FromMilliseconds(200)));
}
TEST_F(PageSchedulerImplTest,
RepeatingTimer_PageInBackground_MeansNothingForVirtualTime) {
page_scheduler_->EnableVirtualTime();
page_scheduler_->SetPageVisible(false);
scheduler_->GetSchedulerHelperForTesting()->SetWorkBatchSizeForTesting(1);
base::TimeTicks initial_real_time = scheduler_->tick_clock()->NowTicks();
int run_count = 0;
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(ThrottleableTaskQueue(), &run_count),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunTasksWhile(mock_task_runner_->TaskRunCountBelow(2000));
// Virtual time means page visibility is ignored.
EXPECT_EQ(1999, run_count);
// The global tick clock has not moved, yet we ran a large number of "delayed"
// tasks despite calling setPageVisible(false).
EXPECT_EQ(initial_real_time, scheduler_->tick_clock()->NowTicks());
}
namespace {
void RunOrderTask(int index, std::vector<int>* out_run_order) {
out_run_order->push_back(index);
}
void DelayedRunOrderTask(int index,
scoped_refptr<TaskQueue> task_queue,
std::vector<int>* out_run_order) {
out_run_order->push_back(index);
task_queue->PostTask(FROM_HERE,
base::BindOnce(&RunOrderTask, index + 1,
base::Unretained(out_run_order)));
}
} // namespace
TEST_F(PageSchedulerImplTest, VirtualTime_NotAllowedToAdvance) {
std::vector<int> run_order;
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
page_scheduler_->EnableVirtualTime();
ThrottleableTaskQueue()->PostTask(
FROM_HERE,
base::BindOnce(&RunOrderTask, 0, base::Unretained(&run_order)));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DelayedRunOrderTask, 1, ThrottleableTaskQueue(),
base::Unretained(&run_order)),
base::TimeDelta::FromMilliseconds(2));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DelayedRunOrderTask, 3, ThrottleableTaskQueue(),
base::Unretained(&run_order)),
base::TimeDelta::FromMilliseconds(4));
mock_task_runner_->RunUntilIdle();
// No timer tasks are allowed to run.
EXPECT_THAT(run_order, ElementsAre());
}
TEST_F(PageSchedulerImplTest, VirtualTime_AllowedToAdvance) {
std::vector<int> run_order;
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kAdvance);
page_scheduler_->EnableVirtualTime();
ThrottleableTaskQueue()->PostTask(
FROM_HERE,
base::BindOnce(&RunOrderTask, 0, base::Unretained(&run_order)));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DelayedRunOrderTask, 1, ThrottleableTaskQueue(),
base::Unretained(&run_order)),
base::TimeDelta::FromMilliseconds(2));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DelayedRunOrderTask, 3, ThrottleableTaskQueue(),
base::Unretained(&run_order)),
base::TimeDelta::FromMilliseconds(4));
mock_task_runner_->RunUntilIdle();
EXPECT_THAT(run_order, ElementsAre(0, 1, 2, 3, 4));
}
class PageSchedulerImplTestWithDisabledBackgroundTimerThrottling
: public PageSchedulerImplTest {
public:
PageSchedulerImplTestWithDisabledBackgroundTimerThrottling() = default;
~PageSchedulerImplTestWithDisabledBackgroundTimerThrottling() override =
default;
bool DisableBackgroundTimerThrottling() const override { return true; }
};
TEST_F(PageSchedulerImplTestWithDisabledBackgroundTimerThrottling,
RepeatingTimer_PageInBackground) {
page_scheduler_->SetPageVisible(false);
int run_count = 0;
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(ThrottleableTaskQueue(), &run_count),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count);
}
TEST_F(PageSchedulerImplTest, VirtualTimeSettings_NewFrameScheduler) {
std::vector<int> run_order;
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
page_scheduler_->EnableVirtualTime();
std::unique_ptr<FrameSchedulerImpl> frame_scheduler =
page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
ThrottleableTaskQueueForScheduler(frame_scheduler.get())
->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RunOrderTask, 1, base::Unretained(&run_order)),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunUntilIdle();
EXPECT_TRUE(run_order.empty());
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kAdvance);
mock_task_runner_->RunUntilIdle();
EXPECT_THAT(run_order, ElementsAre(1));
}
namespace {
template <typename T>
base::OnceClosure MakeDeletionTask(T* obj) {
return base::BindOnce([](T* obj) { delete obj; }, base::Unretained(obj));
}
} // namespace
TEST_F(PageSchedulerImplTest, DeleteFrameSchedulers_InTask) {
for (int i = 0; i < 10; i++) {
FrameSchedulerImpl* frame_scheduler =
page_scheduler_
->CreateFrameSchedulerImpl(nullptr,
FrameScheduler::FrameType::kSubframe)
.release();
ThrottleableTaskQueueForScheduler(frame_scheduler)
->PostDelayedTask(FROM_HERE, MakeDeletionTask(frame_scheduler),
base::TimeDelta::FromMilliseconds(1));
}
mock_task_runner_->RunUntilIdle();
}
TEST_F(PageSchedulerImplTest, DeletePageScheduler_InTask) {
ThrottleableTaskQueue()->PostTask(
FROM_HERE, MakeDeletionTask(page_scheduler_.release()));
mock_task_runner_->RunUntilIdle();
}
TEST_F(PageSchedulerImplTest, DeleteThrottledQueue_InTask) {
page_scheduler_->SetPageVisible(false);
FrameSchedulerImpl* frame_scheduler =
page_scheduler_
->CreateFrameSchedulerImpl(nullptr,
FrameScheduler::FrameType::kSubframe)
.release();
scoped_refptr<TaskQueue> timer_task_queue =
ThrottleableTaskQueueForScheduler(frame_scheduler);
int run_count = 0;
timer_task_queue->PostDelayedTask(
FROM_HERE, MakeRepeatingTask(timer_task_queue, &run_count),
base::TimeDelta::FromMilliseconds(1));
// Note this will run at time t = 10s since we start at time t = 5000us.
// However, we still should run all tasks after frame scheduler deletion.
timer_task_queue->PostDelayedTask(FROM_HERE,
MakeDeletionTask(frame_scheduler),
base::TimeDelta::FromMilliseconds(9990));
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(100));
EXPECT_EQ(90015, run_count);
}
TEST_F(PageSchedulerImplTest, VirtualTimePauseCount_DETERMINISTIC_LOADING) {
page_scheduler_->SetVirtualTimePolicy(
VirtualTimePolicy::kDeterministicLoading);
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->IncrementVirtualTimePauseCount();
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->IncrementVirtualTimePauseCount();
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->DecrementVirtualTimePauseCount();
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->DecrementVirtualTimePauseCount();
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->IncrementVirtualTimePauseCount();
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->DecrementVirtualTimePauseCount();
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
}
TEST_F(PageSchedulerImplTest,
WebScopedVirtualTimePauser_DETERMINISTIC_LOADING) {
page_scheduler_->SetVirtualTimePolicy(
VirtualTimePolicy::kDeterministicLoading);
std::unique_ptr<FrameSchedulerImpl> frame_scheduler =
page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
{
WebScopedVirtualTimePauser virtual_time_pauser =
frame_scheduler->CreateWebScopedVirtualTimePauser(
WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant);
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
virtual_time_pauser.PauseVirtualTime(true);
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
virtual_time_pauser.PauseVirtualTime(false);
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
virtual_time_pauser.PauseVirtualTime(true);
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
}
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
}
namespace {
void RecordVirtualTime(RendererSchedulerImpl* scheduler, base::TimeTicks* out) {
*out = scheduler->GetVirtualTimeDomain()->Now();
}
void PauseAndUnpauseVirtualTime(RendererSchedulerImpl* scheduler,
FrameSchedulerImpl* frame_scheduler,
base::TimeTicks* paused,
base::TimeTicks* unpaused) {
*paused = scheduler->GetVirtualTimeDomain()->Now();
{
WebScopedVirtualTimePauser virtual_time_pauser =
frame_scheduler->CreateWebScopedVirtualTimePauser(
WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant);
virtual_time_pauser.PauseVirtualTime(true);
}
*unpaused = scheduler->GetVirtualTimeDomain()->Now();
}
} // namespace
TEST_F(PageSchedulerImplTest,
WebScopedVirtualTimePauserWithInterleavedTasks_DETERMINISTIC_LOADING) {
// Make task queue manager ask the virtual time domain for the next task delay
// after each task.
scheduler_->GetSchedulerHelperForTesting()->SetWorkBatchSizeForTesting(1);
page_scheduler_->EnableVirtualTime();
page_scheduler_->SetVirtualTimePolicy(
VirtualTimePolicy::kDeterministicLoading);
base::TimeTicks initial_virtual_time =
scheduler_->GetVirtualTimeDomain()->Now();
base::TimeTicks time_paused;
base::TimeTicks time_unpaused;
base::TimeTicks time_second_task;
std::unique_ptr<FrameSchedulerImpl> frame_scheduler =
page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
// Pauses and unpauses virtual time, thereby advancing virtual time by an
// additional 10ms due to WebScopedVirtualTimePauser's delay.
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
WTF::Bind(&PauseAndUnpauseVirtualTime, WTF::Unretained(scheduler_.get()),
WTF::Unretained(frame_scheduler.get()),
WTF::Unretained(&time_paused), WTF::Unretained(&time_unpaused)),
base::TimeDelta::FromMilliseconds(3));
// Will run after the first task has advanced virtual time past 5ms.
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
WTF::Bind(&RecordVirtualTime, WTF::Unretained(scheduler_.get()),
WTF::Unretained(&time_second_task)),
base::TimeDelta::FromMilliseconds(5));
mock_task_runner_->RunUntilIdle();
EXPECT_EQ(time_paused,
initial_virtual_time + base::TimeDelta::FromMilliseconds(3));
EXPECT_EQ(time_unpaused,
initial_virtual_time + base::TimeDelta::FromMilliseconds(13));
EXPECT_EQ(time_second_task,
initial_virtual_time + base::TimeDelta::FromMilliseconds(13));
}
TEST_F(PageSchedulerImplTest,
MultipleWebScopedVirtualTimePausers_DETERMINISTIC_LOADING) {
page_scheduler_->SetVirtualTimePolicy(
VirtualTimePolicy::kDeterministicLoading);
std::unique_ptr<FrameSchedulerImpl> frame_scheduler =
page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
WebScopedVirtualTimePauser virtual_time_pauser1 =
frame_scheduler->CreateWebScopedVirtualTimePauser(
WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant);
WebScopedVirtualTimePauser virtual_time_pauser2 =
frame_scheduler->CreateWebScopedVirtualTimePauser(
WebScopedVirtualTimePauser::VirtualTaskDuration::kNonInstant);
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
virtual_time_pauser1.PauseVirtualTime(true);
virtual_time_pauser2.PauseVirtualTime(true);
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
virtual_time_pauser2.PauseVirtualTime(false);
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
virtual_time_pauser1.PauseVirtualTime(false);
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
}
TEST_F(PageSchedulerImplTest, NestedMessageLoop_DETERMINISTIC_LOADING) {
page_scheduler_->SetVirtualTimePolicy(
VirtualTimePolicy::kDeterministicLoading);
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->OnBeginNestedRunLoop();
EXPECT_FALSE(scheduler_->VirtualTimeAllowedToAdvance());
scheduler_->OnExitNestedRunLoop();
EXPECT_TRUE(scheduler_->VirtualTimeAllowedToAdvance());
}
TEST_F(PageSchedulerImplTest, PauseTimersWhileVirtualTimeIsPaused) {
std::vector<int> run_order;
std::unique_ptr<FrameSchedulerImpl> frame_scheduler =
page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
page_scheduler_->EnableVirtualTime();
ThrottleableTaskQueueForScheduler(frame_scheduler.get())
->PostTask(FROM_HERE, base::BindOnce(&RunOrderTask, 1,
base::Unretained(&run_order)));
mock_task_runner_->RunUntilIdle();
EXPECT_TRUE(run_order.empty());
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kAdvance);
mock_task_runner_->RunUntilIdle();
EXPECT_THAT(run_order, ElementsAre(1));
}
TEST_F(PageSchedulerImplTest, VirtualTimeBudgetExhaustedCallback) {
std::vector<base::TimeTicks> real_times;
std::vector<base::TimeTicks> virtual_times;
page_scheduler_->EnableVirtualTime();
base::TimeTicks initial_real_time = scheduler_->tick_clock()->NowTicks();
base::TimeTicks initial_virtual_time =
scheduler_->GetVirtualTimeDomain()->Now();
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(1));
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(2));
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(5));
ThrottleableTaskRunner()->PostDelayedTask(
FROM_HERE,
MakeVirtualTimeRecorderTask(&clock_, scheduler_.get(), &real_times,
&virtual_times),
base::TimeDelta::FromMilliseconds(7));
page_scheduler_->GrantVirtualTimeBudget(
base::TimeDelta::FromMilliseconds(5),
WTF::Bind(
[](PageScheduler* scheduler) {
scheduler->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
},
WTF::Unretained(page_scheduler_.get())));
mock_task_runner_->RunUntilIdle();
// The timer that is scheduled for the exact point in time when virtual time
// expires will not run.
EXPECT_THAT(real_times, ElementsAre(initial_real_time, initial_real_time,
initial_real_time));
EXPECT_THAT(
virtual_times,
ElementsAre(initial_virtual_time + base::TimeDelta::FromMilliseconds(1),
initial_virtual_time + base::TimeDelta::FromMilliseconds(2),
initial_virtual_time + base::TimeDelta::FromMilliseconds(5)));
}
namespace {
class MockObserver : public PageScheduler::VirtualTimeObserver {
public:
~MockObserver() override = default;
void OnVirtualTimeAdvanced(base::TimeDelta virtual_time_offset) override {
virtual_time_log_.push_back(base::StringPrintf(
"Advanced to %dms",
static_cast<int>(virtual_time_offset.InMilliseconds())));
}
void OnVirtualTimePaused(base::TimeDelta virtual_time_offset) override {
virtual_time_log_.push_back(base::StringPrintf(
"Paused at %dms",
static_cast<int>(virtual_time_offset.InMilliseconds())));
}
const std::vector<std::string>& virtual_time_log() const {
return virtual_time_log_;
}
private:
std::vector<std::string> virtual_time_log_;
};
void NopTask() {}
} // namespace
TEST_F(PageSchedulerImplTest, VirtualTimeObserver) {
MockObserver mock_observer;
page_scheduler_->AddVirtualTimeObserver(&mock_observer);
page_scheduler_->EnableVirtualTime();
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&NopTask),
base::TimeDelta::FromMilliseconds(200));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&NopTask),
base::TimeDelta::FromMilliseconds(20));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&NopTask),
base::TimeDelta::FromMilliseconds(2));
page_scheduler_->GrantVirtualTimeBudget(
base::TimeDelta::FromMilliseconds(1000),
WTF::Bind(
[](PageScheduler* scheduler) {
scheduler->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
},
WTF::Unretained(page_scheduler_.get())));
mock_task_runner_->RunUntilIdle();
EXPECT_THAT(
mock_observer.virtual_time_log(),
ElementsAre("Advanced to 2ms", "Advanced to 20ms", "Advanced to 200ms",
"Advanced to 1000ms", "Paused at 1000ms"));
page_scheduler_->RemoveVirtualTimeObserver(&mock_observer);
}
namespace {
void RepostingTask(scoped_refptr<TaskQueue> task_queue,
int max_count,
int* count) {
if (++(*count) >= max_count)
return;
task_queue->PostTask(FROM_HERE,
base::BindOnce(&RepostingTask, task_queue, max_count,
base::Unretained(count)));
}
void DelayedTask(int* count_in, int* count_out) {
*count_out = *count_in;
}
} // namespace
TEST_F(PageSchedulerImplTest, MaxVirtualTimeTaskStarvationCountOneHundred) {
page_scheduler_->EnableVirtualTime();
page_scheduler_->SetMaxVirtualTimeTaskStarvationCount(100);
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kAdvance);
int count = 0;
int delayed_task_run_at_count = 0;
RepostingTask(ThrottleableTaskQueue(), 1000, &count);
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(DelayedTask, base::Unretained(&count),
base::Unretained(&delayed_task_run_at_count)),
base::TimeDelta::FromMilliseconds(10));
page_scheduler_->GrantVirtualTimeBudget(
base::TimeDelta::FromMilliseconds(1000),
WTF::Bind(
[](PageScheduler* scheduler) {
scheduler->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
},
WTF::Unretained(page_scheduler_.get())));
mock_task_runner_->RunUntilIdle();
// Two delayed tasks with a run of 100 tasks, plus initial call.
EXPECT_EQ(201, count);
EXPECT_EQ(102, delayed_task_run_at_count);
}
TEST_F(PageSchedulerImplTest,
MaxVirtualTimeTaskStarvationCountOneHundredNestedMessageLoop) {
page_scheduler_->EnableVirtualTime();
page_scheduler_->SetMaxVirtualTimeTaskStarvationCount(100);
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kAdvance);
scheduler_->OnBeginNestedRunLoop();
int count = 0;
int delayed_task_run_at_count = 0;
RepostingTask(ThrottleableTaskQueue(), 1000, &count);
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(DelayedTask, WTF::Unretained(&count),
WTF::Unretained(&delayed_task_run_at_count)),
base::TimeDelta::FromMilliseconds(10));
page_scheduler_->GrantVirtualTimeBudget(
base::TimeDelta::FromMilliseconds(1000),
WTF::Bind(
[](PageScheduler* scheduler) {
scheduler->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
},
WTF::Unretained(page_scheduler_.get())));
mock_task_runner_->RunUntilIdle();
EXPECT_EQ(1000, count);
EXPECT_EQ(1000, delayed_task_run_at_count);
}
TEST_F(PageSchedulerImplTest, MaxVirtualTimeTaskStarvationCountZero) {
page_scheduler_->EnableVirtualTime();
page_scheduler_->SetMaxVirtualTimeTaskStarvationCount(0);
page_scheduler_->SetVirtualTimePolicy(VirtualTimePolicy::kAdvance);
int count = 0;
int delayed_task_run_at_count = 0;
RepostingTask(ThrottleableTaskQueue(), 1000, &count);
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE,
base::BindOnce(DelayedTask, WTF::Unretained(&count),
WTF::Unretained(&delayed_task_run_at_count)),
base::TimeDelta::FromMilliseconds(10));
page_scheduler_->GrantVirtualTimeBudget(
base::TimeDelta::FromMilliseconds(1000),
WTF::Bind(
[](PageScheduler* scheduler) {
scheduler->SetVirtualTimePolicy(VirtualTimePolicy::kPause);
},
WTF::Unretained(page_scheduler_.get())));
mock_task_runner_->RunUntilIdle();
EXPECT_EQ(1000, count);
// If the initial count had been higher, the delayed task could have been
// arbitrarily delayed.
EXPECT_EQ(1000, delayed_task_run_at_count);
}
namespace {
void ExpensiveTestTask(base::SimpleTestTickClock* clock,
std::vector<base::TimeTicks>* run_times) {
run_times->push_back(clock->NowTicks());
clock->Advance(base::TimeDelta::FromMilliseconds(250));
}
void InitializeTrialParams() {
std::map<std::string, std::string> params = {{"cpu_budget", "0.01"},
{"max_budget", "0.0"},
{"initial_budget", "0.0"},
{"max_delay", "0.0"}};
const char kParamName[] = "ExpensiveBackgroundTimerThrottling";
const char kGroupName[] = "Enabled";
EXPECT_TRUE(base::AssociateFieldTrialParams(kParamName, kGroupName, params));
EXPECT_TRUE(base::FieldTrialList::CreateFieldTrial(kParamName, kGroupName));
std::map<std::string, std::string> actual_params;
base::GetFieldTrialParams(kParamName, &actual_params);
EXPECT_EQ(actual_params, params);
}
} // namespace
TEST_F(PageSchedulerImplTest, BackgroundTimerThrottling) {
ScopedExpensiveBackgroundTimerThrottlingForTest
budget_background_throttling_enabler(true);
std::unique_ptr<base::FieldTrialList> field_trial_list =
std::make_unique<base::FieldTrialList>(nullptr);
InitializeTrialParams();
page_scheduler_.reset(
new PageSchedulerImpl(nullptr, scheduler_.get(), false));
std::vector<base::TimeTicks> run_times;
frame_scheduler_ = page_scheduler_->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
page_scheduler_->SetPageVisible(true);
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(2500));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMilliseconds(1));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMilliseconds(1));
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(3500));
// Check that these tasks are aligned, but are not subject to budget-based
// throttling.
EXPECT_THAT(
run_times,
ElementsAre(base::TimeTicks() + base::TimeDelta::FromMilliseconds(2501),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(2751)));
run_times.clear();
page_scheduler_->SetPageVisible(false);
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMicroseconds(1));
ThrottleableTaskQueue()->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMicroseconds(1));
mock_task_runner_->RunUntilIdle();
// Check that tasks are aligned and throttled.
EXPECT_THAT(
run_times,
ElementsAre(base::TimeTicks() + base::TimeDelta::FromSeconds(4),
base::TimeTicks() + base::TimeDelta::FromSeconds(26)));
base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
}
TEST_F(PageSchedulerImplTest, OpenWebSocketExemptsFromBudgetThrottling) {
ScopedExpensiveBackgroundTimerThrottlingForTest
budget_background_throttling_enabler(true);
std::unique_ptr<base::FieldTrialList> field_trial_list =
std::make_unique<base::FieldTrialList>(nullptr);
InitializeTrialParams();
std::unique_ptr<PageSchedulerImpl> page_scheduler(
new PageSchedulerImpl(nullptr, scheduler_.get(), false));
std::vector<base::TimeTicks> run_times;
std::unique_ptr<FrameSchedulerImpl> frame_scheduler1 =
page_scheduler->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
std::unique_ptr<FrameSchedulerImpl> frame_scheduler2 =
page_scheduler->CreateFrameSchedulerImpl(
nullptr, FrameScheduler::FrameType::kSubframe);
page_scheduler->SetPageVisible(false);
// Wait for 20s to avoid initial throttling delay.
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(20500));
for (size_t i = 0; i < 3; ++i) {
ThrottleableTaskQueueForScheduler(frame_scheduler1.get())
->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMilliseconds(1));
}
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(55500));
// Check that tasks are throttled.
EXPECT_THAT(
run_times,
ElementsAre(base::TimeTicks() + base::TimeDelta::FromSeconds(21),
base::TimeTicks() + base::TimeDelta::FromSeconds(26),
base::TimeTicks() + base::TimeDelta::FromSeconds(51)));
run_times.clear();
std::unique_ptr<FrameScheduler::ActiveConnectionHandle> websocket_connection =
frame_scheduler1->OnActiveConnectionCreated();
for (size_t i = 0; i < 3; ++i) {
ThrottleableTaskQueueForScheduler(frame_scheduler1.get())
->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMilliseconds(1));
}
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(58500));
// Check that the timer task queue from the first frame is aligned,
// but not throttled.
EXPECT_THAT(
run_times,
ElementsAre(
base::TimeTicks() + base::TimeDelta::FromMilliseconds(56000),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(56250),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(56500)));
run_times.clear();
for (size_t i = 0; i < 3; ++i) {
ThrottleableTaskQueueForScheduler(frame_scheduler2.get())
->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMilliseconds(1));
}
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(59500));
// Check that the second frame scheduler becomes unthrottled.
EXPECT_THAT(
run_times,
ElementsAre(
base::TimeTicks() + base::TimeDelta::FromMilliseconds(59000),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(59250),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(59500)));
run_times.clear();
websocket_connection.reset();
// Wait for 10s to enable throttling back.
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(70500));
for (size_t i = 0; i < 3; ++i) {
ThrottleableTaskQueueForScheduler(frame_scheduler1.get())
->PostDelayedTask(
FROM_HERE, base::BindOnce(&ExpensiveTestTask, &clock_, &run_times),
base::TimeDelta::FromMilliseconds(1));
}
mock_task_runner_->RunUntilIdle();
// WebSocket is closed, budget-based throttling now applies.
EXPECT_THAT(
run_times,
ElementsAre(base::TimeTicks() + base::TimeDelta::FromSeconds(84),
base::TimeTicks() + base::TimeDelta::FromSeconds(109),
base::TimeTicks() + base::TimeDelta::FromSeconds(134)));
base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
}
} // namespace page_scheduler_impl_unittest
} // namespace scheduler
} // namespace blink