blob: 69757afcbd60193556e0c47c67cd8a4cfa9f57d7 [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h"
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/metrics/field_trial_params.h"
#include "base/run_loop.h"
#include "base/task/common/task_annotator.h"
#include "base/task/sequence_manager/test/sequence_manager_for_test.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/unguessable_token.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/switches.h"
#include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
#include "third_party/blink/public/platform/web_runtime_features.h"
#include "third_party/blink/renderer/platform/scheduler/common/features.h"
#include "third_party/blink/renderer/platform/scheduler/common/task_priority.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/frame_task_queue_controller.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_task_queue.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/page_scheduler_impl.h"
#include "third_party/blink/renderer/platform/scheduler/main_thread/task_type_names.h"
#include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_queue_type.h"
#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_task_queue.h"
#include "third_party/blink/renderer/platform/scheduler/test/web_scheduling_test_helper.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
using base::sequence_manager::TaskQueue;
using testing::UnorderedElementsAre;
namespace blink {
namespace scheduler {
// To avoid symbol collisions in jumbo builds.
namespace frame_scheduler_impl_unittest {
using FeatureHandle = FrameOrWorkerScheduler::SchedulingAffectingFeatureHandle;
using PrioritisationType = MainThreadTaskQueue::QueueTraits::PrioritisationType;
using testing::Return;
namespace {
constexpr base::TimeDelta kDefaultThrottledWakeUpInterval =
PageSchedulerImpl::kDefaultThrottledWakeUpInterval;
constexpr base::TimeDelta kIntensiveThrottledWakeUpInterval =
PageSchedulerImpl::kIntensiveThrottledWakeUpInterval;
constexpr auto kShortDelay = base::Milliseconds(10);
// This is a wrapper around MainThreadSchedulerImpl::CreatePageScheduler, that
// returns the PageScheduler as a PageSchedulerImpl.
std::unique_ptr<PageSchedulerImpl> CreatePageScheduler(
PageScheduler::Delegate* page_scheduler_delegate,
MainThreadSchedulerImpl* scheduler,
AgentGroupScheduler& agent_group_scheduler) {
std::unique_ptr<PageScheduler> page_scheduler =
agent_group_scheduler.CreatePageScheduler(page_scheduler_delegate);
std::unique_ptr<PageSchedulerImpl> page_scheduler_impl(
static_cast<PageSchedulerImpl*>(page_scheduler.release()));
return page_scheduler_impl;
}
// This is a wrapper around PageSchedulerImpl::CreateFrameScheduler, that
// returns the FrameScheduler as a FrameSchedulerImpl.
std::unique_ptr<FrameSchedulerImpl> CreateFrameScheduler(
PageSchedulerImpl* page_scheduler,
FrameScheduler::Delegate* delegate,
bool is_in_embedded_frame_tree,
FrameScheduler::FrameType frame_type) {
auto frame_scheduler = page_scheduler->CreateFrameScheduler(
delegate, is_in_embedded_frame_tree, frame_type);
std::unique_ptr<FrameSchedulerImpl> frame_scheduler_impl(
static_cast<FrameSchedulerImpl*>(frame_scheduler.release()));
return frame_scheduler_impl;
}
void RecordRunTime(std::vector<base::TimeTicks>* run_times) {
run_times->push_back(base::TimeTicks::Now());
}
class TestObject {
public:
explicit TestObject(int* counter) : counter_(counter) {}
~TestObject() { ++(*counter_); }
private:
int* counter_;
};
} // namespace
// All TaskTypes that can be passed to
// FrameSchedulerImpl::CreateQueueTraitsForTaskType().
constexpr TaskType kAllFrameTaskTypes[] = {
TaskType::kInternalContentCapture,
TaskType::kJavascriptTimerImmediate,
TaskType::kJavascriptTimerDelayedLowNesting,
TaskType::kJavascriptTimerDelayedHighNesting,
TaskType::kInternalLoading,
TaskType::kNetworking,
TaskType::kNetworkingUnfreezable,
TaskType::kNetworkingUnfreezableImageLoading,
TaskType::kNetworkingControl,
TaskType::kLowPriorityScriptExecution,
TaskType::kDOMManipulation,
TaskType::kHistoryTraversal,
TaskType::kEmbed,
TaskType::kCanvasBlobSerialization,
TaskType::kRemoteEvent,
TaskType::kWebSocket,
TaskType::kMicrotask,
TaskType::kUnshippedPortMessage,
TaskType::kFileReading,
TaskType::kPresentation,
TaskType::kSensor,
TaskType::kPerformanceTimeline,
TaskType::kWebGL,
TaskType::kIdleTask,
TaskType::kInternalDefault,
TaskType::kMiscPlatformAPI,
TaskType::kFontLoading,
TaskType::kApplicationLifeCycle,
TaskType::kBackgroundFetch,
TaskType::kPermission,
TaskType::kPostedMessage,
TaskType::kServiceWorkerClientMessage,
TaskType::kWorkerAnimation,
TaskType::kUserInteraction,
TaskType::kMediaElementEvent,
TaskType::kInternalWebCrypto,
TaskType::kInternalMedia,
TaskType::kInternalMediaRealTime,
TaskType::kInternalUserInteraction,
TaskType::kInternalIntersectionObserver,
TaskType::kInternalFindInPage,
TaskType::kInternalContinueScriptLoading,
TaskType::kDatabaseAccess,
TaskType::kInternalNavigationAssociated,
TaskType::kInternalTest,
TaskType::kWebLocks,
TaskType::kInternalFrameLifecycleControl,
TaskType::kInternalTranslation,
TaskType::kInternalInspector,
TaskType::kInternalNavigationAssociatedUnfreezable,
TaskType::kInternalHighPriorityLocalFrame,
TaskType::kInternalInputBlocking,
TaskType::kWakeLock,
TaskType::kStorage,
TaskType::kWebGPU,
TaskType::kInternalPostMessageForwarding,
TaskType::kInternalNavigationCancellation};
static_assert(
static_cast<int>(TaskType::kMaxValue) == 83,
"When adding a TaskType, make sure that kAllFrameTaskTypes is updated.");
void AppendToVectorTestTask(Vector<String>* vector, String value) {
vector->push_back(std::move(value));
}
class FrameSchedulerDelegateForTesting : public FrameScheduler::Delegate {
public:
FrameSchedulerDelegateForTesting() = default;
~FrameSchedulerDelegateForTesting() override = default;
ukm::UkmRecorder* GetUkmRecorder() override { return nullptr; }
ukm::SourceId GetUkmSourceId() override { return ukm::kInvalidSourceId; }
void UpdateTaskTime(base::TimeDelta task_time) override {
update_task_time_calls_++;
}
void OnTaskCompleted(base::TimeTicks,
base::TimeTicks,
base::TimeTicks) override {}
const base::UnguessableToken& GetAgentClusterId() const override {
return base::UnguessableToken::Null();
}
MOCK_METHOD(void, UpdateBackForwardCacheDisablingFeatures, (BlockingDetails));
int update_task_time_calls_ = 0;
};
MATCHER(BlockingDetailsHasCCNS, "Blocking details has CCNS.") {
bool vector_empty = arg.non_sticky_features_and_js_locations.empty();
bool vector_has_ccns =
arg.sticky_features_and_js_locations.Contains(
FeatureAndJSLocationBlockingBFCache(
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoStore,
nullptr)) &&
arg.sticky_features_and_js_locations.Contains(
FeatureAndJSLocationBlockingBFCache(
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoCache,
nullptr));
return vector_empty && vector_has_ccns;
}
MATCHER_P(BlockingDetailsHasWebSocket,
handle,
"BlockingDetails has WebSocket.") {
bool handle_has_web_socket =
(handle->GetFeatureAndJSLocationBlockingBFCache() ==
FeatureAndJSLocationBlockingBFCache(
SchedulingPolicy::Feature::kWebSocket, nullptr));
bool vector_empty = arg.sticky_features_and_js_locations.empty();
return handle_has_web_socket && vector_empty;
}
MATCHER(BlockingDetailsIsEmpty, "BlockingDetails is empty.") {
bool non_sticky_vector_empty =
arg.non_sticky_features_and_js_locations.empty();
bool sticky_vector_empty = arg.sticky_features_and_js_locations.empty();
return non_sticky_vector_empty && sticky_vector_empty;
}
class FrameSchedulerImplTest : public testing::Test {
public:
FrameSchedulerImplTest()
: task_environment_(
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::TaskEnvironment::ThreadPoolExecutionMode::QUEUED) {}
// Constructs a FrameSchedulerImplTest with a list of features to enable and a
// list of features to disable.
FrameSchedulerImplTest(
std::vector<base::test::FeatureRef> features_to_enable,
std::vector<base::test::FeatureRef> features_to_disable)
: FrameSchedulerImplTest() {
feature_list_.InitWithFeatures(features_to_enable, features_to_disable);
}
// Constructs a FrameSchedulerImplTest with a feature to enable, associated
// params, and a list of features to disable.
FrameSchedulerImplTest(
const base::Feature& feature_to_enable,
const base::FieldTrialParams& feature_to_enable_params,
const std::vector<base::test::FeatureRef>& features_to_disable)
: FrameSchedulerImplTest() {
feature_list_.InitWithFeaturesAndParameters(
{{feature_to_enable, feature_to_enable_params}}, features_to_disable);
}
~FrameSchedulerImplTest() override = default;
void SetUp() override {
scheduler_ = std::make_unique<MainThreadSchedulerImpl>(
base::sequence_manager::SequenceManagerForTest::Create(
nullptr, task_environment_.GetMainThreadTaskRunner(),
task_environment_.GetMockTickClock(),
base::sequence_manager::SequenceManager::Settings::Builder()
.SetPrioritySettings(CreatePrioritySettings())
.Build()));
agent_group_scheduler_ = scheduler_->CreateAgentGroupScheduler();
page_scheduler_ =
CreatePageScheduler(nullptr, scheduler_.get(), *agent_group_scheduler_);
frame_scheduler_delegate_ = std::make_unique<
testing::StrictMock<FrameSchedulerDelegateForTesting>>();
frame_scheduler_ = CreateFrameScheduler(
page_scheduler_.get(), frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
}
void ResetFrameScheduler(bool is_in_embedded_frame_tree,
FrameScheduler::FrameType frame_type) {
auto new_delegate_ = std::make_unique<
testing::StrictMock<FrameSchedulerDelegateForTesting>>();
frame_scheduler_ =
CreateFrameScheduler(page_scheduler_.get(), new_delegate_.get(),
is_in_embedded_frame_tree, frame_type);
frame_scheduler_delegate_ = std::move(new_delegate_);
}
void StorePageInBackForwardCache() {
page_scheduler_->SetPageVisible(false);
page_scheduler_->SetPageFrozen(true);
page_scheduler_->SetPageBackForwardCached(true);
}
void RestorePageFromBackForwardCache() {
page_scheduler_->SetPageVisible(true);
page_scheduler_->SetPageFrozen(false);
page_scheduler_->SetPageBackForwardCached(false);
}
void TearDown() override {
throttleable_task_queue_.reset();
frame_scheduler_.reset();
page_scheduler_.reset();
agent_group_scheduler_ = nullptr;
scheduler_->Shutdown();
scheduler_.reset();
frame_scheduler_delegate_.reset();
}
// Helper for posting several tasks of specific prioritisation types for
// testing the relative order of tasks. |task_descriptor| is a string with
// space delimited task identifiers. The first letter of each task identifier
// specifies the prioritisation type:
// - 'R': Regular (normal priority)
// - 'V': Internal Script Continuation (very high priority)
// - 'B': Best-effort
// - 'D': Database
void PostTestTasksForPrioritisationType(Vector<String>* run_order,
const String& task_descriptor) {
std::istringstream stream(task_descriptor.Utf8());
PrioritisationType prioritisation_type;
while (!stream.eof()) {
std::string task;
stream >> task;
switch (task[0]) {
case 'R':
prioritisation_type = PrioritisationType::kRegular;
break;
case 'V':
prioritisation_type = PrioritisationType::kInternalScriptContinuation;
break;
case 'B':
prioritisation_type = PrioritisationType::kBestEffort;
break;
case 'D':
prioritisation_type = PrioritisationType::kExperimentalDatabase;
break;
default:
EXPECT_FALSE(true);
return;
}
auto queue_traits =
FrameSchedulerImpl::PausableTaskQueueTraits().SetPrioritisationType(
prioritisation_type);
GetTaskQueue(queue_traits)
->GetTaskRunnerWithDefaultTaskType()
->PostTask(FROM_HERE,
base::BindOnce(&AppendToVectorTestTask, run_order,
String::FromUTF8(task)));
}
}
// Helper for posting several tasks to specific queues. |task_descriptor| is a
// string with space delimited task identifiers. The first letter of each task
// identifier specifies the task queue:
// - 'L': Loading task queue
// - 'T': Throttleable task queue
// - 'P': Pausable task queue
// - 'U': Unpausable task queue
// - 'D': Deferrable task queue
void PostTestTasksToQueuesWithTrait(Vector<String>* run_order,
const String& task_descriptor) {
std::istringstream stream(task_descriptor.Utf8());
while (!stream.eof()) {
std::string task;
stream >> task;
switch (task[0]) {
case 'L':
LoadingTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
String::FromUTF8(task)));
break;
case 'T':
ThrottleableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
String::FromUTF8(task)));
break;
case 'P':
PausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
String::FromUTF8(task)));
break;
case 'U':
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
String::FromUTF8(task)));
break;
case 'D':
DeferrableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&AppendToVectorTestTask, run_order,
String::FromUTF8(task)));
break;
default:
NOTREACHED();
}
}
}
static void ResetForNavigation(FrameSchedulerImpl* frame_scheduler) {
frame_scheduler->ResetForNavigation();
}
base::TimeDelta GetTaskTime() { return frame_scheduler_->task_time_; }
int GetTotalUpdateTaskTimeCalls() {
return frame_scheduler_delegate_->update_task_time_calls_;
}
void ResetTotalUpdateTaskTimeCalls() {
frame_scheduler_delegate_->update_task_time_calls_ = 0;
}
// Fast-forwards to the next time aligned on |interval|.
void FastForwardToAlignedTime(base::TimeDelta interval) {
const base::TimeTicks now = base::TimeTicks::Now();
const base::TimeTicks aligned =
now.SnappedToNextTick(base::TimeTicks(), interval);
if (aligned != now)
task_environment_.FastForwardBy(aligned - now);
}
static uint64_t GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
FrameSchedulerImpl* frame_scheduler) {
return frame_scheduler
->GetActiveFeaturesTrackedForBackForwardCacheMetricsMask();
}
protected:
scoped_refptr<MainThreadTaskQueue> throttleable_task_queue() {
return throttleable_task_queue_;
}
void LazyInitThrottleableTaskQueue() {
EXPECT_FALSE(throttleable_task_queue());
throttleable_task_queue_ = ThrottleableTaskQueue();
EXPECT_TRUE(throttleable_task_queue());
}
scoped_refptr<MainThreadTaskQueue> GetTaskQueue(
MainThreadTaskQueue::QueueTraits queue_traits) {
return frame_scheduler_->FrameTaskQueueControllerForTest()->GetTaskQueue(
queue_traits);
}
scoped_refptr<MainThreadTaskQueue> ThrottleableTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::ThrottleableTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue>
JavaScriptTimerNormalThrottleableTaskQueue() {
return GetTaskQueue(
FrameSchedulerImpl::ThrottleableTaskQueueTraits().SetPrioritisationType(
PrioritisationType::kJavaScriptTimer));
}
scoped_refptr<MainThreadTaskQueue>
JavaScriptTimerIntensivelyThrottleableTaskQueue() {
return GetTaskQueue(
FrameSchedulerImpl::ThrottleableTaskQueueTraits()
.SetPrioritisationType(PrioritisationType::kJavaScriptTimer)
.SetCanBeIntensivelyThrottled(true));
}
scoped_refptr<MainThreadTaskQueue> JavaScriptTimerNonThrottleableTaskQueue() {
return GetTaskQueue(
FrameSchedulerImpl::DeferrableTaskQueueTraits().SetPrioritisationType(
PrioritisationType::kJavaScriptTimer));
}
scoped_refptr<MainThreadTaskQueue> LoadingTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::LoadingTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> LoadingControlTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::LoadingControlTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> UnfreezableLoadingTaskQueue() {
return GetTaskQueue(
FrameSchedulerImpl::UnfreezableLoadingTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> DeferrableTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::DeferrableTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> PausableTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::PausableTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> UnpausableTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::UnpausableTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> ForegroundOnlyTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::ForegroundOnlyTaskQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> InputBlockingTaskQueue() {
return GetTaskQueue(FrameSchedulerImpl::InputBlockingQueueTraits());
}
scoped_refptr<MainThreadTaskQueue> GetTaskQueue(TaskType type) {
return frame_scheduler_->GetTaskQueue(type);
}
bool IsThrottled() {
EXPECT_TRUE(throttleable_task_queue());
return throttleable_task_queue()->IsThrottled();
}
bool IsTaskTypeThrottled(TaskType task_type) {
scoped_refptr<MainThreadTaskQueue> task_queue = GetTaskQueue(task_type);
return task_queue->IsThrottled();
}
SchedulingLifecycleState CalculateLifecycleState(
FrameScheduler::ObserverType type) {
return frame_scheduler_->CalculateLifecycleState(type);
}
void DidCommitProvisionalLoad(
FrameScheduler::NavigationType navigation_type) {
frame_scheduler_->DidCommitProvisionalLoad(
/*is_web_history_inert_commit=*/false, navigation_type);
}
base::test::ScopedFeatureList& scoped_feature_list() { return feature_list_; }
base::test::ScopedFeatureList feature_list_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<MainThreadSchedulerImpl> scheduler_;
Persistent<AgentGroupScheduler> agent_group_scheduler_;
std::unique_ptr<PageSchedulerImpl> page_scheduler_;
std::unique_ptr<FrameSchedulerImpl> frame_scheduler_;
std::unique_ptr<testing::StrictMock<FrameSchedulerDelegateForTesting>>
frame_scheduler_delegate_;
scoped_refptr<MainThreadTaskQueue> throttleable_task_queue_;
};
class FrameSchedulerImplStopInBackgroundDisabledTest
: public FrameSchedulerImplTest,
public ::testing::WithParamInterface<TaskType> {
public:
FrameSchedulerImplStopInBackgroundDisabledTest()
: FrameSchedulerImplTest({}, {blink::features::kStopInBackground}) {}
};
namespace {
class MockLifecycleObserver {
public:
MockLifecycleObserver()
: not_throttled_count_(0u),
hidden_count_(0u),
throttled_count_(0u),
stopped_count_(0u) {}
inline void CheckObserverState(base::Location from,
size_t not_throttled_count_expectation,
size_t hidden_count_expectation,
size_t throttled_count_expectation,
size_t stopped_count_expectation) {
EXPECT_EQ(not_throttled_count_expectation, not_throttled_count_)
<< from.ToString();
EXPECT_EQ(hidden_count_expectation, hidden_count_) << from.ToString();
EXPECT_EQ(throttled_count_expectation, throttled_count_) << from.ToString();
EXPECT_EQ(stopped_count_expectation, stopped_count_) << from.ToString();
}
void OnLifecycleStateChanged(SchedulingLifecycleState state) {
switch (state) {
case SchedulingLifecycleState::kNotThrottled:
not_throttled_count_++;
break;
case SchedulingLifecycleState::kHidden:
hidden_count_++;
break;
case SchedulingLifecycleState::kThrottled:
throttled_count_++;
break;
case SchedulingLifecycleState::kStopped:
stopped_count_++;
break;
// We should not have another state, and compiler checks it.
}
}
FrameOrWorkerScheduler::OnLifecycleStateChangedCallback GetCallback() {
return base::BindRepeating(&MockLifecycleObserver::OnLifecycleStateChanged,
base::Unretained(this));
}
private:
size_t not_throttled_count_;
size_t hidden_count_;
size_t throttled_count_;
size_t stopped_count_;
};
void IncrementCounter(int* counter) {
++*counter;
}
// Simulate running a task of a particular length by fast forwarding the task
// environment clock, which is used to determine the wall time of a task.
void RunTaskOfLength(base::test::TaskEnvironment* task_environment,
base::TimeDelta length) {
task_environment->FastForwardBy(length);
}
class FrameSchedulerImplTestWithIntensiveWakeUpThrottlingBase
: public FrameSchedulerImplTest {
public:
using Super = FrameSchedulerImplTest;
FrameSchedulerImplTestWithIntensiveWakeUpThrottlingBase()
: FrameSchedulerImplTest({features::kIntensiveWakeUpThrottling},
{features::kStopInBackground}) {}
void SetUp() override {
Super::SetUp();
ClearIntensiveWakeUpThrottlingPolicyOverrideCacheForTesting();
}
void TearDown() override {
ClearIntensiveWakeUpThrottlingPolicyOverrideCacheForTesting();
Super::TearDown();
}
const int kNumTasks = 5;
const base::TimeDelta kGracePeriod =
GetIntensiveWakeUpThrottlingGracePeriod(false);
};
// Test param for FrameSchedulerImplTestWithIntensiveWakeUpThrottling
struct IntensiveWakeUpThrottlingTestParam {
// TaskType used to obtain TaskRunners from the FrameScheduler.
TaskType task_type;
// Whether it is expected that tasks will be intensively throttled.
bool is_intensive_throttling_expected;
};
class FrameSchedulerImplTestWithIntensiveWakeUpThrottling
: public FrameSchedulerImplTestWithIntensiveWakeUpThrottlingBase,
public ::testing::WithParamInterface<IntensiveWakeUpThrottlingTestParam> {
public:
FrameSchedulerImplTestWithIntensiveWakeUpThrottling() = default;
TaskType GetTaskType() const { return GetParam().task_type; }
bool IsIntensiveThrottlingExpected() const {
return GetParam().is_intensive_throttling_expected;
}
// Get the TaskRunner from |frame_scheduler_| using the test's task type
// parameter.
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() {
return GetTaskRunner(frame_scheduler_.get());
}
// Get the TaskRunner from the provided |frame_scheduler| using the test's
// task type parameter.
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(
FrameSchedulerImpl* frame_scheduler) {
const TaskType task_type = GetTaskType();
if (task_type == TaskType::kWebSchedulingPostedTask) {
test_web_scheduling_task_queues_.push_back(
frame_scheduler->CreateWebSchedulingTaskQueue(
WebSchedulingQueueType::kTaskQueue,
WebSchedulingPriority::kUserVisiblePriority));
return test_web_scheduling_task_queues_.back()->GetTaskRunner();
}
return frame_scheduler->GetTaskRunner(task_type);
}
base::TimeDelta GetExpectedWakeUpInterval() const {
if (IsIntensiveThrottlingExpected())
return kIntensiveThrottledWakeUpInterval;
return kDefaultThrottledWakeUpInterval;
}
private:
// Store web scheduling task queues that are created for tests so
// they do not get destroyed. Destroying them before their tasks finish
// running will break throttling.
Vector<std::unique_ptr<WebSchedulingTaskQueue>>
test_web_scheduling_task_queues_;
};
class FrameSchedulerImplTestWithIntensiveWakeUpThrottlingPolicyOverride
: public FrameSchedulerImplTestWithIntensiveWakeUpThrottlingBase {
public:
FrameSchedulerImplTestWithIntensiveWakeUpThrottlingPolicyOverride() = default;
// This should only be called once per test, and prior to the
// PageSchedulerImpl logic actually parsing the policy switch.
void SetPolicyOverride(bool enabled) {
DCHECK(!scoped_command_line_.GetProcessCommandLine()->HasSwitch(
switches::kIntensiveWakeUpThrottlingPolicy));
scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
switches::kIntensiveWakeUpThrottlingPolicy,
enabled ? switches::kIntensiveWakeUpThrottlingPolicy_ForceEnable
: switches::kIntensiveWakeUpThrottlingPolicy_ForceDisable);
}
private:
base::test::ScopedCommandLine scoped_command_line_;
};
} // namespace
// Throttleable task queue is initialized lazily, so there're two scenarios:
// - Task queue created first and throttling decision made later;
// - Scheduler receives relevant signals to make a throttling decision but
// applies one once task queue gets created.
// We test both (ExplicitInit/LazyInit) of them.
TEST_F(FrameSchedulerImplTest, PageVisible) {
EXPECT_FALSE(throttleable_task_queue());
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, PageHidden_ExplicitInit) {
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
page_scheduler_->SetPageVisible(false);
EXPECT_TRUE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, PageHidden_LazyInit) {
page_scheduler_->SetPageVisible(false);
LazyInitThrottleableTaskQueue();
EXPECT_TRUE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, PageHiddenThenVisible_ExplicitInit) {
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
page_scheduler_->SetPageVisible(false);
EXPECT_TRUE(IsThrottled());
page_scheduler_->SetPageVisible(true);
EXPECT_FALSE(IsThrottled());
page_scheduler_->SetPageVisible(false);
EXPECT_TRUE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest,
FrameHiddenThenVisible_CrossOrigin_ExplicitInit) {
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
frame_scheduler_->SetFrameVisible(false);
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
frame_scheduler_->SetCrossOriginToNearestMainFrame(false);
EXPECT_FALSE(IsThrottled());
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
EXPECT_TRUE(IsThrottled());
frame_scheduler_->SetFrameVisible(true);
EXPECT_FALSE(IsThrottled());
frame_scheduler_->SetFrameVisible(false);
EXPECT_TRUE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, FrameHidden_CrossOrigin_LazyInit) {
frame_scheduler_->SetFrameVisible(false);
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
LazyInitThrottleableTaskQueue();
EXPECT_TRUE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, FrameHidden_SameOrigin_ExplicitInit) {
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
frame_scheduler_->SetFrameVisible(false);
EXPECT_FALSE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, FrameHidden_SameOrigin_LazyInit) {
frame_scheduler_->SetFrameVisible(false);
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, FrameVisible_CrossOrigin_ExplicitInit) {
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
EXPECT_TRUE(throttleable_task_queue());
frame_scheduler_->SetFrameVisible(true);
EXPECT_FALSE(IsThrottled());
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
EXPECT_FALSE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, FrameVisible_CrossOrigin_LazyInit) {
frame_scheduler_->SetFrameVisible(true);
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
LazyInitThrottleableTaskQueue();
EXPECT_FALSE(IsThrottled());
}
TEST_F(FrameSchedulerImplTest, PauseAndResume) {
int counter = 0;
LoadingTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
ThrottleableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
DeferrableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
PausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
frame_scheduler_->SetPaused(true);
EXPECT_EQ(0, counter);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, counter);
frame_scheduler_->SetPaused(false);
EXPECT_EQ(1, counter);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(5, counter);
}
TEST_F(FrameSchedulerImplTest, PauseAndResumeForCooperativeScheduling) {
EXPECT_TRUE(LoadingTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(ThrottleableTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(DeferrableTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(PausableTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(UnpausableTaskQueue()->IsQueueEnabled());
frame_scheduler_->SetPreemptedForCooperativeScheduling(
FrameOrWorkerScheduler::Preempted(true));
EXPECT_FALSE(LoadingTaskQueue()->IsQueueEnabled());
EXPECT_FALSE(ThrottleableTaskQueue()->IsQueueEnabled());
EXPECT_FALSE(DeferrableTaskQueue()->IsQueueEnabled());
EXPECT_FALSE(PausableTaskQueue()->IsQueueEnabled());
EXPECT_FALSE(UnpausableTaskQueue()->IsQueueEnabled());
frame_scheduler_->SetPreemptedForCooperativeScheduling(
FrameOrWorkerScheduler::Preempted(false));
EXPECT_TRUE(LoadingTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(ThrottleableTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(DeferrableTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(PausableTaskQueue()->IsQueueEnabled());
EXPECT_TRUE(UnpausableTaskQueue()->IsQueueEnabled());
}
namespace {
// A task that re-posts itself with a delay in order until it has run
// |num_remaining_tasks| times.
void RePostTask(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
base::TimeDelta delay,
int* num_remaining_tasks) {
--(*num_remaining_tasks);
if (*num_remaining_tasks > 0) {
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RePostTask, task_runner, delay,
base::Unretained(num_remaining_tasks)),
delay);
}
}
} // namespace
// Verify that tasks in a throttled task queue cause:
// - Before intensive wake up throttling kicks in: 1 wake up per second
// - After intensive wake up throttling kick in:
// - Low nesting level: 1 wake up per second
// - High nesting level: 1 wake up per minute
// Disable the kStopInBackground feature because it hides the effect of
// intensive wake up throttling.
// Flake test: crbug.com/1328967
TEST_P(FrameSchedulerImplStopInBackgroundDisabledTest,
DISABLED_ThrottledTaskExecution) {
// This TaskRunner is throttled.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(GetParam());
// Hide the page. This enables wake up throttling.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
// Schedule tasks with a short delay, during the intensive wake up throttling
// grace period.
int num_remaining_tasks =
base::Seconds(kIntensiveWakeUpThrottling_GracePeriodSeconds_Default)
.IntDiv(kDefaultThrottledWakeUpInterval);
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RePostTask, task_runner, kShortDelay,
base::Unretained(&num_remaining_tasks)),
kShortDelay);
// A task should run every second.
while (num_remaining_tasks > 0) {
int previous_num_remaining_tasks = num_remaining_tasks;
task_environment_.FastForwardBy(kDefaultThrottledWakeUpInterval);
EXPECT_EQ(previous_num_remaining_tasks - 1, num_remaining_tasks);
}
// Schedule tasks with a short delay, after the intensive wake up throttling
// grace period.
num_remaining_tasks = 5;
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&RePostTask, task_runner, kShortDelay,
base::Unretained(&num_remaining_tasks)),
kShortDelay);
// Task run every minute if the nesting level is high, or every second
// otherwise.
const base::TimeDelta expected_period_after_grace_period =
(GetParam() == TaskType::kJavascriptTimerDelayedLowNesting)
? kDefaultThrottledWakeUpInterval
: kIntensiveThrottledWakeUpInterval;
while (num_remaining_tasks > 0) {
int previous_num_remaining_tasks = num_remaining_tasks;
task_environment_.FastForwardBy(expected_period_after_grace_period);
EXPECT_EQ(previous_num_remaining_tasks - 1, num_remaining_tasks);
}
}
INSTANTIATE_TEST_SUITE_P(
AllTimerTaskTypes,
FrameSchedulerImplStopInBackgroundDisabledTest,
testing::Values(TaskType::kJavascriptTimerDelayedLowNesting,
TaskType::kJavascriptTimerDelayedHighNesting),
[](const testing::TestParamInfo<TaskType>& info) {
return TaskTypeNames::TaskTypeToString(info.param);
});
TEST_F(FrameSchedulerImplTest, FreezeForegroundOnlyTasks) {
int counter = 0;
ForegroundOnlyTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
frame_scheduler_->SetFrameVisible(false);
EXPECT_EQ(0, counter);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, counter);
frame_scheduler_->SetFrameVisible(true);
EXPECT_EQ(0, counter);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, counter);
}
TEST_F(FrameSchedulerImplTest, PageFreezeAndUnfreeze) {
int counter = 0;
LoadingTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
ThrottleableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
DeferrableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
PausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
page_scheduler_->SetPageVisible(false);
page_scheduler_->SetPageFrozen(true);
EXPECT_EQ(0, counter);
base::RunLoop().RunUntilIdle();
// unpausable tasks continue to run.
EXPECT_EQ(1, counter);
page_scheduler_->SetPageFrozen(false);
EXPECT_EQ(1, counter);
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(5, counter);
}
// Similar to PageFreezeAndUnfreeze, but unfreezes task queues by making the
// page visible instead of by invoking SetPageFrozen(false).
TEST_F(FrameSchedulerImplTest, PageFreezeAndPageVisible) {
int counter = 0;
LoadingTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
ThrottleableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
DeferrableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
PausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
page_scheduler_->SetPageVisible(false);
page_scheduler_->SetPageFrozen(true);
EXPECT_EQ(0, counter);
base::RunLoop().RunUntilIdle();
// unpausable tasks continue to run.
EXPECT_EQ(1, counter);
// Making the page visible should cause frozen queues to resume.
page_scheduler_->SetPageVisible(true);
EXPECT_EQ(1, counter);
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_EQ(5, counter);
}
TEST_F(FrameSchedulerImplTest, PagePostsCpuTasks) {
EXPECT_TRUE(GetTaskTime().is_zero());
EXPECT_EQ(0, GetTotalUpdateTaskTimeCalls());
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&RunTaskOfLength, &task_environment_,
base::Milliseconds(10)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetTaskTime().is_zero());
EXPECT_EQ(0, GetTotalUpdateTaskTimeCalls());
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&RunTaskOfLength, &task_environment_,
base::Milliseconds(100)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetTaskTime().is_zero());
EXPECT_EQ(1, GetTotalUpdateTaskTimeCalls());
}
TEST_F(FrameSchedulerImplTest, FramePostsCpuTasksThroughReloadRenavigate) {
const struct {
bool embedded_frame_tree;
FrameScheduler::FrameType frame_type;
FrameScheduler::NavigationType navigation_type;
bool expect_task_time_zero;
int expected_total_calls;
} kTestCases[] = {{false, FrameScheduler::FrameType::kMainFrame,
FrameScheduler::NavigationType::kOther, false, 0},
{false, FrameScheduler::FrameType::kMainFrame,
FrameScheduler::NavigationType::kReload, false, 0},
{false, FrameScheduler::FrameType::kMainFrame,
FrameScheduler::NavigationType::kSameDocument, true, 1},
{false, FrameScheduler::FrameType::kSubframe,
FrameScheduler::NavigationType::kOther, true, 1},
{false, FrameScheduler::FrameType::kSubframe,
FrameScheduler::NavigationType::kSameDocument, true, 1},
{true, FrameScheduler::FrameType::kMainFrame,
FrameScheduler::NavigationType::kOther, true, 1},
{true, FrameScheduler::FrameType::kMainFrame,
FrameScheduler::NavigationType::kSameDocument, true, 1},
{true, FrameScheduler::FrameType::kSubframe,
FrameScheduler::NavigationType::kOther, true, 1},
{true, FrameScheduler::FrameType::kSubframe,
FrameScheduler::NavigationType::kSameDocument, true, 1}};
for (const auto& test_case : kTestCases) {
SCOPED_TRACE(String::Format(
"FrameType: %d, NavigationType: %d : TaskTime.is_zero %d, CallCount %d",
test_case.frame_type, test_case.navigation_type,
test_case.expect_task_time_zero, test_case.expected_total_calls));
ResetFrameScheduler(test_case.embedded_frame_tree, test_case.frame_type);
EXPECT_TRUE(GetTaskTime().is_zero());
EXPECT_EQ(0, GetTotalUpdateTaskTimeCalls());
// Check the rest of the values after different types of commit.
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&RunTaskOfLength, &task_environment_,
base::Milliseconds(60)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetTaskTime().is_zero());
EXPECT_EQ(0, GetTotalUpdateTaskTimeCalls());
DidCommitProvisionalLoad(test_case.navigation_type);
UnpausableTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&RunTaskOfLength, &task_environment_,
base::Milliseconds(60)));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(test_case.expect_task_time_zero, GetTaskTime().is_zero());
EXPECT_EQ(test_case.expected_total_calls, GetTotalUpdateTaskTimeCalls());
}
}
class FrameSchedulerImplTestWithUnfreezableLoading
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplTestWithUnfreezableLoading()
: FrameSchedulerImplTest({blink::features::kLoadingTasksUnfreezable},
{}) {
WebRuntimeFeatures::EnableBackForwardCache(true);
}
};
TEST_F(FrameSchedulerImplTestWithUnfreezableLoading,
LoadingTasksKeepRunningWhenFrozen) {
int counter = 0;
UnfreezableLoadingTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
LoadingTaskQueue()->GetTaskRunnerWithDefaultTaskType()->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
page_scheduler_->SetPageVisible(false);
page_scheduler_->SetPageFrozen(true);
EXPECT_EQ(0, counter);
base::RunLoop().RunUntilIdle();
// Unfreezable tasks continue to run.
EXPECT_EQ(1, counter);
page_scheduler_->SetPageFrozen(false);
EXPECT_EQ(1, counter);
// Same as RunUntilIdle but also advances the clock if necessary.
task_environment_.FastForwardUntilNoTasksRemain();
// Freezable tasks resume.
EXPECT_EQ(2, counter);
}
// Tests if throttling observer callbacks work.
TEST_F(FrameSchedulerImplTest, LifecycleObserver) {
std::unique_ptr<MockLifecycleObserver> observer =
std::make_unique<MockLifecycleObserver>();
size_t not_throttled_count = 0u;
size_t hidden_count = 0u;
size_t throttled_count = 0u;
size_t stopped_count = 0u;
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
throttled_count, stopped_count);
auto observer_handle = frame_scheduler_->AddLifecycleObserver(
FrameScheduler::ObserverType::kLoader, observer->GetCallback());
// Initial state should be synchronously notified here.
// We assume kNotThrottled is notified as an initial state, but it could
// depend on implementation details and can be changed.
observer->CheckObserverState(FROM_HERE, ++not_throttled_count, hidden_count,
throttled_count, stopped_count);
// Once the page gets to be invisible, it should notify the observer of
// kHidden synchronously.
page_scheduler_->SetPageVisible(false);
observer->CheckObserverState(FROM_HERE, not_throttled_count, ++hidden_count,
throttled_count, stopped_count);
// We do not issue new notifications without actually changing visibility
// state.
page_scheduler_->SetPageVisible(false);
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
throttled_count, stopped_count);
task_environment_.FastForwardBy(base::Seconds(30));
// The frame gets throttled after some time in background.
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
++throttled_count, stopped_count);
// We shouldn't issue new notifications for kThrottled state as well.
page_scheduler_->SetPageVisible(false);
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
throttled_count, stopped_count);
// Setting background page to STOPPED, notifies observers of kStopped.
page_scheduler_->SetPageFrozen(true);
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
throttled_count, ++stopped_count);
// When page is not in the STOPPED state, then page visibility is used,
// notifying observer of kThrottled.
page_scheduler_->SetPageFrozen(false);
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
++throttled_count, stopped_count);
// Going back to visible state should notify the observer of kNotThrottled
// synchronously.
page_scheduler_->SetPageVisible(true);
observer->CheckObserverState(FROM_HERE, ++not_throttled_count, hidden_count,
throttled_count, stopped_count);
// Remove from the observer list, and see if any other callback should not be
// invoked when the condition is changed.
observer_handle.reset();
page_scheduler_->SetPageVisible(false);
// Wait 100 secs virtually and run pending tasks just in case.
task_environment_.FastForwardBy(base::Seconds(100));
base::RunLoop().RunUntilIdle();
observer->CheckObserverState(FROM_HERE, not_throttled_count, hidden_count,
throttled_count, stopped_count);
}
TEST_F(FrameSchedulerImplTest, DefaultSchedulingLifecycleState) {
EXPECT_EQ(CalculateLifecycleState(FrameScheduler::ObserverType::kLoader),
SchedulingLifecycleState::kNotThrottled);
EXPECT_EQ(
CalculateLifecycleState(FrameScheduler::ObserverType::kWorkerScheduler),
SchedulingLifecycleState::kNotThrottled);
}
TEST_F(FrameSchedulerImplTest, SubesourceLoadingPaused) {
// A loader observer and related counts.
std::unique_ptr<MockLifecycleObserver> loader_observer =
std::make_unique<MockLifecycleObserver>();
size_t loader_throttled_count = 0u;
size_t loader_not_throttled_count = 0u;
size_t loader_hidden_count = 0u;
size_t loader_stopped_count = 0u;
// A worker observer and related counts.
std::unique_ptr<MockLifecycleObserver> worker_observer =
std::make_unique<MockLifecycleObserver>();
size_t worker_throttled_count = 0u;
size_t worker_not_throttled_count = 0u;
size_t worker_hidden_count = 0u;
size_t worker_stopped_count = 0u;
// Both observers should start with no responses.
loader_observer->CheckObserverState(
FROM_HERE, loader_not_throttled_count, loader_hidden_count,
loader_throttled_count, loader_stopped_count);
worker_observer->CheckObserverState(
FROM_HERE, worker_not_throttled_count, worker_hidden_count,
worker_throttled_count, worker_stopped_count);
// Adding the observers should recieve a non-throttled response
auto loader_observer_handle = frame_scheduler_->AddLifecycleObserver(
FrameScheduler::ObserverType::kLoader, loader_observer->GetCallback());
auto worker_observer_handle = frame_scheduler_->AddLifecycleObserver(
FrameScheduler::ObserverType::kWorkerScheduler,
worker_observer->GetCallback());
loader_observer->CheckObserverState(
FROM_HERE, ++loader_not_throttled_count, loader_hidden_count,
loader_throttled_count, loader_stopped_count);
worker_observer->CheckObserverState(
FROM_HERE, ++worker_not_throttled_count, worker_hidden_count,
worker_throttled_count, worker_stopped_count);
{
auto pause_handle_a = frame_scheduler_->GetPauseSubresourceLoadingHandle();
loader_observer->CheckObserverState(
FROM_HERE, loader_not_throttled_count, loader_hidden_count,
loader_throttled_count, ++loader_stopped_count);
worker_observer->CheckObserverState(
FROM_HERE, ++worker_not_throttled_count, worker_hidden_count,
worker_throttled_count, worker_stopped_count);
std::unique_ptr<MockLifecycleObserver> loader_observer_added_after_stopped =
std::make_unique<MockLifecycleObserver>();
auto loader_observer_added_after_stopped_handle =
frame_scheduler_->AddLifecycleObserver(
FrameScheduler::ObserverType::kLoader,
loader_observer_added_after_stopped->GetCallback());
// This observer should see stopped when added.
loader_observer_added_after_stopped->CheckObserverState(FROM_HERE, 0, 0, 0,
1u);
// Adding another handle should not create a new state.
auto pause_handle_b = frame_scheduler_->GetPauseSubresourceLoadingHandle();
loader_observer->CheckObserverState(
FROM_HERE, loader_not_throttled_count, loader_hidden_count,
loader_throttled_count, loader_stopped_count);
worker_observer->CheckObserverState(
FROM_HERE, worker_not_throttled_count, worker_hidden_count,
worker_throttled_count, worker_stopped_count);
}
// Removing the handles should return the state to non throttled.
loader_observer->CheckObserverState(
FROM_HERE, ++loader_not_throttled_count, loader_hidden_count,
loader_throttled_count, loader_stopped_count);
worker_observer->CheckObserverState(
FROM_HERE, ++worker_not_throttled_count, worker_hidden_count,
worker_throttled_count, worker_stopped_count);
}
TEST_F(FrameSchedulerImplTest, LogIpcsPostedToFramesInBackForwardCache) {
base::HistogramTester histogram_tester;
// Create the task queue implicitly.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(TaskType::kInternalTest);
StorePageInBackForwardCache();
// Run the tasks so that they are recorded in the histogram
task_environment_.FastForwardBy(base::Hours(1));
// Post IPC tasks, accounting for delay for when tracking starts.
{
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(1);
task_runner->PostTask(FROM_HERE, base::DoNothing());
}
{
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash_2(2);
task_runner->PostTask(FROM_HERE, base::DoNothing());
}
// Logging is delayed by one second, so guarantee that our IPCS are logged.
task_environment_.FastForwardBy(base::Seconds(2));
task_environment_.RunUntilIdle();
// Once the page is restored from the cache, IPCs should no longer be
// recorded.
RestorePageFromBackForwardCache();
// Start posting tasks immediately - will not be recorded
{
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash_3(3);
task_runner->PostTask(FROM_HERE, base::DoNothing());
}
{
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash_4(4);
task_runner->PostTask(FROM_HERE, base::DoNothing());
}
EXPECT_THAT(
histogram_tester.GetAllSamples(
"BackForwardCache.Experimental."
"UnexpectedIPCMessagePostedToCachedFrame.MethodHash"),
testing::UnorderedElementsAre(base::Bucket(1, 1), base::Bucket(2, 1)));
// TimeUntilIPCReceived should have values in the 300000 bucket corresponding
// with the hour delay in task_environment_.FastForwardBy.
EXPECT_THAT(
histogram_tester.GetAllSamples(
"BackForwardCache.Experimental."
"UnexpectedIPCMessagePostedToCachedFrame.TimeUntilIPCReceived"),
testing::UnorderedElementsAre(base::Bucket(300000, 2)));
}
TEST_F(FrameSchedulerImplTest,
LogIpcsFromMultipleThreadsPostedToFramesInBackForwardCache) {
base::HistogramTester histogram_tester;
// Create the task queue explicitly to ensure it exists when the page enters
// the back-forward cache, and that the IPC handler is registerd as well.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(TaskType::kInternalTest);
StorePageInBackForwardCache();
// Run the tasks so that they are recorded in the histogram
task_environment_.FastForwardBy(base::Hours(1));
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(1);
task_runner->PostTask(FROM_HERE, base::DoNothing());
},
task_runner));
task_environment_.RunUntilIdle();
base::RepeatingClosure restore_from_cache_callback = base::BindRepeating(
&FrameSchedulerImplTest::RestorePageFromBackForwardCache,
base::Unretained(this));
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
{
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(2);
task_runner->PostTask(FROM_HERE, base::DoNothing());
}
},
task_runner));
// Logging is delayed by one second, so guarantee that our IPCS are logged.
task_environment_.FastForwardBy(base::Seconds(2));
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<base::SingleThreadTaskRunner> task_runner,
base::RepeatingClosure restore_from_cache_callback) {
{
// Once the page is restored from the cache, ensure that the IPC
// restoring the page from the cache is not recorded as well.
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(3);
task_runner->PostTask(FROM_HERE, restore_from_cache_callback);
}
{
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(4);
task_runner->PostTask(FROM_HERE, base::DoNothing());
}
},
task_runner, restore_from_cache_callback));
task_environment_.RunUntilIdle();
// Start posting tasks immediately - will not be recorded
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(5);
task_runner->PostTask(FROM_HERE, base::DoNothing());
},
task_runner));
task_environment_.RunUntilIdle();
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
base::TaskAnnotator::ScopedSetIpcHash scoped_set_ipc_hash(6);
task_runner->PostTask(FROM_HERE, base::DoNothing());
},
task_runner));
task_environment_.RunUntilIdle();
EXPECT_THAT(
histogram_tester.GetAllSamples(
"BackForwardCache.Experimental."
"UnexpectedIPCMessagePostedToCachedFrame.MethodHash"),
testing::UnorderedElementsAre(base::Bucket(1, 1), base::Bucket(2, 1)));
}
class InputHighPriorityFrameSchedulerImplTest : public FrameSchedulerImplTest {
public:
InputHighPriorityFrameSchedulerImplTest()
: FrameSchedulerImplTest(
{},
{::blink::features::kInputTargetClientHighPriority}) {}
};
// TODO(farahcharab) Move priority testing to MainThreadTaskQueueTest after
// landing the change that moves priority computation to MainThreadTaskQueue.
TEST_F(InputHighPriorityFrameSchedulerImplTest,
NormalPriorityInputBlockingTaskQueue) {
page_scheduler_->SetPageVisible(false);
EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
TaskPriority::kNormalPriority);
page_scheduler_->SetPageVisible(true);
EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
TaskPriority::kNormalPriority);
}
TEST_F(FrameSchedulerImplTest, HighestPriorityInputBlockingTaskQueue) {
page_scheduler_->SetPageVisible(false);
EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
TaskPriority::kHighestPriority);
page_scheduler_->SetPageVisible(true);
EXPECT_EQ(InputBlockingTaskQueue()->GetQueuePriority(),
TaskPriority::kHighestPriority);
}
TEST_F(FrameSchedulerImplTest, RenderBlockingImageLoading) {
auto render_blocking_task_queue =
GetTaskQueue(TaskType::kNetworkingUnfreezableImageLoading);
page_scheduler_->SetPageVisible(false);
EXPECT_EQ(render_blocking_task_queue->GetQueuePriority(),
TaskPriority::kNormalPriority);
page_scheduler_->SetPageVisible(true);
EXPECT_EQ(render_blocking_task_queue->GetQueuePriority(),
TaskPriority::kExtremelyHighPriority);
}
TEST_F(FrameSchedulerImplTest, TaskTypeToTaskQueueMapping) {
// Make sure the queue lookup and task type to queue traits map works as
// expected. This test will fail if these task types are moved to different
// default queues.
EXPECT_EQ(GetTaskQueue(TaskType::kJavascriptTimerDelayedLowNesting),
JavaScriptTimerNormalThrottleableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kJavascriptTimerDelayedHighNesting),
JavaScriptTimerIntensivelyThrottleableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kJavascriptTimerImmediate),
JavaScriptTimerNonThrottleableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kWebSocket), DeferrableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kDatabaseAccess), PausableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kPostedMessage), PausableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kWebLocks), UnpausableTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kNetworking), LoadingTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kNetworkingControl),
LoadingControlTaskQueue());
EXPECT_EQ(GetTaskQueue(TaskType::kInternalTranslation),
ForegroundOnlyTaskQueue());
}
// Verify that kJavascriptTimer* are the only non-internal TaskType that can be
// throttled. This ensures that the Javascript timer throttling experiment only
// affects wake ups from Javascript timers https://crbug.com/1075553
TEST_F(FrameSchedulerImplTest, ThrottledTaskTypes) {
page_scheduler_->SetPageVisible(false);
for (TaskType task_type : kAllFrameTaskTypes) {
SCOPED_TRACE(testing::Message()
<< "TaskType is "
<< TaskTypeNames::TaskTypeToString(task_type));
switch (task_type) {
case TaskType::kInternalContentCapture:
case TaskType::kJavascriptTimerDelayedLowNesting:
case TaskType::kJavascriptTimerDelayedHighNesting:
case TaskType::kInternalTranslation:
EXPECT_TRUE(IsTaskTypeThrottled(task_type));
break;
default:
EXPECT_FALSE(IsTaskTypeThrottled(task_type));
break;
};
}
}
class FrameSchedulerImplDatabaseAccessWithoutHighPriority
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplDatabaseAccessWithoutHighPriority()
: FrameSchedulerImplTest({}, {kHighPriorityDatabaseTaskType}) {}
};
TEST_F(FrameSchedulerImplDatabaseAccessWithoutHighPriority, QueueTraits) {
auto da_queue = GetTaskQueue(TaskType::kDatabaseAccess);
EXPECT_EQ(da_queue->GetQueueTraits().prioritisation_type,
MainThreadTaskQueue::QueueTraits::PrioritisationType::kRegular);
EXPECT_EQ(da_queue->GetQueuePriority(), TaskPriority::kNormalPriority);
}
class FrameSchedulerImplDatabaseAccessWithHighPriority
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplDatabaseAccessWithHighPriority()
: FrameSchedulerImplTest({kHighPriorityDatabaseTaskType}, {}) {}
};
TEST_F(FrameSchedulerImplDatabaseAccessWithHighPriority, QueueTraits) {
auto da_queue = GetTaskQueue(TaskType::kDatabaseAccess);
EXPECT_EQ(da_queue->GetQueueTraits().prioritisation_type,
MainThreadTaskQueue::QueueTraits::PrioritisationType::
kExperimentalDatabase);
EXPECT_EQ(da_queue->GetQueuePriority(), TaskPriority::kHighPriority);
}
TEST_F(FrameSchedulerImplDatabaseAccessWithHighPriority, RunOrder) {
Vector<String> run_order;
PostTestTasksForPrioritisationType(&run_order, "D1 R1 D2 V1 B1");
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre("V1", "D1", "D2", "R1", "B1"));
}
TEST_F(FrameSchedulerImplDatabaseAccessWithHighPriority,
NormalPriorityInBackground) {
page_scheduler_->SetPageVisible(false);
Vector<String> run_order;
PostTestTasksForPrioritisationType(&run_order, "D1 R1 D2 V1 B1");
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre("V1", "D1", "R1", "D2", "B1"));
}
TEST_F(FrameSchedulerImplTest, ContentCaptureHasIdleTaskQueue) {
auto task_queue = GetTaskQueue(TaskType::kInternalContentCapture);
EXPECT_EQ(TaskPriority::kBestEffortPriority, task_queue->GetQueuePriority());
}
TEST_F(FrameSchedulerImplTest, ComputePriorityForDetachedFrame) {
auto task_queue = GetTaskQueue(TaskType::kJavascriptTimerDelayedLowNesting);
// Just check that it does not crash.
page_scheduler_.reset();
frame_scheduler_->ComputePriority(task_queue.get());
}
TEST_F(FrameSchedulerImplTest,
LowPriorityScriptExecutionHasBestEffortPriority) {
auto task_queue = GetTaskQueue(TaskType::kLowPriorityScriptExecution);
EXPECT_EQ(TaskPriority::kBestEffortPriority, task_queue->GetQueuePriority());
}
namespace {
// Mask is a preferred way of plumbing the list of features, but a list
// is more convenient to read in the tests.
// Here we ensure that these two methods are equivalent.
uint64_t ComputeMaskFromFeatures(FrameSchedulerImpl* frame_scheduler) {
uint64_t result = 0;
for (SchedulingPolicy::Feature feature :
frame_scheduler->GetActiveFeaturesTrackedForBackForwardCacheMetrics()) {
result |= (1 << static_cast<size_t>(feature));
}
return result;
}
} // namespace
TEST_F(FrameSchedulerImplTest, BackForwardCacheOptOut) {
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre());
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
auto feature_handle1 = frame_scheduler_->RegisterFeature(
SchedulingPolicy::Feature::kWebSocket,
{SchedulingPolicy::DisableBackForwardCache()});
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre(SchedulingPolicy::Feature::kWebSocket));
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
auto feature_handle2 = frame_scheduler_->RegisterFeature(
SchedulingPolicy::Feature::kWebRTC,
{SchedulingPolicy::DisableBackForwardCache()});
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre(SchedulingPolicy::Feature::kWebSocket,
SchedulingPolicy::Feature::kWebRTC));
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
feature_handle1.reset();
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre(SchedulingPolicy::Feature::kWebRTC));
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
feature_handle2.reset();
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre());
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
}
TEST_F(FrameSchedulerImplTest, BackForwardCacheOptOut_FrameNavigated) {
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre());
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
auto feature_handle = frame_scheduler_->RegisterFeature(
SchedulingPolicy::Feature::kWebSocket,
{SchedulingPolicy::DisableBackForwardCache()});
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre(SchedulingPolicy::Feature::kWebSocket));
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
frame_scheduler_->RegisterStickyFeature(
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoStore,
{SchedulingPolicy::DisableBackForwardCache()});
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre(
SchedulingPolicy::Feature::kWebSocket,
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoStore));
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
// Same document navigations don't affect anything.
frame_scheduler_->DidCommitProvisionalLoad(
false, FrameScheduler::NavigationType::kSameDocument);
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre(
SchedulingPolicy::Feature::kWebSocket,
SchedulingPolicy::Feature::kMainResourceHasCacheControlNoStore));
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
// Regular navigations reset all features.
frame_scheduler_->DidCommitProvisionalLoad(
false, FrameScheduler::NavigationType::kOther);
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre());
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
// Resetting a feature handle after navigation shouldn't do anything.
feature_handle.reset();
EXPECT_THAT(
frame_scheduler_->GetActiveFeaturesTrackedForBackForwardCacheMetrics(),
testing::UnorderedElementsAre());
EXPECT_EQ(ComputeMaskFromFeatures(frame_scheduler_.get()),
GetActiveFeaturesTrackedForBackForwardCacheMetricsMask(
frame_scheduler_.get()));
}
TEST_F(FrameSchedulerImplTest, FeatureUpload) {
ResetFrameScheduler(/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kMainFrame);
frame_scheduler_->GetTaskRunner(TaskType::kJavascriptTimerImmediate)
->PostTask(
FROM_HERE,
base::BindOnce(
[](FrameSchedulerImpl* frame_scheduler,
testing::StrictMock<FrameSchedulerDelegateForTesting>*
delegate) {
frame_scheduler->RegisterStickyFeature(
SchedulingPolicy::Feature::
kMainResourceHasCacheControlNoStore,
{SchedulingPolicy::DisableBackForwardCache()});
frame_scheduler->RegisterStickyFeature(
SchedulingPolicy::Feature::
kMainResourceHasCacheControlNoCache,
{SchedulingPolicy::DisableBackForwardCache()});
// Ensure that the feature upload is delayed.
testing::Mock::VerifyAndClearExpectations(delegate);
EXPECT_CALL(*delegate, UpdateBackForwardCacheDisablingFeatures(
BlockingDetailsHasCCNS()));
},
frame_scheduler_.get(), frame_scheduler_delegate_.get()));
base::RunLoop().RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(frame_scheduler_delegate_.get());
}
TEST_F(FrameSchedulerImplTest, FeatureUpload_FrameDestruction) {
ResetFrameScheduler(/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kMainFrame);
FeatureHandle feature_handle(frame_scheduler_->RegisterFeature(
SchedulingPolicy::Feature::kWebSocket,
{SchedulingPolicy::DisableBackForwardCache()}));
frame_scheduler_->GetTaskRunner(TaskType::kJavascriptTimerImmediate)
->PostTask(
FROM_HERE,
base::BindOnce(
[](FrameSchedulerImpl* frame_scheduler,
testing::StrictMock<FrameSchedulerDelegateForTesting>*
delegate,
FeatureHandle* feature_handle) {
// Ensure that the feature upload is delayed.
testing::Mock::VerifyAndClearExpectations(delegate);
EXPECT_CALL(*delegate,
UpdateBackForwardCacheDisablingFeatures(
BlockingDetailsHasWebSocket(feature_handle)));
},
frame_scheduler_.get(), frame_scheduler_delegate_.get(),
&feature_handle));
frame_scheduler_->GetTaskRunner(TaskType::kJavascriptTimerImmediate)
->PostTask(FROM_HERE,
base::BindOnce(
[](FrameSchedulerImpl* frame_scheduler,
testing::StrictMock<FrameSchedulerDelegateForTesting>*
delegate,
FeatureHandle* feature_handle) {
feature_handle->reset();
ResetForNavigation(frame_scheduler);
// Ensure that we don't upload the features for frame
// destruction.
testing::Mock::VerifyAndClearExpectations(delegate);
EXPECT_CALL(*delegate,
UpdateBackForwardCacheDisablingFeatures(
BlockingDetailsIsEmpty()))
.Times(0);
},
frame_scheduler_.get(), frame_scheduler_delegate_.get(),
&feature_handle));
base::RunLoop().RunUntilIdle();
testing::Mock::VerifyAndClearExpectations(frame_scheduler_delegate_.get());
}
class WebSchedulingTaskQueueTest : public FrameSchedulerImplTest,
public WebSchedulingTestHelper::Delegate {
public:
void SetUp() override {
FrameSchedulerImplTest::SetUp();
web_scheduling_test_helper_ =
std::make_unique<WebSchedulingTestHelper>(*this);
}
void TearDown() override {
FrameSchedulerImplTest::TearDown();
web_scheduling_test_helper_.reset();
}
FrameOrWorkerScheduler& GetFrameOrWorkerScheduler() override {
return *frame_scheduler_.get();
}
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner(
TaskType task_type) override {
return frame_scheduler_->GetTaskRunner(task_type);
}
protected:
using TestTaskSpecEntry = WebSchedulingTestHelper::TestTaskSpecEntry;
using WebSchedulingParams = WebSchedulingTestHelper::WebSchedulingParams;
std::unique_ptr<WebSchedulingTestHelper> web_scheduling_test_helper_;
};
TEST_F(WebSchedulingTaskQueueTest, TasksRunInPriorityOrder) {
Vector<String> run_order;
Vector<TestTaskSpecEntry> test_spec = {
{.descriptor = "BG1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "BG2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "UV1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UV2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UB1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})},
{.descriptor = "UB2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})}};
web_scheduling_test_helper_->PostTestTasks(&run_order, test_spec);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre("UB1", "UB2", "UV1", "UV2", "BG1", "BG2"));
}
TEST_F(WebSchedulingTaskQueueTest, DynamicTaskPriorityOrder) {
Vector<String> run_order;
Vector<TestTaskSpecEntry> test_spec = {
{.descriptor = "BG1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "BG2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "UV1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UV2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UB1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})},
{.descriptor = "UB2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})}};
web_scheduling_test_helper_->PostTestTasks(&run_order, test_spec);
web_scheduling_test_helper_
->GetWebSchedulingTaskQueue(WebSchedulingQueueType::kTaskQueue,
WebSchedulingPriority::kUserBlockingPriority)
->SetPriority(WebSchedulingPriority::kBackgroundPriority);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre("UV1", "UV2", "BG1", "BG2", "UB1", "UB2"));
}
TEST_F(WebSchedulingTaskQueueTest, DynamicTaskPriorityOrderDelayedTasks) {
Vector<String> run_order;
Vector<TestTaskSpecEntry> test_spec = {
{.descriptor = "UB1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority}),
.delay = base::Milliseconds(5)},
{.descriptor = "UB2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority}),
.delay = base::Milliseconds(5)},
{.descriptor = "UV1",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority}),
.delay = base::Milliseconds(5)},
{.descriptor = "UV2",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority}),
.delay = base::Milliseconds(5)}};
web_scheduling_test_helper_->PostTestTasks(&run_order, test_spec);
web_scheduling_test_helper_
->GetWebSchedulingTaskQueue(WebSchedulingQueueType::kTaskQueue,
WebSchedulingPriority::kUserBlockingPriority)
->SetPriority(WebSchedulingPriority::kBackgroundPriority);
task_environment_.FastForwardBy(base::Milliseconds(5));
EXPECT_THAT(run_order, testing::ElementsAre("UV1", "UV2", "UB1", "UB2"));
}
TEST_F(WebSchedulingTaskQueueTest, TasksAndContinuations) {
Vector<String> run_order;
Vector<TestTaskSpecEntry> test_spec = {
{.descriptor = "BG",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "BG-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "UV",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UV-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UB",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})},
{.descriptor = "UB-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})}};
web_scheduling_test_helper_->PostTestTasks(&run_order, test_spec);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre("UB-C", "UB", "UV-C", "UV", "BG-C", "BG"));
}
TEST_F(WebSchedulingTaskQueueTest, DynamicPriorityContinuations) {
Vector<String> run_order;
Vector<TestTaskSpecEntry> test_spec = {
{.descriptor = "BG-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "UV-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UB-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})}};
web_scheduling_test_helper_->PostTestTasks(&run_order, test_spec);
web_scheduling_test_helper_
->GetWebSchedulingTaskQueue(WebSchedulingQueueType::kContinuationQueue,
WebSchedulingPriority::kUserBlockingPriority)
->SetPriority(WebSchedulingPriority::kBackgroundPriority);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order, testing::ElementsAre("UV-C", "BG-C", "UB-C"));
}
TEST_F(WebSchedulingTaskQueueTest, WebScheduingAndNonWebScheduingTasks) {
Vector<String> run_order;
Vector<TestTaskSpecEntry> test_spec = {
{.descriptor = "Idle",
.type_info = TaskType::kLowPriorityScriptExecution},
{.descriptor = "BG",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "BG-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kBackgroundPriority})},
{.descriptor = "UV",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UV-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kUserVisiblePriority})},
{.descriptor = "UB",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kTaskQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})},
{.descriptor = "UB-C",
.type_info = WebSchedulingParams(
{.queue_type = WebSchedulingQueueType::kContinuationQueue,
.priority = WebSchedulingPriority::kUserBlockingPriority})},
{.descriptor = "Timer",
.type_info = TaskType::kJavascriptTimerDelayedLowNesting},
{.descriptor = "VH1",
.type_info = TaskType::kInternalContinueScriptLoading},
{.descriptor = "VH2",
.type_info = TaskType::kInternalNavigationCancellation}};
web_scheduling_test_helper_->PostTestTasks(&run_order, test_spec);
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_order,
testing::ElementsAre("VH1", "VH2", "UB-C", "UB", "UV-C", "UV",
"Timer", "BG-C", "BG", "Idle"));
}
// Verify that tasks posted with TaskType::kJavascriptTimerDelayed* and
// delayed web scheduling tasks run at the expected time when throttled.
TEST_F(FrameSchedulerImplTest, ThrottledJSTimerTasksRunTime) {
constexpr TaskType kJavaScriptTimerTaskTypes[] = {
TaskType::kJavascriptTimerDelayedLowNesting,
TaskType::kJavascriptTimerDelayedHighNesting,
TaskType::kWebSchedulingPostedTask};
// Snap the time to a multiple of 1 second. Otherwise, the exact run time
// of throttled tasks after hiding the page will vary.
FastForwardToAlignedTime(base::Seconds(1));
const base::TimeTicks start = base::TimeTicks::Now();
// Hide the page to start throttling JS Timers.
page_scheduler_->SetPageVisible(false);
std::map<TaskType, std::vector<base::TimeTicks>> run_times;
// Create the web scheduler task queue outside of the scope of the for loop.
// This is necessary because otherwise the queue is deleted before tasks run,
// and this breaks throttling.
std::unique_ptr<WebSchedulingTaskQueue> web_scheduling_task_queue =
frame_scheduler_->CreateWebSchedulingTaskQueue(
WebSchedulingQueueType::kTaskQueue,
WebSchedulingPriority::kUserVisiblePriority);
// Post tasks with each Javascript Timer Task Type and with a
// WebSchedulingTaskQueue.
for (TaskType task_type : kJavaScriptTimerTaskTypes) {
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
task_type == TaskType::kWebSchedulingPostedTask
? web_scheduling_task_queue->GetTaskRunner()
: frame_scheduler_->GetTaskRunner(task_type);
// Note: Taking the address of an element in |run_times| is safe because
// inserting elements in a map does not invalidate references.
task_runner->PostTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times[task_type]));
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times[task_type]),
base::Milliseconds(1000));
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times[task_type]),
base::Milliseconds(1002));
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times[task_type]),
base::Milliseconds(1004));
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times[task_type]),
base::Milliseconds(2500));
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times[task_type]),
base::Milliseconds(6000));
}
// Make posted tasks run.
task_environment_.FastForwardBy(base::Hours(1));
// The effective delay of a throttled task is >= the requested delay, and is
// within [N * 1000, N * 1000 + 3] ms, where N is an integer. This is because
// the wake up rate is 1 per second, and the duration of each wake up is 3 ms.
for (TaskType task_type : kJavaScriptTimerTaskTypes) {
EXPECT_THAT(run_times[task_type],
testing::ElementsAre(start + base::Milliseconds(0),
start + base::Milliseconds(1000),
start + base::Milliseconds(1002),
start + base::Milliseconds(2000),
start + base::Milliseconds(3000),
start + base::Milliseconds(6000)));
}
}
namespace {
class MockMainThreadScheduler : public MainThreadSchedulerImpl {
public:
explicit MockMainThreadScheduler(
base::test::TaskEnvironment& task_environment)
: MainThreadSchedulerImpl(
base::sequence_manager::SequenceManagerForTest::Create(
nullptr,
task_environment.GetMainThreadTaskRunner(),
task_environment.GetMockTickClock(),
base::sequence_manager::SequenceManager::Settings::Builder()
.SetPrioritySettings(CreatePrioritySettings())
.Build())) {}
MOCK_METHOD(void, OnMainFramePaint, ());
};
} // namespace
TEST_F(FrameSchedulerImplTest, ReportFMPAndFCPForMainFrames) {
MockMainThreadScheduler mock_main_thread_scheduler{task_environment_};
AgentGroupScheduler* agent_group_scheduler =
mock_main_thread_scheduler.CreateAgentGroupScheduler();
std::unique_ptr<PageSchedulerImpl> page_scheduler = CreatePageScheduler(
nullptr, &mock_main_thread_scheduler, *agent_group_scheduler);
std::unique_ptr<FrameSchedulerImpl> main_frame_scheduler =
CreateFrameScheduler(page_scheduler.get(), nullptr,
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kMainFrame);
EXPECT_CALL(mock_main_thread_scheduler, OnMainFramePaint).Times(2);
main_frame_scheduler->OnFirstMeaningfulPaint();
main_frame_scheduler->OnFirstContentfulPaintInMainFrame();
main_frame_scheduler = nullptr;
page_scheduler = nullptr;
agent_group_scheduler = nullptr;
mock_main_thread_scheduler.Shutdown();
}
TEST_F(FrameSchedulerImplTest, DontReportFMPAndFCPForSubframes) {
MockMainThreadScheduler mock_main_thread_scheduler{task_environment_};
AgentGroupScheduler* agent_group_scheduler =
mock_main_thread_scheduler.CreateAgentGroupScheduler();
std::unique_ptr<PageSchedulerImpl> page_scheduler = CreatePageScheduler(
nullptr, &mock_main_thread_scheduler, *agent_group_scheduler);
// Test for direct subframes.
{
std::unique_ptr<FrameSchedulerImpl> subframe_scheduler =
CreateFrameScheduler(page_scheduler.get(), nullptr,
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
EXPECT_CALL(mock_main_thread_scheduler, OnMainFramePaint).Times(0);
subframe_scheduler->OnFirstMeaningfulPaint();
}
// Now test for embedded main frames.
{
std::unique_ptr<FrameSchedulerImpl> subframe_scheduler =
CreateFrameScheduler(page_scheduler.get(), nullptr,
/*is_in_embedded_frame_tree=*/true,
FrameScheduler::FrameType::kMainFrame);
EXPECT_CALL(mock_main_thread_scheduler, OnMainFramePaint).Times(0);
subframe_scheduler->OnFirstMeaningfulPaint();
}
page_scheduler = nullptr;
agent_group_scheduler = nullptr;
mock_main_thread_scheduler.Shutdown();
}
// Verify that tasks run at the expected time in a frame that is same-origin
// with the main frame, on a page that isn't loading when hidden ("quick"
// intensive wake up throttling kicks in).
TEST_P(FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
TaskExecutionSameOriginFrame) {
ASSERT_FALSE(frame_scheduler_->IsCrossOriginToNearestMainFrame());
// Throttled TaskRunner to which tasks are posted in this test.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetTaskRunner();
// Snap the time to a multiple of
// |kIntensiveThrottledWakeUpInterval|. Otherwise, the time at which
// tasks can run after throttling is enabled will vary.
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
const base::TimeTicks test_start = base::TimeTicks::Now();
// Hide the page. This starts the delay to throttle background wake ups.
EXPECT_FALSE(page_scheduler_->IsLoading());
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
// Initially, wake ups are not intensively throttled.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start);
std::vector<base::TimeTicks> run_times;
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
kShortDelay + i * kDefaultThrottledWakeUpInterval);
}
task_environment_.FastForwardBy(kGracePeriod);
EXPECT_THAT(run_times,
testing::ElementsAre(
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + 2 * kDefaultThrottledWakeUpInterval,
scope_start + 3 * kDefaultThrottledWakeUpInterval,
scope_start + 4 * kDefaultThrottledWakeUpInterval,
scope_start + 5 * kDefaultThrottledWakeUpInterval));
}
// After the grace period:
// Test that wake ups are 1-second aligned if there is no recent wake up.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(1));
std::vector<base::TimeTicks> run_times;
// Schedule task to run 1 minute after the last one.
const base::TimeTicks last_task_run_at = test_start + base::Seconds(5);
const base::TimeDelta delay =
last_task_run_at + kIntensiveThrottledWakeUpInterval - scope_start;
EXPECT_EQ(delay, base::Seconds(5));
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times), delay);
task_environment_.FastForwardBy(delay);
EXPECT_THAT(run_times, testing::ElementsAre(scope_start + delay));
}
// Test that if there is a recent wake up:
// TaskType can be intensively throttled: Wake ups are 1-minute aligned
// Otherwise: Wake ups are 1-second aligned
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(1) + base::Seconds(5));
std::vector<base::TimeTicks> run_times;
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
kShortDelay + i * kDefaultThrottledWakeUpInterval);
}
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
if (IsIntensiveThrottlingExpected()) {
const base::TimeTicks aligned_time = scope_start + base::Seconds(55);
EXPECT_EQ(aligned_time.SnappedToNextTick(
base::TimeTicks(), kIntensiveThrottledWakeUpInterval),
aligned_time);
EXPECT_THAT(run_times,
testing::ElementsAre(aligned_time, aligned_time, aligned_time,
aligned_time, aligned_time));
} else {
EXPECT_THAT(run_times,
testing::ElementsAre(scope_start + base::Seconds(1),
scope_start + base::Seconds(2),
scope_start + base::Seconds(3),
scope_start + base::Seconds(4),
scope_start + base::Seconds(5)));
}
}
// Post an extra task with a short delay. The wake up should be 1-minute
// aligned if the TaskType supports intensive throttling, or 1-second aligned
// otherwise.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(2));
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval);
task_environment_.FastForwardBy(kIntensiveThrottledWakeUpInterval);
EXPECT_THAT(run_times, testing::ElementsAre(scope_start +
GetExpectedWakeUpInterval()));
}
// Post an extra task with a delay longer than the intensive throttling wake
// up interval. The wake up should be 1-second aligned, even if the TaskType
// supports intensive throttling, because there was no wake up in the last
// minute.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(3));
std::vector<base::TimeTicks> run_times;
const base::TimeDelta kLongDelay =
kIntensiveThrottledWakeUpInterval * 5 + kDefaultThrottledWakeUpInterval;
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times), kLongDelay);
task_environment_.FastForwardBy(kLongDelay);
EXPECT_THAT(run_times, testing::ElementsAre(scope_start + kLongDelay));
}
// Post tasks with short delays after the page communicated with the user in
// background. Tasks should be 1-second aligned for 3 seconds. After that, if
// the TaskType supports intensive throttling, wake ups should be 1-minute
// aligned.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start,
test_start + base::Minutes(8) + kDefaultThrottledWakeUpInterval);
std::vector<base::TimeTicks> run_times;
page_scheduler_->OnTitleOrFaviconUpdated();
task_runner->PostDelayedTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
RecordRunTime(&run_times);
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval * (i + 1));
}
}),
kDefaultThrottledWakeUpInterval);
task_environment_.FastForwardUntilNoTasksRemain();
if (IsIntensiveThrottlingExpected()) {
EXPECT_THAT(
run_times,
testing::ElementsAre(
scope_start + base::Seconds(1), scope_start + base::Seconds(2),
scope_start + base::Seconds(3),
scope_start - kDefaultThrottledWakeUpInterval + base::Minutes(1),
scope_start - kDefaultThrottledWakeUpInterval + base::Minutes(1),
scope_start - kDefaultThrottledWakeUpInterval +
base::Minutes(1)));
} else {
EXPECT_THAT(
run_times,
testing::ElementsAre(
scope_start + base::Seconds(1), scope_start + base::Seconds(2),
scope_start + base::Seconds(3), scope_start + base::Seconds(4),
scope_start + base::Seconds(5), scope_start + base::Seconds(6)));
}
}
}
// Verify that tasks run at the expected time in a frame that is cross-origin
// with the main frame with intensive wake up throttling.
TEST_P(FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
TaskExecutionCrossOriginFrame) {
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
// Throttled TaskRunner to which tasks are posted in this test.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetTaskRunner();
// Snap the time to a multiple of
// |kIntensiveThrottledWakeUpInterval|. Otherwise, the time at which
// tasks can run after throttling is enabled will vary.
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
const base::TimeTicks test_start = base::TimeTicks::Now();
// Hide the page. This starts the delay to throttle background wake ups.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
// Initially, wake ups are not intensively throttled.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start);
std::vector<base::TimeTicks> run_times;
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
kShortDelay + i * kDefaultThrottledWakeUpInterval);
}
task_environment_.FastForwardBy(kGracePeriod);
EXPECT_THAT(run_times,
testing::ElementsAre(scope_start + base::Seconds(1),
scope_start + base::Seconds(2),
scope_start + base::Seconds(3),
scope_start + base::Seconds(4),
scope_start + base::Seconds(5)));
}
// After the grace period:
// Test posting a task when there is no recent wake up. The wake up should be
// 1-minute aligned if the TaskType supports intensive throttling (in a main
// frame, it would have been 1-second aligned since there was no wake up in
// the last minute). Otherwise, it should be 1-second aligned.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(1));
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval);
task_environment_.FastForwardBy(kIntensiveThrottledWakeUpInterval);
EXPECT_THAT(run_times, testing::ElementsAre(scope_start +
GetExpectedWakeUpInterval()));
}
// Test posting many tasks with short delays. Wake ups should be 1-minute
// aligned if the TaskType supports intensive throttling, or 1-second aligned
// otherwise.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(2));
std::vector<base::TimeTicks> run_times;
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
kShortDelay + i * kDefaultThrottledWakeUpInterval);
}
task_environment_.FastForwardBy(kIntensiveThrottledWakeUpInterval);
if (IsIntensiveThrottlingExpected()) {
const base::TimeTicks aligned_time =
scope_start + kIntensiveThrottledWakeUpInterval;
EXPECT_THAT(run_times,
testing::ElementsAre(aligned_time, aligned_time, aligned_time,
aligned_time, aligned_time));
} else {
EXPECT_THAT(run_times,
testing::ElementsAre(scope_start + base::Seconds(1),
scope_start + base::Seconds(2),
scope_start + base::Seconds(3),
scope_start + base::Seconds(4),
scope_start + base::Seconds(5)));
}
}
// Post an extra task with a short delay. Wake ups should be 1-minute aligned
// if the TaskType supports intensive throttling, or 1-second aligned
// otherwise.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(3));
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval);
task_environment_.FastForwardBy(kIntensiveThrottledWakeUpInterval);
EXPECT_THAT(run_times, testing::ElementsAre(scope_start +
GetExpectedWakeUpInterval()));
}
// Post an extra task with a delay longer than the intensive throttling wake
// up interval. The wake up should be 1-minute aligned if the TaskType
// supports intensive throttling (in a main frame, it would have been 1-second
// aligned because there was no wake up in the last minute). Otherwise, it
// should be 1-second aligned.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(4));
std::vector<base::TimeTicks> run_times;
const base::TimeDelta kLongDelay = kIntensiveThrottledWakeUpInterval * 6;
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kLongDelay - kShortDelay);
task_environment_.FastForwardBy(kLongDelay);
EXPECT_THAT(run_times, testing::ElementsAre(scope_start + kLongDelay));
}
// Post tasks with short delays after the page communicated with the user in
// background. Wake ups should be 1-minute aligned if the TaskType supports
// intensive throttling, since cross-origin frames are not affected by title
// or favicon update. Otherwise, they should be 1-second aligned.
{
const base::TimeTicks scope_start = base::TimeTicks::Now();
EXPECT_EQ(scope_start, test_start + base::Minutes(10));
std::vector<base::TimeTicks> run_times;
page_scheduler_->OnTitleOrFaviconUpdated();
task_runner->PostDelayedTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
RecordRunTime(&run_times);
for (int i = 0; i < kNumTasks; ++i) {
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval * (i + 1));
}
page_scheduler_->OnTitleOrFaviconUpdated();
}),
kDefaultThrottledWakeUpInterval);
task_environment_.FastForwardUntilNoTasksRemain();
if (IsIntensiveThrottlingExpected()) {
EXPECT_THAT(
run_times,
testing::ElementsAre(
scope_start + base::Minutes(1), scope_start + base::Minutes(2),
scope_start + base::Minutes(2), scope_start + base::Minutes(2),
scope_start + base::Minutes(2), scope_start + base::Minutes(2)));
} else {
EXPECT_THAT(
run_times,
testing::ElementsAre(
scope_start + base::Seconds(1), scope_start + base::Seconds(2),
scope_start + base::Seconds(3), scope_start + base::Seconds(4),
scope_start + base::Seconds(5), scope_start + base::Seconds(6)));
}
}
}
// Verify that tasks from different frames that are same-origin with the main
// frame run at the expected time.
TEST_P(FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
ManySameOriginFrames) {
ASSERT_FALSE(frame_scheduler_->IsCrossOriginToNearestMainFrame());
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetTaskRunner();
// Create a FrameScheduler that is same-origin with the main frame, and an
// associated throttled TaskRunner.
std::unique_ptr<FrameSchedulerImpl> other_frame_scheduler =
CreateFrameScheduler(page_scheduler_.get(),
frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
ASSERT_FALSE(other_frame_scheduler->IsCrossOriginToNearestMainFrame());
const scoped_refptr<base::SingleThreadTaskRunner> other_task_runner =
GetTaskRunner(other_frame_scheduler.get());
// Snap the time to a multiple of
// |kIntensiveThrottledWakeUpInterval|. Otherwise, the time at which
// tasks can run after throttling is enabled will vary.
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
// Hide the page and wait until the intensive throttling grace period has
// elapsed.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
task_environment_.FastForwardBy(kGracePeriod);
// Post tasks in both frames, with delays shorter than the intensive wake up
// interval.
const base::TimeTicks post_time = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval + kShortDelay);
other_task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
2 * kDefaultThrottledWakeUpInterval + kShortDelay);
task_environment_.FastForwardUntilNoTasksRemain();
// The first task is 1-second aligned, because there was no wake up in the
// last minute. The second task is 1-minute aligned if the TaskType supports
// intensive throttling, or 1-second aligned otherwise.
if (IsIntensiveThrottlingExpected()) {
EXPECT_THAT(run_times, testing::ElementsAre(
post_time + 2 * kDefaultThrottledWakeUpInterval,
post_time + kIntensiveThrottledWakeUpInterval));
} else {
EXPECT_THAT(
run_times,
testing::ElementsAre(post_time + 2 * kDefaultThrottledWakeUpInterval,
post_time + 3 * kDefaultThrottledWakeUpInterval));
}
}
// Verify that intensive wake up throttling starts after 5 minutes instead of 1
// minute if the page is loading when hidden.
TEST_P(FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
TaskExecutionPageLoadingWhenHidden) {
ASSERT_FALSE(frame_scheduler_->IsCrossOriginToNearestMainFrame());
// Throttled TaskRunner to which tasks are posted in this test.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetTaskRunner();
// Snap the time to a multiple of
// |kIntensiveThrottledWakeUpInterval|. Otherwise, the time at which
// tasks can run after throttling is enabled will vary.
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
const base::TimeTicks test_start = base::TimeTicks::Now();
// Create a main frame and simulate a load in it.
std::unique_ptr<FrameSchedulerImpl> main_frame_scheduler =
CreateFrameScheduler(page_scheduler_.get(),
frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kMainFrame);
main_frame_scheduler->DidCommitProvisionalLoad(
/*is_web_history_inert_commit=*/false,
/*navigation_type=*/FrameScheduler::NavigationType::kOther);
EXPECT_TRUE(page_scheduler_->IsLoading());
// Hide the page. This starts the delay to throttle background wake ups.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
// Wake ups are only "intensively" throttled after 5 minutes.
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times), base::Seconds(59));
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Seconds(297));
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Seconds(298));
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Seconds(300));
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Seconds(301));
task_environment_.FastForwardBy(base::Minutes(7));
if (IsIntensiveThrottlingExpected()) {
EXPECT_THAT(run_times, testing::ElementsAre(test_start + base::Seconds(59),
test_start + base::Seconds(297),
test_start + base::Seconds(298),
test_start + base::Seconds(300),
test_start + base::Minutes(6)));
} else {
EXPECT_THAT(run_times,
testing::ElementsAre(test_start + base::Seconds(59),
test_start + base::Seconds(297),
test_start + base::Seconds(298),
test_start + base::Seconds(300),
test_start + base::Seconds(301)));
}
}
// Verify that intensive throttling is disabled when there is an opt-out.
TEST_P(FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
AggressiveThrottlingOptOut) {
constexpr int kNumTasks = 3;
// |task_runner| is throttled.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetTaskRunner();
// |other_task_runner| is throttled. It belongs to a different frame on the
// same page.
const auto other_frame_scheduler = CreateFrameScheduler(
page_scheduler_.get(), frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
const scoped_refptr<base::SingleThreadTaskRunner> other_task_runner =
GetTaskRunner(other_frame_scheduler.get());
// Fast-forward the time to a multiple of
// |kIntensiveThrottledWakeUpInterval|. Otherwise,
// the time at which tasks can run after throttling is enabled will vary.
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
// Hide the page and wait until the intensive throttling grace period has
// elapsed.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
task_environment_.FastForwardBy(kGracePeriod);
{
// Wake ups are intensively throttled, since there is no throttling opt-out.
const base::TimeTicks scope_start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times), kShortDelay);
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval + kShortDelay);
task_environment_.FastForwardUntilNoTasksRemain();
if (IsIntensiveThrottlingExpected()) {
// Note: Intensive throttling is not applied on the 1st task since there
// is no recent wake up.
EXPECT_THAT(run_times,
testing::ElementsAre(
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + kIntensiveThrottledWakeUpInterval));
} else {
EXPECT_THAT(run_times,
testing::ElementsAre(
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + 2 * kDefaultThrottledWakeUpInterval));
}
}
{
// Create an opt-out.
auto handle = frame_scheduler_->RegisterFeature(
SchedulingPolicy::Feature::kWebRTC,
{SchedulingPolicy::DisableAggressiveThrottling()});
{
// Tasks should run after |kDefaultThrottledWakeUpInterval|, since
// aggressive throttling is disabled, but default wake up throttling
// remains enabled.
const base::TimeTicks scope_start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
for (int i = 1; i < kNumTasks + 1; ++i) {
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
i * kShortDelay);
}
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_THAT(
run_times,
testing::ElementsAre(scope_start + kDefaultThrottledWakeUpInterval,
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + kDefaultThrottledWakeUpInterval));
}
{
// Same thing for another frame on the same page.
const base::TimeTicks scope_start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
for (int i = 1; i < kNumTasks + 1; ++i) {
other_task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
i * kShortDelay);
}
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_THAT(
run_times,
testing::ElementsAre(scope_start + kDefaultThrottledWakeUpInterval,
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + kDefaultThrottledWakeUpInterval));
}
}
// Fast-forward so that there is no recent wake up. Then, align the time on
// |kIntensiveThrottledWakeUpInterval| to simplify expectations.
task_environment_.FastForwardBy(kIntensiveThrottledWakeUpInterval);
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
{
// Wake ups are intensively throttled, since there is no throttling opt-out.
const base::TimeTicks scope_start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times), kShortDelay);
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
kDefaultThrottledWakeUpInterval + kShortDelay);
task_environment_.FastForwardUntilNoTasksRemain();
if (IsIntensiveThrottlingExpected()) {
// Note: Intensive throttling is not applied on the 1st task since there
// is no recent wake up.
EXPECT_THAT(run_times,
testing::ElementsAre(
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + kIntensiveThrottledWakeUpInterval));
} else {
EXPECT_THAT(run_times,
testing::ElementsAre(
scope_start + kDefaultThrottledWakeUpInterval,
scope_start + 2 * kDefaultThrottledWakeUpInterval));
}
}
}
// Verify that tasks run at the same time when a frame switches between being
// same-origin and cross-origin with the main frame.
TEST_P(FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
FrameChangesOriginType) {
EXPECT_FALSE(frame_scheduler_->IsCrossOriginToNearestMainFrame());
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
GetTaskRunner();
// Create a new FrameScheduler that remains cross-origin with the main frame
// throughout the test.
std::unique_ptr<FrameSchedulerImpl> cross_origin_frame_scheduler =
CreateFrameScheduler(page_scheduler_.get(),
frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
cross_origin_frame_scheduler->SetCrossOriginToNearestMainFrame(true);
const scoped_refptr<base::SingleThreadTaskRunner> cross_origin_task_runner =
GetTaskRunner(cross_origin_frame_scheduler.get());
// Snap the time to a multiple of
// |kIntensiveThrottledWakeUpInterval|. Otherwise, the time at which
// tasks can run after throttling is enabled will vary.
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
// Hide the page and wait until the intensive throttling grace period has
// elapsed.
EXPECT_TRUE(page_scheduler_->IsPageVisible());
page_scheduler_->SetPageVisible(false);
task_environment_.FastForwardBy(kGracePeriod);
{
// Post delayed tasks with short delays to both frames. The
// main-frame-origin task can run at the desired time, because there is no
// recent wake up. The cross-origin task must run at an aligned time.
int counter = 0;
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter, base::Unretained(&counter)),
kDefaultThrottledWakeUpInterval);
int cross_origin_counter = 0;
cross_origin_task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter,
base::Unretained(&cross_origin_counter)),
kDefaultThrottledWakeUpInterval);
// Make the |frame_scheduler_| cross-origin. Its task must now run at an
// aligned time.
frame_scheduler_->SetCrossOriginToNearestMainFrame(true);
task_environment_.FastForwardBy(kDefaultThrottledWakeUpInterval);
if (IsIntensiveThrottlingExpected()) {
EXPECT_EQ(0, counter);
EXPECT_EQ(0, cross_origin_counter);
} else {
EXPECT_EQ(1, counter);
EXPECT_EQ(1, cross_origin_counter);
}
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
EXPECT_EQ(1, counter);
EXPECT_EQ(1, cross_origin_counter);
}
{
// Post delayed tasks with long delays that aren't aligned with the wake up
// interval. They should run at aligned times, since they are cross-origin.
const base::TimeDelta kLongUnalignedDelay =
5 * kIntensiveThrottledWakeUpInterval + kDefaultThrottledWakeUpInterval;
int counter = 0;
task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter, base::Unretained(&counter)),
kLongUnalignedDelay);
int cross_origin_counter = 0;
cross_origin_task_runner->PostDelayedTask(
FROM_HERE,
base::BindOnce(&IncrementCounter,
base::Unretained(&cross_origin_counter)),
kLongUnalignedDelay);
// Make the |frame_scheduler_| same-origin. Its task can now run at a
// 1-second aligned time, since there was no wake up in the last minute.
frame_scheduler_->SetCrossOriginToNearestMainFrame(false);
task_environment_.FastForwardBy(kLongUnalignedDelay);
if (IsIntensiveThrottlingExpected()) {
EXPECT_EQ(1, counter);
EXPECT_EQ(0, cross_origin_counter);
} else {
EXPECT_EQ(1, counter);
EXPECT_EQ(1, cross_origin_counter);
}
FastForwardToAlignedTime(kIntensiveThrottledWakeUpInterval);
EXPECT_EQ(1, counter);
EXPECT_EQ(1, cross_origin_counter);
}
}
INSTANTIATE_TEST_SUITE_P(
AllTimerTaskTypes,
FrameSchedulerImplTestWithIntensiveWakeUpThrottling,
testing::Values(
IntensiveWakeUpThrottlingTestParam{
/* task_type=*/TaskType::kJavascriptTimerDelayedLowNesting,
/* is_intensive_throttling_expected=*/false},
IntensiveWakeUpThrottlingTestParam{
/* task_type=*/TaskType::kJavascriptTimerDelayedHighNesting,
/* is_intensive_throttling_expected=*/true},
IntensiveWakeUpThrottlingTestParam{
/* task_type=*/TaskType::kWebSchedulingPostedTask,
/* is_intensive_throttling_expected=*/true}),
[](const testing::TestParamInfo<IntensiveWakeUpThrottlingTestParam>& info) {
return TaskTypeNames::TaskTypeToString(info.param.task_type);
});
TEST_F(FrameSchedulerImplTestWithIntensiveWakeUpThrottlingPolicyOverride,
PolicyForceEnable) {
SetPolicyOverride(/* enabled = */ true);
EXPECT_TRUE(IsIntensiveWakeUpThrottlingEnabled());
// The parameters should be the defaults.
EXPECT_EQ(
base::Seconds(kIntensiveWakeUpThrottling_GracePeriodSeconds_Default),
GetIntensiveWakeUpThrottlingGracePeriod(false));
}
TEST_F(FrameSchedulerImplTestWithIntensiveWakeUpThrottlingPolicyOverride,
PolicyForceDisable) {
SetPolicyOverride(/* enabled = */ false);
EXPECT_FALSE(IsIntensiveWakeUpThrottlingEnabled());
}
class FrameSchedulerImplTestQuickIntensiveWakeUpThrottlingEnabled
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplTestQuickIntensiveWakeUpThrottlingEnabled()
: FrameSchedulerImplTest(
{features::kQuickIntensiveWakeUpThrottlingAfterLoading},
{}) {}
};
TEST_F(FrameSchedulerImplTestQuickIntensiveWakeUpThrottlingEnabled,
LoadingPageGracePeriod) {
EXPECT_EQ(
base::Seconds(kIntensiveWakeUpThrottling_GracePeriodSeconds_Default),
GetIntensiveWakeUpThrottlingGracePeriod(true));
}
TEST_F(FrameSchedulerImplTestQuickIntensiveWakeUpThrottlingEnabled,
LoadedPageGracePeriod) {
EXPECT_EQ(base::Seconds(
kIntensiveWakeUpThrottling_GracePeriodSecondsLoaded_Default),
GetIntensiveWakeUpThrottlingGracePeriod(false));
}
// Verify that non-delayed kWebSchedulingPostedTask tasks are not throttled.
TEST_F(FrameSchedulerImplTest, ImmediateWebSchedulingTasksAreNotThrottled) {
std::vector<base::TimeTicks> run_times;
// Make sure we are *not* aligned to a 1 second boundary by aligning to a 1
// second boundary and moving past it a bit. If we were throttled, even
// non-delayed tasks will need to wait until the next aligned interval to run.
FastForwardToAlignedTime(base::Seconds(1));
task_environment_.FastForwardBy(base::Milliseconds(1));
const base::TimeTicks start = base::TimeTicks::Now();
// Hide the page to start throttling timers.
page_scheduler_->SetPageVisible(false);
// Post a non-delayed task to a web scheduling task queue.
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_
->CreateWebSchedulingTaskQueue(
WebSchedulingQueueType::kTaskQueue,
WebSchedulingPriority::kUserVisiblePriority)
->GetTaskRunner();
task_runner->PostTask(FROM_HERE, base::BindOnce(&RecordRunTime, &run_times));
// Run any ready tasks, which includes our non-delayed non-throttled web
// scheduling task. If we are throttled, our task will not run.
base::RunLoop().RunUntilIdle();
EXPECT_THAT(run_times, testing::ElementsAre(start));
}
class FrameSchedulerImplThrottleForegroundTimersEnabledTest
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplThrottleForegroundTimersEnabledTest()
: FrameSchedulerImplTest({features::kThrottleForegroundTimers}, {}) {}
};
TEST_F(FrameSchedulerImplThrottleForegroundTimersEnabledTest,
ForegroundPageTimerThrottling) {
page_scheduler_->SetPageVisible(true);
// Snap the time to a multiple of 32ms.
FastForwardToAlignedTime(base::Milliseconds(32));
const base::TimeTicks start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(
TaskType::kJavascriptTimerDelayedLowNesting);
for (int i = 0; i < 5; i++) {
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Milliseconds(50) * i);
}
// Make posted tasks run.
task_environment_.FastForwardBy(base::Seconds(5));
EXPECT_THAT(run_times,
testing::ElementsAre(start, start + base::Milliseconds(64),
start + base::Milliseconds(128),
start + base::Milliseconds(160),
start + base::Milliseconds(224)));
}
// Make sure the normal throttling (1 wake up per second) is applied when the
// page becomes non-visible
TEST_F(FrameSchedulerImplThrottleForegroundTimersEnabledTest,
BackgroundPageTimerThrottling) {
page_scheduler_->SetPageVisible(false);
// Snap the time to a multiple of 1 second.
FastForwardToAlignedTime(base::Seconds(1));
const base::TimeTicks start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(
TaskType::kJavascriptTimerDelayedLowNesting);
for (int i = 0; i < 5; i++) {
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Milliseconds(50) * i);
}
// Make posted tasks run.
task_environment_.FastForwardBy(base::Seconds(5));
EXPECT_THAT(run_times,
testing::ElementsAre(start, start + base::Milliseconds(1000),
start + base::Milliseconds(1000),
start + base::Milliseconds(1000),
start + base::Milliseconds(1000)));
}
TEST_F(FrameSchedulerImplThrottleForegroundTimersEnabledTest,
HiddenAudiblePageTimerThrottling) {
page_scheduler_->SetPageVisible(false);
page_scheduler_->AudioStateChanged(/*is_audio_playing=*/true);
// Snap the time to a multiple of 32ms.
FastForwardToAlignedTime(base::Milliseconds(32));
const base::TimeTicks start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
const scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(
TaskType::kJavascriptTimerDelayedLowNesting);
for (int i = 0; i < 5; i++) {
task_runner->PostDelayedTask(FROM_HERE,
base::BindOnce(&RecordRunTime, &run_times),
base::Milliseconds(50) * i);
}
// Make posted tasks run.
task_environment_.FastForwardBy(base::Seconds(5));
EXPECT_THAT(run_times,
testing::ElementsAre(start, start + base::Milliseconds(64),
start + base::Milliseconds(128),
start + base::Milliseconds(160),
start + base::Milliseconds(224)));
}
TEST_F(FrameSchedulerImplThrottleForegroundTimersEnabledTest,
VisibleCrossOriginFrameThrottling) {
std::unique_ptr<FrameSchedulerImpl> cross_origin_frame_scheduler =
CreateFrameScheduler(page_scheduler_.get(),
frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
page_scheduler_->SetPageVisible(true);
cross_origin_frame_scheduler->SetCrossOriginToNearestMainFrame(true);
const scoped_refptr<base::SingleThreadTaskRunner> cross_origin_task_runner =
cross_origin_frame_scheduler->GetTaskRunner(
TaskType::kJavascriptTimerDelayedLowNesting);
cross_origin_frame_scheduler->SetFrameVisible(true);
// Snap the time to a multiple of 32ms.
FastForwardToAlignedTime(base::Milliseconds(32));
const base::TimeTicks start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
for (int i = 0; i < 5; i++) {
cross_origin_task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
base::Milliseconds(50) * i);
}
// Make posted tasks run.
task_environment_.FastForwardBy(base::Seconds(5));
EXPECT_THAT(run_times,
testing::ElementsAre(start, start + base::Milliseconds(64),
start + base::Milliseconds(128),
start + base::Milliseconds(160),
start + base::Milliseconds(224)));
}
TEST_F(FrameSchedulerImplThrottleForegroundTimersEnabledTest,
HiddenCrossOriginFrameThrottling) {
std::unique_ptr<FrameSchedulerImpl> cross_origin_frame_scheduler =
CreateFrameScheduler(page_scheduler_.get(),
frame_scheduler_delegate_.get(),
/*is_in_embedded_frame_tree=*/false,
FrameScheduler::FrameType::kSubframe);
page_scheduler_->SetPageVisible(true);
cross_origin_frame_scheduler->SetCrossOriginToNearestMainFrame(true);
const scoped_refptr<base::SingleThreadTaskRunner> cross_origin_task_runner =
cross_origin_frame_scheduler->GetTaskRunner(
TaskType::kJavascriptTimerDelayedLowNesting);
cross_origin_frame_scheduler->SetFrameVisible(false);
// Snap the time to a multiple of 1 second.
FastForwardToAlignedTime(base::Seconds(1));
const base::TimeTicks start = base::TimeTicks::Now();
std::vector<base::TimeTicks> run_times;
for (int i = 0; i < 5; i++) {
cross_origin_task_runner->PostDelayedTask(
FROM_HERE, base::BindOnce(&RecordRunTime, &run_times),
base::Milliseconds(50) * i);
}
// Make posted tasks run.
task_environment_.FastForwardBy(base::Seconds(5));
EXPECT_THAT(run_times,
testing::ElementsAre(start, start + base::Milliseconds(1000),
start + base::Milliseconds(1000),
start + base::Milliseconds(1000),
start + base::Milliseconds(1000)));
}
TEST_F(FrameSchedulerImplTest, PostMessageForwardingHasVeryHighPriority) {
auto task_queue = GetTaskQueue(TaskType::kInternalPostMessageForwarding);
EXPECT_EQ(TaskPriority::kVeryHighPriority, task_queue->GetQueuePriority());
}
class FrameSchedulerImplDeleterTaskRunnerEnabledTest
: public FrameSchedulerImplTest {
public:
FrameSchedulerImplDeleterTaskRunnerEnabledTest()
: FrameSchedulerImplTest(
{blink::features::kUseBlinkSchedulerTaskRunnerWithCustomDeleter},
{}) {}
};
TEST_F(FrameSchedulerImplDeleterTaskRunnerEnabledTest,
DeleteSoonUsesBackupTaskRunner) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(TaskType::kInternalTest);
int counter = 0;
task_runner->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
frame_scheduler_.reset();
EXPECT_EQ(0, counter);
// Because of graceful shutdown, the increment task should run since it was
// queued. Since it's empty after the task finishes, the queue should then be
// shut down.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, counter);
std::unique_ptr<TestObject> test_object =
std::make_unique<TestObject>(&counter);
task_runner->DeleteSoon(FROM_HERE, std::move(test_object));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, counter);
}
enum class DeleterTaskRunnerEnabled { kEnabled, kDisabled };
class FrameSchedulerImplTaskRunnerWithCustomDeleterTest
: public FrameSchedulerImplTest,
public ::testing::WithParamInterface<DeleterTaskRunnerEnabled> {
public:
FrameSchedulerImplTaskRunnerWithCustomDeleterTest() {
feature_list_.Reset();
if (GetParam() == DeleterTaskRunnerEnabled::kEnabled) {
feature_list_.InitWithFeatures(
{blink::features::kUseBlinkSchedulerTaskRunnerWithCustomDeleter}, {});
} else {
feature_list_.InitWithFeatures(
{}, {blink::features::kUseBlinkSchedulerTaskRunnerWithCustomDeleter});
}
}
};
TEST_P(FrameSchedulerImplTaskRunnerWithCustomDeleterTest,
DeleteSoonAfterShutdown) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
frame_scheduler_->GetTaskRunner(TaskType::kInternalTest);
int counter = 0;
// Deleting before shutdown should always work.
std::unique_ptr<TestObject> test_object1 =
std::make_unique<TestObject>(&counter);
task_runner->DeleteSoon(FROM_HERE, std::move(test_object1));
EXPECT_EQ(counter, 0);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(counter, 1);
task_runner->PostTask(
FROM_HERE, base::BindOnce(&IncrementCounter, base::Unretained(&counter)));
frame_scheduler_.reset();
EXPECT_EQ(counter, 1);
// Because of graceful shutdown, the increment task should run since it was
// queued. Since it's empty after the task finishes, the queue should then be
// shut down.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(counter, 2);
std::unique_ptr<TestObject> test_object2 =
std::make_unique<TestObject>(&counter);
TestObject* unowned_test_object2 = test_object2.get();
task_runner->DeleteSoon(FROM_HERE, std::move(test_object2));
EXPECT_EQ(counter, 2);
base::RunLoop().RunUntilIdle();
// Without the custom task runner, this leaks.
if (GetParam() == DeleterTaskRunnerEnabled::kDisabled) {
EXPECT_EQ(counter, 2);
delete (unowned_test_object2);
} else {
EXPECT_EQ(counter, 3);
}
}
INSTANTIATE_TEST_SUITE_P(
,
FrameSchedulerImplTaskRunnerWithCustomDeleterTest,
testing::Values(DeleterTaskRunnerEnabled::kEnabled,
DeleterTaskRunnerEnabled::kDisabled),
[](const testing::TestParamInfo<DeleterTaskRunnerEnabled>& info) {
switch (info.param) {
case DeleterTaskRunnerEnabled::kEnabled:
return "Enabled";
case DeleterTaskRunnerEnabled::kDisabled:
return "Disabled";
}
});
} // namespace frame_scheduler_impl_unittest
} // namespace scheduler
} // namespace blink