blob: d2aa32e71a55cd5660667da7dec1c4291a3d3f23 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/loader/fetch/resource_load_scheduler.h"
#include <memory>
#include "base/test/scoped_feature_list.h"
#include "base/test/test_mock_time_task_runner.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/loader/loading_behavior_flag.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/loader/fetch/console_logger.h"
#include "third_party/blink/renderer/platform/loader/fetch/loading_behavior_observer.h"
#include "third_party/blink/renderer/platform/loader/testing/test_resource_fetcher_properties.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/test/fake_frame_scheduler.h"
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
namespace blink {
namespace {
class MockClient final : public GarbageCollected<MockClient>,
public ResourceLoadSchedulerClient {
public:
// A delegate that can be used to determine the order clients were run in.
class MockClientDelegate {
DISALLOW_NEW();
public:
MockClientDelegate() = default;
~MockClientDelegate() = default;
void NotifyRun(MockClient* client) { client_order_.push_back(client); }
// The call order that hte clients ran in.
const HeapVector<Member<MockClient>>& client_order() {
return client_order_;
}
void Trace(Visitor* visitor) const { visitor->Trace(client_order_); }
private:
HeapVector<Member<MockClient>> client_order_;
};
~MockClient() = default;
void SetDelegate(MockClientDelegate* delegate) { delegate_ = delegate; }
void Run() override {
if (delegate_)
delegate_->NotifyRun(this);
EXPECT_FALSE(was_run_);
was_run_ = true;
}
bool WasRun() { return was_run_; }
void Trace(Visitor* visitor) const override {
ResourceLoadSchedulerClient::Trace(visitor);
visitor->Trace(console_logger_);
}
private:
Member<DetachableConsoleLogger> console_logger_ =
MakeGarbageCollected<DetachableConsoleLogger>();
MockClientDelegate* delegate_;
bool was_run_ = false;
};
class LoadingBehaviorObserverImpl final
: public GarbageCollected<LoadingBehaviorObserverImpl>,
public LoadingBehaviorObserver {
public:
void DidObserveLoadingBehavior(LoadingBehaviorFlag behavior) override {
loading_behavior_flag_ |= behavior;
}
int32_t loading_behavior_flag() const { return loading_behavior_flag_; }
private:
int32_t loading_behavior_flag_ = 0;
};
class ResourceLoadSchedulerTestBase : public testing::Test {
public:
class MockConsoleLogger final : public GarbageCollected<MockConsoleLogger>,
public ConsoleLogger {
public:
bool HasMessage() const { return has_message_; }
private:
void AddConsoleMessageImpl(mojom::ConsoleMessageSource,
mojom::ConsoleMessageLevel,
const String&,
bool discard_duplicates) override {
has_message_ = true;
}
bool has_message_ = false;
};
using ThrottleOption = ResourceLoadScheduler::ThrottleOption;
void SetUp() override {
auto* properties = MakeGarbageCollected<TestResourceFetcherProperties>();
properties->SetShouldBlockLoadingSubResource(true);
auto frame_scheduler = std::make_unique<scheduler::FakeFrameScheduler>();
console_logger_ = MakeGarbageCollected<MockConsoleLogger>();
loading_observer_behavior_ =
MakeGarbageCollected<LoadingBehaviorObserverImpl>();
scheduler_ = MakeGarbageCollected<ResourceLoadScheduler>(
ResourceLoadScheduler::ThrottlingPolicy::kTight,
ResourceLoadScheduler::ThrottleOptionOverride::kNone,
properties->MakeDetachable(), frame_scheduler.get(),
*MakeGarbageCollected<DetachableConsoleLogger>(console_logger_),
loading_observer_behavior_.Get());
scheduler_->SetOptimizationGuideHints(std::move(optimization_hints_));
Scheduler()->SetOutstandingLimitForTesting(1);
}
void TearDown() override { Scheduler()->Shutdown(); }
MockConsoleLogger* GetConsoleLogger() { return console_logger_; }
ResourceLoadScheduler* Scheduler() { return scheduler_; }
bool Release(ResourceLoadScheduler::ClientId client) {
return Scheduler()->Release(
client, ResourceLoadScheduler::ReleaseOption::kReleaseOnly,
ResourceLoadScheduler::TrafficReportHints::InvalidInstance());
}
bool ReleaseAndSchedule(ResourceLoadScheduler::ClientId client) {
return Scheduler()->Release(
client, ResourceLoadScheduler::ReleaseOption::kReleaseAndSchedule,
ResourceLoadScheduler::TrafficReportHints::InvalidInstance());
}
bool WasDelayCompetingLowPriorityRequestsObserved() {
return loading_observer_behavior_->loading_behavior_flag() &
kLoadingBehaviorCompetingLowPriorityRequestsDelayed;
}
protected:
base::test::ScopedFeatureList feature_list_;
Persistent<MockConsoleLogger> console_logger_;
Persistent<LoadingBehaviorObserverImpl> loading_observer_behavior_;
Persistent<ResourceLoadScheduler> scheduler_;
mojom::blink::DelayCompetingLowPriorityRequestsHintsPtr optimization_hints_;
};
class ResourceLoadSchedulerTest
: public ResourceLoadSchedulerTestBase,
public testing::WithParamInterface<
std::tuple<base::test::ScopedFeatureList::FeatureAndParams, bool>> {
public:
void SetUp() override {
std::vector<base::test::ScopedFeatureList::FeatureAndParams>
features_with_params;
std::vector<base::Feature> disabled_features;
bool enabled = std::get<1>(GetParam());
if (enabled) {
features_with_params.push_back(std::get<0>(GetParam()));
} else {
disabled_features.push_back(std::get<0>(GetParam()).feature);
}
feature_list_.InitWithFeaturesAndParameters(features_with_params,
disabled_features);
ResourceLoadSchedulerTestBase::SetUp();
}
};
INSTANTIATE_TEST_SUITE_P(
All,
ResourceLoadSchedulerTest,
testing::Combine(
// A list of FeatureAndParams structs representing a single feature and
// an arbitrary number of parameters.
testing::Values(base::test::ScopedFeatureList::FeatureAndParams(
features::kDelayCompetingLowPriorityRequests,
{{"delay_type", "always"}})),
// A boolean indicating whether or not the feature is enabled.
testing::Bool()));
TEST_P(ResourceLoadSchedulerTest, StopStoppableRequest) {
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kStopped);
// A request that disallows throttling should be queued.
MockClient* client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_FALSE(client1->WasRun());
// Another request that disallows throttling, but allows stopping should also
// be queued.
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kStoppable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_FALSE(client2->WasRun());
// Another request that disallows throttling and stopping also should be run
// even it makes the outstanding number reaches to the limit.
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kCanNotBeStoppedOrThrottled,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_TRUE(client3->WasRun());
// Call Release() with different options just in case.
EXPECT_TRUE(Release(id1));
EXPECT_TRUE(ReleaseAndSchedule(id2));
EXPECT_TRUE(ReleaseAndSchedule(id3));
// Should not succeed to call with the same ID twice.
EXPECT_FALSE(Release(id1));
// Should not succeed to call with the invalid ID or unused ID.
EXPECT_FALSE(Release(ResourceLoadScheduler::kInvalidClientId));
EXPECT_FALSE(Release(static_cast<ResourceLoadScheduler::ClientId>(774)));
}
TEST_P(ResourceLoadSchedulerTest, ThrottleThrottleableRequest) {
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kThrottled);
Scheduler()->SetOutstandingLimitForTesting(0);
// A request that allows throttling should be queued.
MockClient* client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_FALSE(client1->WasRun());
// Another request that disallows throttling also should be run even it makes
// the outstanding number reaches to the limit.
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kStoppable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_TRUE(client2->WasRun());
// Another request that disallows stopping should be run even it makes the
// outstanding number reaches to the limit.
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kCanNotBeStoppedOrThrottled,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_TRUE(client3->WasRun());
// Call Release() with different options just in case.
EXPECT_TRUE(Release(id1));
EXPECT_TRUE(ReleaseAndSchedule(id2));
EXPECT_TRUE(ReleaseAndSchedule(id3));
// Should not succeed to call with the same ID twice.
EXPECT_FALSE(Release(id1));
// Should not succeed to call with the invalid ID or unused ID.
EXPECT_FALSE(Release(ResourceLoadScheduler::kInvalidClientId));
EXPECT_FALSE(Release(static_cast<ResourceLoadScheduler::ClientId>(774)));
}
TEST_P(ResourceLoadSchedulerTest, Throttled) {
// The first request should be ran synchronously.
MockClient* client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_TRUE(client1->WasRun());
// Another request should be throttled until the first request calls Release.
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_FALSE(client2->WasRun());
// Two more requests.
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_FALSE(client3->WasRun());
MockClient* client4 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client4, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id4);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4);
EXPECT_FALSE(client4->WasRun());
// Call Release() to run the second request.
EXPECT_TRUE(ReleaseAndSchedule(id1));
EXPECT_TRUE(client2->WasRun());
// Call Release() with kReleaseOnly should not run the third and the fourth
// requests.
EXPECT_TRUE(Release(id2));
EXPECT_FALSE(client3->WasRun());
EXPECT_FALSE(client4->WasRun());
// Should be able to call Release() for a client that hasn't run yet. This
// should run another scheduling to run the fourth request.
EXPECT_TRUE(ReleaseAndSchedule(id3));
EXPECT_TRUE(client4->WasRun());
}
TEST_P(ResourceLoadSchedulerTest, Unthrottle) {
// Push three requests.
MockClient* client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_TRUE(client1->WasRun());
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_FALSE(client2->WasRun());
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_FALSE(client3->WasRun());
// Allows to pass all requests.
Scheduler()->SetOutstandingLimitForTesting(3);
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
// Release all.
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id2));
EXPECT_TRUE(Release(id1));
}
TEST_P(ResourceLoadSchedulerTest, Stopped) {
// Push three requests.
MockClient* client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_TRUE(client1->WasRun());
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_FALSE(client2->WasRun());
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kThrottleable,
ResourceLoadPriority::kMedium, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_FALSE(client3->WasRun());
// Setting outstanding_limit_ to 0 in ThrottlingState::kStopped, prevents
// further requests.
Scheduler()->SetOutstandingLimitForTesting(0);
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// Calling Release() still does not run the second request.
EXPECT_TRUE(ReleaseAndSchedule(id1));
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// Release all.
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id2));
}
TEST_P(ResourceLoadSchedulerTest, PriorityIsConsidered) {
// Push three requests.
MockClient* client1 = MakeGarbageCollected<MockClient>();
// Allows one kHigh priority request by limits below.
Scheduler()->SetOutstandingLimitForTesting(0, 1);
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 10 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 1 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 3 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
MockClient* client4 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client4, ThrottleOption::kThrottleable,
ResourceLoadPriority::kHigh, 0 /* intra_priority */,
&id4);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4);
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
if (base::FeatureList::IsEnabled(
features::kDelayCompetingLowPriorityRequests)) {
// Allows two requests (regardless of priority). No other clients are run,
// because the kHigh request is considered running / in-flight, so the kLow
// priority request should be delayed behind it as per this feature.
Scheduler()->SetOutstandingLimitForTesting(2);
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
// ResourceLoadPriority::kLow requests will not run until client4 is
// released.
EXPECT_TRUE(ReleaseAndSchedule(id4));
EXPECT_FALSE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
Scheduler()->SetOutstandingLimitForTesting(3);
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
EXPECT_TRUE(WasDelayCompetingLowPriorityRequestsObserved());
} else {
Scheduler()->SetOutstandingLimitForTesting(2);
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
Scheduler()->SetOutstandingLimitForTesting(3);
EXPECT_FALSE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
Scheduler()->SetOutstandingLimitForTesting(4);
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
EXPECT_FALSE(WasDelayCompetingLowPriorityRequestsObserved());
}
// Release the rest.
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id2));
EXPECT_TRUE(Release(id1));
}
TEST_P(ResourceLoadSchedulerTest, AllowedRequestsRunInPriorityOrder) {
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kStopped);
Scheduler()->SetOutstandingLimitForTesting(0);
MockClient::MockClientDelegate delegate;
// Push two requests.
MockClient* client1 = MakeGarbageCollected<MockClient>();
MockClient* client2 = MakeGarbageCollected<MockClient>();
client1->SetDelegate(&delegate);
client2->SetDelegate(&delegate);
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kStoppable,
ResourceLoadPriority::kLowest, 10 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kHigh, 1 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
Scheduler()->SetOutstandingLimitForTesting(1);
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kThrottled);
if (base::FeatureList::IsEnabled(
features::kDelayCompetingLowPriorityRequests)) {
EXPECT_FALSE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(ReleaseAndSchedule(id2));
EXPECT_TRUE(client1->WasRun());
// Finish releasing all.
EXPECT_TRUE(Release(id1));
EXPECT_TRUE(WasDelayCompetingLowPriorityRequestsObserved());
} else {
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
// Release all.
EXPECT_TRUE(Release(id1));
EXPECT_TRUE(Release(id2));
EXPECT_FALSE(WasDelayCompetingLowPriorityRequestsObserved());
}
// Verify high priority request ran first.
auto& order = delegate.client_order();
EXPECT_EQ(order[0], client2);
EXPECT_EQ(order[1], client1);
}
TEST_P(ResourceLoadSchedulerTest, StoppableRequestResumesWhenThrottled) {
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kStopped);
// Push two requests.
MockClient* client1 = MakeGarbageCollected<MockClient>();
Scheduler()->SetOutstandingLimitForTesting(0);
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kStoppable,
ResourceLoadPriority::kLowest, 10 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kHigh, 1 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kStoppable,
ResourceLoadPriority::kLowest, 10 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kThrottled);
EXPECT_TRUE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
Scheduler()->SetOutstandingLimitForTesting(1);
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_TRUE(client3->WasRun());
// Release all.
EXPECT_TRUE(Release(id1));
EXPECT_TRUE(Release(id2));
EXPECT_TRUE(Release(id3));
}
TEST_P(ResourceLoadSchedulerTest, SetPriority) {
// Push three requests.
MockClient* client1 = MakeGarbageCollected<MockClient>();
// Allows one kHigh priority request by limits below.
Scheduler()->SetOutstandingLimitForTesting(0, 1);
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 5 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 10 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
Scheduler()->SetPriority(id1, ResourceLoadPriority::kHigh, 0);
EXPECT_TRUE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
Scheduler()->SetPriority(id3, ResourceLoadPriority::kLow, 2);
EXPECT_TRUE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
if (base::FeatureList::IsEnabled(
features::kDelayCompetingLowPriorityRequests)) {
// Loosen the policy to adopt the normal limit for all. One request
// regardless of priority can be granted.
Scheduler()->LoosenThrottlingPolicy();
// The kLow requests are still delayed behind the in-flight kHigh one, since
// it hasn't been released yet.
EXPECT_TRUE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// Releasing the in-flight high priority request makes room for the next two
// low priority requests to be granted, since the limit is two, and low
// priority.
EXPECT_TRUE(ReleaseAndSchedule(id1));
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// kHigh priority does not help the third request here.
Scheduler()->SetPriority(id3, ResourceLoadPriority::kHigh, 0);
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// Release remaining clients.
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id2));
EXPECT_FALSE(WasDelayCompetingLowPriorityRequestsObserved());
} else {
// Loosen the policy to adopt the normal limit for all. Two requests
// regardless of priority can be granted (including the in-flight high
// priority request).
Scheduler()->LoosenThrottlingPolicy();
Scheduler()->SetOutstandingLimitForTesting(0, 2);
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// kHigh priority does not help the third request here.
Scheduler()->SetPriority(id3, ResourceLoadPriority::kHigh, 0);
EXPECT_TRUE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
// Release all.
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id2));
EXPECT_TRUE(Release(id1));
EXPECT_FALSE(WasDelayCompetingLowPriorityRequestsObserved());
}
}
TEST_P(ResourceLoadSchedulerTest, LoosenThrottlingPolicy) {
MockClient* client1 = MakeGarbageCollected<MockClient>();
Scheduler()->SetOutstandingLimitForTesting(0, 0);
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
MockClient* client3 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client3, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
MockClient* client4 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client4, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id4);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4);
Scheduler()->SetPriority(id2, ResourceLoadPriority::kLow, 0);
Scheduler()->SetPriority(id3, ResourceLoadPriority::kLow, 0);
Scheduler()->SetPriority(id4, ResourceLoadPriority::kMedium, 0);
// As the policy is |kTight|, |kMedium| is throttled.
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
EXPECT_FALSE(client4->WasRun());
Scheduler()->SetOutstandingLimitForTesting(0, 2);
// The initial scheduling policy is |kTight|, setting the
// outstanding limit for the normal mode doesn't take effect.
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
EXPECT_FALSE(client4->WasRun());
// Now let's tighten the limit again.
Scheduler()->SetOutstandingLimitForTesting(0, 0);
// ...and change the scheduling policy to |kNormal|.
Scheduler()->LoosenThrottlingPolicy();
EXPECT_FALSE(client1->WasRun());
EXPECT_FALSE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
EXPECT_FALSE(client4->WasRun());
Scheduler()->SetOutstandingLimitForTesting(0, 2);
EXPECT_FALSE(client1->WasRun());
EXPECT_TRUE(client2->WasRun());
EXPECT_FALSE(client3->WasRun());
EXPECT_TRUE(client4->WasRun());
// Release all.
EXPECT_TRUE(Release(id4));
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id2));
EXPECT_TRUE(Release(id1));
}
TEST_P(ResourceLoadSchedulerTest, ConsoleMessage) {
auto test_task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
Scheduler()->SetClockForTesting(test_task_runner->GetMockClock());
Scheduler()->SetOutstandingLimitForTesting(0, 0);
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kThrottled);
// Push two requests into the queue.
MockClient* client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_FALSE(client1->WasRun());
MockClient* client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLowest, 0 /* intra_priority */,
&id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_FALSE(client2->WasRun());
// Cancel the first request
EXPECT_TRUE(Release(id1));
// Advance current time a little and triggers an life cycle event, but it
// still won't awake the warning logic.
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(50));
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kNotThrottled);
EXPECT_FALSE(GetConsoleLogger()->HasMessage());
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kThrottled);
// Modify current time to awake the console warning logic, and the second
// client should be used for console logging.
test_task_runner->FastForwardBy(base::TimeDelta::FromSeconds(15));
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kNotThrottled);
EXPECT_TRUE(GetConsoleLogger()->HasMessage());
EXPECT_TRUE(Release(id2));
}
mojom::blink::DelayCompetingLowPriorityRequestsHintsPtr
CreateOptimizationGuideHints(
features::DelayCompetingLowPriorityRequestsDelayType delay_milestone,
features::DelayCompetingLowPriorityRequestsThreshold priority_threshold) {
auto optimization_hints =
mojom::blink::DelayCompetingLowPriorityRequestsHints::New();
switch (delay_milestone) {
case features::DelayCompetingLowPriorityRequestsDelayType::kFirstPaint:
optimization_hints->delay_type =
mojom::blink::DelayCompetingLowPriorityRequestsDelayType::kFirstPaint;
break;
case features::DelayCompetingLowPriorityRequestsDelayType::
kFirstContentfulPaint:
optimization_hints->delay_type = mojom::blink::
DelayCompetingLowPriorityRequestsDelayType::kFirstContentfulPaint;
break;
default:
NOTREACHED();
break;
}
switch (priority_threshold) {
case features::DelayCompetingLowPriorityRequestsThreshold::kMedium:
optimization_hints->priority_threshold = mojom::blink::
DelayCompetingLowPriorityRequestsPriorityThreshold::kMedium;
break;
case features::DelayCompetingLowPriorityRequestsThreshold::kHigh:
optimization_hints->priority_threshold = mojom::blink::
DelayCompetingLowPriorityRequestsPriorityThreshold::kHigh;
break;
}
return optimization_hints;
}
class ResourceLoadSchedulerTestDelayCompetingLowPriorityRequests
: public ResourceLoadSchedulerTestBase,
public testing::WithParamInterface<
std::tuple<bool /* use_optimization_guide */,
features::DelayCompetingLowPriorityRequestsDelayType,
features::DelayCompetingLowPriorityRequestsThreshold>> {
public:
void SetUp() override {
std::map<std::string, std::string> parameters;
bool use_optimization_guide = std::get<0>(GetParam());
until_ = std::get<1>(GetParam());
priority_threshold_ = std::get<2>(GetParam());
if (use_optimization_guide) {
parameters[features::kDelayCompetingLowPriorityRequestsDelayParam.name] =
"use_optimization_guide";
optimization_hints_ =
CreateOptimizationGuideHints(until_, priority_threshold_);
} else {
switch (until_) {
case features::DelayCompetingLowPriorityRequestsDelayType::kFirstPaint:
parameters[features::kDelayCompetingLowPriorityRequestsDelayParam
.name] = "first_paint";
break;
case features::DelayCompetingLowPriorityRequestsDelayType::
kFirstContentfulPaint:
parameters[features::kDelayCompetingLowPriorityRequestsDelayParam
.name] = "first_contentful_paint";
break;
default:
NOTREACHED();
break;
}
switch (priority_threshold_) {
case features::DelayCompetingLowPriorityRequestsThreshold::kMedium:
parameters[features::kDelayCompetingLowPriorityRequestsThresholdParam
.name] = "medium";
break;
case features::DelayCompetingLowPriorityRequestsThreshold::kHigh:
parameters[features::kDelayCompetingLowPriorityRequestsThresholdParam
.name] = "high";
break;
}
}
feature_list_.InitWithFeaturesAndParameters(
{{features::kDelayCompetingLowPriorityRequests, parameters}}, {});
ASSERT_TRUE(base::FeatureList::IsEnabled(
features::kDelayCompetingLowPriorityRequests));
if (use_optimization_guide) {
ASSERT_EQ(features::kDelayCompetingLowPriorityRequestsDelayParam.Get(),
features::DelayCompetingLowPriorityRequestsDelayType::
kUseOptimizationGuide);
ASSERT_EQ(
features::kDelayCompetingLowPriorityRequestsThresholdParam.Get(),
features::kDelayCompetingLowPriorityRequestsThresholdParam
.default_value);
} else {
ASSERT_EQ(features::kDelayCompetingLowPriorityRequestsDelayParam.Get(),
until_);
ASSERT_EQ(
features::kDelayCompetingLowPriorityRequestsThresholdParam.Get(),
priority_threshold_);
}
ResourceLoadSchedulerTestBase::SetUp();
}
// Returns a ResourceLoadPriority value (either kHigh, or kMedium)
// corresponding with the priority threshold field trial parameter.
ResourceLoadPriority ImportantPriority() {
if (priority_threshold_ ==
features::DelayCompetingLowPriorityRequestsThreshold::kMedium) {
return ResourceLoadPriority::kMedium;
}
if (priority_threshold_ ==
features::DelayCompetingLowPriorityRequestsThreshold::kHigh) {
return ResourceLoadPriority::kHigh;
}
NOTREACHED();
return ResourceLoadPriority::kUnresolved;
}
protected:
features::DelayCompetingLowPriorityRequestsDelayType until_;
features::DelayCompetingLowPriorityRequestsThreshold priority_threshold_;
};
INSTANTIATE_TEST_SUITE_P(
All,
ResourceLoadSchedulerTestDelayCompetingLowPriorityRequests,
testing::Combine(
// True when use optimization guide:
testing::Bool(),
// Delay type parameter:
testing::Values(
features::DelayCompetingLowPriorityRequestsDelayType::kFirstPaint,
features::DelayCompetingLowPriorityRequestsDelayType::
kFirstContentfulPaint),
// Priority threshold parameter:
testing::Values(
features::DelayCompetingLowPriorityRequestsThreshold::kMedium,
features::DelayCompetingLowPriorityRequestsThreshold::kHigh)));
TEST_P(ResourceLoadSchedulerTestDelayCompetingLowPriorityRequests,
DelayRequests) {
ResourceLoadPriority important = ImportantPriority();
// No throttling, so we can test the delay logic in an unobstructed way.
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kNotThrottled);
Scheduler()->SetOutstandingLimitForTesting(
ResourceLoadScheduler::kOutstandingUnlimited);
MockClient* important_client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(important_client1, ThrottleOption::kThrottleable,
important, 0 /* intra_priority */, &id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
EXPECT_TRUE(important_client1->WasRun());
MockClient* important_client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id2 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(important_client2, ThrottleOption::kThrottleable,
important, 0 /* intra_priority */, &id2);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id2);
EXPECT_TRUE(important_client2->WasRun());
MockClient* low_client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(low_client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
EXPECT_FALSE(low_client1->WasRun());
MockClient* low_client2 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id4 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(low_client2, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 0 /* intra_priority */,
&id4);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id4);
EXPECT_FALSE(low_client2->WasRun());
EXPECT_TRUE(ReleaseAndSchedule(id1));
// Releasing one important request is not enough to grant the low priority
// clients.
EXPECT_FALSE(low_client1->WasRun());
EXPECT_FALSE(low_client2->WasRun());
// Triggering the loading milestone that we're not interested is not enough to
// grant the low priority clients.
if (until_ ==
features::DelayCompetingLowPriorityRequestsDelayType::kFirstPaint) {
Scheduler()->MarkFirstContentfulPaint();
} else {
Scheduler()->MarkFirstPaint();
}
EXPECT_FALSE(low_client1->WasRun());
EXPECT_FALSE(low_client2->WasRun());
// Triggering the loading milestone we are interested in *is* enough to grant
// the low priority clients.
Scheduler()->MarkFirstPaint();
Scheduler()->MarkFirstContentfulPaint();
EXPECT_TRUE(low_client1->WasRun());
EXPECT_TRUE(low_client2->WasRun());
EXPECT_TRUE(Release(id2));
EXPECT_TRUE(Release(id3));
EXPECT_TRUE(Release(id4));
EXPECT_TRUE(WasDelayCompetingLowPriorityRequestsObserved());
}
// Tests that DelayCompetingLowPriorityRequests does not delay
// requests for background pages.
TEST_P(ResourceLoadSchedulerTestDelayCompetingLowPriorityRequests, Hidden) {
ResourceLoadPriority important = ImportantPriority();
// Set up hidden lifecycle state.
Scheduler()->OnLifecycleStateChanged(
scheduler::SchedulingLifecycleState::kHidden);
Scheduler()->SetOutstandingLimitForTesting(
ResourceLoadScheduler::kOutstandingUnlimited);
// Make an important request.
MockClient* important_client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id1 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(important_client1, ThrottleOption::kThrottleable,
important, 0 /* intra_priority */, &id1);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id1);
// Make a low-priority request.
MockClient* low_client1 = MakeGarbageCollected<MockClient>();
ResourceLoadScheduler::ClientId id3 = ResourceLoadScheduler::kInvalidClientId;
Scheduler()->Request(low_client1, ThrottleOption::kThrottleable,
ResourceLoadPriority::kLow, 0 /* intra_priority */,
&id3);
EXPECT_NE(ResourceLoadScheduler::kInvalidClientId, id3);
// It should not have been delayed because the page is hidden.
EXPECT_FALSE(WasDelayCompetingLowPriorityRequestsObserved());
}
} // namespace
} // namespace blink