| // 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 |