| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/loader/keep_alive_url_loader_service.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/attribution_reporting/constants.h" |
| #include "content/browser/attribution_reporting/attribution_data_host_manager_impl.h" |
| #include "content/browser/attribution_reporting/test/mock_attribution_data_host_manager.h" |
| #include "content/browser/attribution_reporting/test/mock_attribution_manager.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/test/navigation_simulator_impl.h" |
| #include "content/test/test_render_view_host.h" |
| #include "content/test/test_web_contents.h" |
| #include "mojo/public/cpp/bindings/associated_remote.h" |
| #include "mojo/public/cpp/bindings/pending_associated_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/system/functions.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h" |
| #include "services/network/public/cpp/content_security_policy/content_security_policy.h" |
| #include "services/network/public/cpp/parsed_headers.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/attribution.mojom.h" |
| #include "services/network/public/mojom/content_security_policy.mojom.h" |
| #include "services/network/public/mojom/early_hints.mojom.h" |
| #include "services/network/public/mojom/ip_address_space.mojom.h" |
| #include "services/network/public/mojom/referrer_policy.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/loader/url_loader_throttle.h" |
| #include "third_party/blink/public/mojom/loader/fetch_later.mojom.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "base/mac/mac_util.h" |
| #endif |
| |
| namespace content { |
| namespace { |
| |
| using attribution_reporting::kAttributionReportingRegisterSourceHeader; |
| using attribution_reporting::kAttributionReportingRegisterTriggerHeader; |
| |
| using testing::_; |
| using testing::Eq; |
| using testing::IsEmpty; |
| using testing::SizeIs; |
| using testing::WithArg; |
| |
| constexpr char kTestRequestUrl[] = "https://example.test"; |
| constexpr char kTestResponseHeaderName[] = "My-Test-Header"; |
| constexpr char kTestResponseHeaderValue[] = "my-test-value"; |
| constexpr char kTestRedirectRequestUrl[] = "https://redirect.test"; |
| constexpr char kTestUnSafeRedirectRequestUrl[] = "about:blank"; |
| constexpr char kTestViolatingCSPRedirectRequestUrl[] = |
| "https://violate-csp.test"; |
| |
| // Mock a receiver URLLoaderClient that may exist in renderer. |
| class MockReceiverURLLoaderClient : public network::mojom::URLLoaderClient { |
| public: |
| MockReceiverURLLoaderClient() = default; |
| MockReceiverURLLoaderClient(const MockReceiverURLLoaderClient&) = delete; |
| MockReceiverURLLoaderClient& operator=(const MockReceiverURLLoaderClient&) = |
| delete; |
| ~MockReceiverURLLoaderClient() override { |
| if (receiver_.is_bound()) { |
| // Flush the pipe to make sure there aren't any lingering events. |
| receiver_.FlushForTesting(); |
| } |
| } |
| |
| mojo::PendingRemote<network::mojom::URLLoaderClient> |
| BindNewPipeAndPassRemote() { |
| return receiver_.BindNewPipeAndPassRemote(); |
| } |
| |
| // Note that this also unbinds the receiver. |
| void ResetReceiver() { receiver_.reset(); } |
| |
| // `network::mojom::URLLoaderClient` overrides: |
| MOCK_METHOD1(OnReceiveEarlyHints, void(network::mojom::EarlyHintsPtr)); |
| MOCK_METHOD3(OnReceiveResponse, |
| void(network::mojom::URLResponseHeadPtr, |
| mojo::ScopedDataPipeConsumerHandle, |
| std::optional<mojo_base::BigBuffer>)); |
| MOCK_METHOD2(OnReceiveRedirect, |
| void(const net::RedirectInfo&, |
| network::mojom::URLResponseHeadPtr)); |
| MOCK_METHOD3(OnUploadProgress, |
| void(int64_t, int64_t, base::OnceCallback<void()>)); |
| MOCK_METHOD1(OnTransferSizeUpdated, void(int32_t)); |
| MOCK_METHOD1(OnComplete, void(const network::URLLoaderCompletionStatus&)); |
| |
| private: |
| mojo::Receiver<network::mojom::URLLoaderClient> receiver_{this}; |
| }; |
| |
| // Fakes a URLLoaderFactory that may exist in renderer, which only delegates to |
| // `remote_url_loader_factory`. |
| class FakeRemoteURLLoaderFactory { |
| public: |
| static constexpr int kRequestId = 1; |
| |
| FakeRemoteURLLoaderFactory() = default; |
| FakeRemoteURLLoaderFactory(const FakeRemoteURLLoaderFactory&) = delete; |
| FakeRemoteURLLoaderFactory& operator=(const FakeRemoteURLLoaderFactory&) = |
| delete; |
| ~FakeRemoteURLLoaderFactory() = default; |
| |
| mojo::PendingReceiver<network::mojom::URLLoaderFactory> |
| BindNewPipeAndPassReceiver() { |
| return remote_url_loader_factory.BindNewPipeAndPassReceiver(); |
| } |
| |
| // Binds `remote_url_loader` to a new URLLoader. |
| void CreateLoaderAndStart( |
| const network::ResourceRequest& request, |
| mojo::PendingRemote<network::mojom::URLLoaderClient> client, |
| bool expect_success = true) { |
| remote_url_loader_factory->CreateLoaderAndStart( |
| remote_url_loader.BindNewPipeAndPassReceiver(), |
| /*request_id=*/kRequestId, /*options=*/0, request, std::move(client), |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); |
| remote_url_loader_factory.FlushForTesting(); |
| ASSERT_EQ(remote_url_loader.is_connected(), expect_success); |
| } |
| |
| bool is_remote_url_loader_connected() { |
| return remote_url_loader.is_connected(); |
| } |
| void reset_remote_url_loader() { remote_url_loader.reset(); } |
| |
| private: |
| mojo::Remote<network::mojom::URLLoaderFactory> remote_url_loader_factory; |
| mojo::Remote<network::mojom::URLLoader> remote_url_loader; |
| }; |
| |
| // Fakes a FetchLaterLoaderFactory that may exist in renderer, which only |
| // delegates to `remote_fetch_later_loader_factory`. |
| class FakeRemoteFetchLaterLoaderFactory { |
| public: |
| FakeRemoteFetchLaterLoaderFactory() = default; |
| FakeRemoteFetchLaterLoaderFactory(const FakeRemoteFetchLaterLoaderFactory&) = |
| delete; |
| FakeRemoteFetchLaterLoaderFactory& operator=( |
| const FakeRemoteFetchLaterLoaderFactory&) = delete; |
| ~FakeRemoteFetchLaterLoaderFactory() = default; |
| |
| mojo::PendingAssociatedReceiver<blink::mojom::FetchLaterLoaderFactory> |
| BindNewEndpointAndPassDedicatedReceiver() { |
| return remote_fetch_later_loader_factory_ |
| .BindNewEndpointAndPassDedicatedReceiver(); |
| } |
| |
| // Binds `remote_fetch_later_loader_` to a new URLLoader. |
| void CreateLoader(const network::ResourceRequest& request, |
| bool expect_success = true) { |
| remote_fetch_later_loader_factory_->CreateLoader( |
| remote_fetch_later_loader_.BindNewEndpointAndPassReceiver(), |
| /*request_id=*/1, /*options=*/0, request, |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); |
| remote_fetch_later_loader_factory_.FlushForTesting(); |
| ASSERT_EQ(remote_fetch_later_loader_.is_connected(), expect_success); |
| } |
| |
| bool is_remote_fetch_later_loader_connected() { |
| return remote_fetch_later_loader_.is_connected(); |
| } |
| void reset_remote_fetch_later_loader() { remote_fetch_later_loader_.reset(); } |
| |
| private: |
| mojo::AssociatedRemote<blink::mojom::FetchLaterLoaderFactory> |
| remote_fetch_later_loader_factory_; |
| mojo::AssociatedRemote<blink::mojom::FetchLaterLoader> |
| remote_fetch_later_loader_; |
| }; |
| |
| class ConfigurableURLLoaderThrottle final : public blink::URLLoaderThrottle { |
| public: |
| explicit ConfigurableURLLoaderThrottle(bool deferring = false, |
| bool canceling_before_start = false, |
| bool canceling_before_redirect = false) |
| : deferring_(deferring), |
| canceling_before_start_(canceling_before_start), |
| canceling_before_redirect_(canceling_before_redirect) {} |
| |
| ~ConfigurableURLLoaderThrottle() override = default; |
| // Not copyable. |
| ConfigurableURLLoaderThrottle(const ConfigurableURLLoaderThrottle&) = delete; |
| ConfigurableURLLoaderThrottle& operator=( |
| const ConfigurableURLLoaderThrottle&) = delete; |
| |
| // blink::URLLoaderThrottle overrides: |
| void WillStartRequest(network::ResourceRequest* request, |
| bool* defer) override { |
| will_start_request_called_ = true; |
| *defer = deferring_; |
| if (canceling_before_start_) { |
| delegate()->CancelWithError(net::ERR_ABORTED); |
| } |
| } |
| void WillRedirectRequest( |
| net::RedirectInfo* redirect_info, |
| const network::mojom::URLResponseHead& /* response_head */, |
| bool* defer, |
| std::vector<std::string>* /* to_be_removed_headers */, |
| net::HttpRequestHeaders* /* modified_headers */, |
| net::HttpRequestHeaders* /* modified_cors_exempt_headers */) override { |
| will_redirect_request_called_ = true; |
| *defer = deferring_; |
| if (canceling_before_redirect_) { |
| delegate()->CancelWithError(net::ERR_ABORTED); |
| } |
| } |
| void WillProcessResponse(const GURL& response_url_, |
| network::mojom::URLResponseHead* response_head, |
| bool* defer) override { |
| will_process_response_called_ = true; |
| *defer = deferring_; |
| } |
| |
| bool will_start_request_called() const { return will_start_request_called_; } |
| bool will_redirect_request_called() const { |
| return will_redirect_request_called_; |
| } |
| bool will_process_response_called() const { |
| return will_process_response_called_; |
| } |
| |
| Delegate* delegate() { return delegate_; } |
| |
| private: |
| bool will_start_request_called_ = false; |
| bool will_redirect_request_called_ = false; |
| bool will_process_response_called_ = false; |
| |
| const bool deferring_; |
| const bool canceling_before_start_; |
| const bool canceling_before_redirect_; |
| }; |
| |
| // Returns true if `arg` has a header of the given `name` and `value`. |
| // `arg` is an `network::mojom::URLResponseHeadPtr`. |
| MATCHER_P2(ResponseHasHeader, |
| name, |
| value, |
| base::StringPrintf("Response has %sheader[%s=%s]", |
| negation ? "no " : "", |
| name, |
| value)) { |
| return arg->headers->HasHeaderValue(name, value); |
| } |
| |
| network::ResourceRequest CreateFetchLaterResourceRequest(const GURL& url) { |
| network::ResourceRequest request; |
| request.url = url; |
| request.keepalive = true; |
| request.is_fetch_later_api = true; |
| request.resource_type = static_cast<int>(blink::mojom::ResourceType::kXhr); |
| return request; |
| } |
| |
| network::ResourceRequest CreateResourceRequest( |
| const GURL& url, |
| bool keepalive = true, |
| bool is_trusted = false, |
| std::optional<network::mojom::RedirectMode> redirect_mode = std::nullopt) { |
| network::ResourceRequest request; |
| request.url = url; |
| request.keepalive = keepalive; |
| request.resource_type = static_cast<int>(blink::mojom::ResourceType::kXhr); |
| if (is_trusted) { |
| request.trusted_params = network::ResourceRequest::TrustedParams(); |
| } |
| if (redirect_mode) { |
| request.redirect_mode = *redirect_mode; |
| } |
| return request; |
| } |
| |
| network::mojom::URLResponseHeadPtr CreateResponseHead( |
| const std::vector<std::pair<std::string, std::string>>& extra_headers = |
| {}) { |
| auto response = network::mojom::URLResponseHead::New(); |
| net::HttpResponseHeaders::Builder builder({1, 1}, "200 OK"); |
| for (const auto& [name, value] : extra_headers) { |
| builder.AddHeader(name, value); |
| } |
| response->headers = builder.Build(); |
| return response; |
| } |
| |
| net::RedirectInfo CreateRedirectInfo(const GURL& new_url) { |
| net::RedirectInfo redirect_info; |
| redirect_info.new_method = "GET"; |
| redirect_info.new_url = new_url; |
| redirect_info.status_code = 301; |
| return redirect_info; |
| } |
| |
| network::mojom::EarlyHintsPtr CreateEarlyHints( |
| const GURL& url, |
| const std::vector<std::pair<std::string, std::string>>& extra_headers = |
| {}) { |
| auto response_headers = |
| base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK\n"); |
| for (const auto& header : extra_headers) { |
| response_headers->SetHeader(header.first, header.second); |
| } |
| return network::mojom::EarlyHints::New( |
| network::PopulateParsedHeaders(response_headers.get(), url), |
| network::mojom::ReferrerPolicy::kDefault, |
| network::mojom::IPAddressSpace::kPublic); |
| } |
| |
| } // namespace |
| |
| class KeepAliveURLLoaderServiceTestBase : public RenderViewHostTestHarness { |
| public: |
| KeepAliveURLLoaderServiceTestBase() |
| : RenderViewHostTestHarness( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| |
| protected: |
| void SetUp() override { |
| network_url_loader_factory_ = |
| std::make_unique<network::TestURLLoaderFactory>( |
| /*observe_loader_requests=*/true); |
| // Intercepts Mojo bad-message error. |
| mojo::SetDefaultProcessErrorHandler( |
| base::BindLambdaForTesting([&](const std::string& error) { |
| ASSERT_FALSE(mojo_bad_message_.has_value()); |
| mojo_bad_message_ = error; |
| })); |
| RenderViewHostTestHarness::SetUp(); |
| |
| test_web_contents()->NavigateAndCommit(GURL("https://example.com")); |
| |
| // Start a navigation but don't commit just yet, since we want to inject the |
| // context created in `BindKeepAliveURLLoaderFactory()`. |
| pending_navigation_ = NavigationSimulatorImpl::CreateBrowserInitiated( |
| GURL("https://example.com"), web_contents()); |
| pending_navigation_->Start(); |
| } |
| |
| void TearDown() override { |
| pending_navigation_.reset(); |
| network_url_loader_factory_ = nullptr; |
| loader_service_ = nullptr; |
| mojo::SetDefaultProcessErrorHandler(base::NullCallback()); |
| mojo_bad_message_ = std::nullopt; |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| void ExpectMojoBadMessage(const std::string& message) { |
| EXPECT_EQ(mojo_bad_message_, message); |
| } |
| |
| NavigationRequest* GetNavigationRequest() { |
| return pending_navigation_->GetNavigationHandle(); |
| } |
| |
| // Asks KeepAliveURLLoaderService to bind a KeepAliveURLLoaderFactory to the |
| // given `remote_url_loader_factory`. |
| // More than one factory can be bound to the same service. |
| void BindKeepAliveURLLoaderFactory( |
| FakeRemoteURLLoaderFactory& remote_url_loader_factory) { |
| mojo::Remote<network::mojom::URLLoaderFactory> factory; |
| network_url_loader_factory().Clone(factory.BindNewPipeAndPassReceiver()); |
| auto pending_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| factory.Unbind()); |
| |
| // Remote: `remote_url_loader_factory` |
| // Receiver: Held in `loader_service_`. |
| auto context = loader_service().BindFactory( |
| remote_url_loader_factory.BindNewPipeAndPassReceiver(), |
| network::SharedURLLoaderFactory::Create(std::move(pending_factory)), |
| static_cast<RenderFrameHostImpl*>(main_rfh()) |
| ->policy_container_host() |
| ->Clone()); |
| |
| // Ensure that the context above is the one used in the NavigationRequest. |
| // This is to make sure the OnDidCommitNavigation call that happens during |
| // navigation commit below will use the the loader & context that is |
| // expected by the test. We no longer call OnDidCommitNavigation manually |
| // since if we change RFHs we might not have a PolicyContainerHost or |
| // RFH origin yet here, causing problems with tests, attribution context |
| // etc. |
| GetNavigationRequest()->SetKeepAliveURLLoaderFactoryContextForTesting( |
| context); |
| pending_navigation_->Commit(); |
| |
| AddConnectSrcCSPToRFH(kTestRedirectRequestUrl); |
| } |
| |
| network::TestURLLoaderFactory::PendingRequest* GetLastPendingRequest() { |
| return &network_url_loader_factory_->pending_requests()->back(); |
| } |
| |
| const std::vector<network::TestURLLoaderFactory::PendingRequest>& |
| GetPendingRequests() const { |
| return *network_url_loader_factory_->pending_requests(); |
| } |
| |
| void AddConnectSrcCSPToRFH(const std::string& allowed_url) { |
| static_cast<RenderFrameHostImpl*>(main_rfh()) |
| ->policy_container_host() |
| ->AddContentSecurityPolicies(network::ParseContentSecurityPolicies( |
| "connect-src " + allowed_url, |
| network::mojom::ContentSecurityPolicyType::kEnforce, |
| network::mojom::ContentSecurityPolicySource::kMeta, |
| GURL(kTestRequestUrl))); |
| } |
| |
| network::TestURLLoaderFactory& network_url_loader_factory() { |
| return *network_url_loader_factory_; |
| } |
| KeepAliveURLLoaderService& loader_service() { |
| if (!loader_service_) { |
| loader_service_ = std::make_unique<KeepAliveURLLoaderService>( |
| static_cast<StoragePartitionImpl*>( |
| main_rfh()->GetStoragePartition())); |
| } |
| return *loader_service_; |
| } |
| base::test::ScopedFeatureList& feature_list() { return scoped_feature_list_; } |
| |
| TestWebContents* test_web_contents() { |
| return static_cast<TestWebContents*>(web_contents()); |
| } |
| |
| std::unique_ptr<NavigationSimulatorImpl> pending_navigation_; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| // Intercepts network facotry requests instead of using production factory. |
| std::unique_ptr<network::TestURLLoaderFactory> network_url_loader_factory_ = |
| nullptr; |
| // The test target. |
| std::unique_ptr<KeepAliveURLLoaderService> loader_service_ = nullptr; |
| std::optional<std::string> mojo_bad_message_; |
| }; |
| |
| class KeepAliveURLLoaderServiceTest : public KeepAliveURLLoaderServiceTestBase { |
| protected: |
| void SetUp() override { |
| feature_list().InitWithFeatures( |
| {blink::features::kKeepAliveInBrowserMigration, |
| blink::features::kAttributionReportingInBrowserMigration}, |
| {}); |
| KeepAliveURLLoaderServiceTestBase::SetUp(); |
| } |
| }; |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| LoadKeepAliveRequestWithInvalidFeatureAndTerminate) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads a keepalive request with invalid feature config: |
| base::test::ScopedFeatureList overwritten_feature_list; |
| overwritten_feature_list.InitAndDisableFeature( |
| blink::features::kKeepAliveInBrowserMigration); |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/false); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE(renderer_loader_factory.is_remote_url_loader_connected()); |
| ExpectMojoBadMessage( |
| "Unexpected call to " |
| "KeepAliveURLLoaderFactories::CreateLoaderAndStart()"); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, LoadFetchLaterRequestAndTerminate) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads FetchLater request (which is also keepalive request), but is not |
| // allowed with URLLoaderFactory. |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/false); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE(renderer_loader_factory.is_remote_url_loader_connected()); |
| ExpectMojoBadMessage( |
| "Unexpected `resource_request.is_fetch_later_api` in " |
| "KeepAliveURLLoaderFactories::CreateLoaderAndStart(): must not be set"); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, LoadNonKeepaliveRequestAndTerminate) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads non-keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl), /*keepalive=*/false), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/false); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE(renderer_loader_factory.is_remote_url_loader_connected()); |
| ExpectMojoBadMessage( |
| "Unexpected `resource_request` in " |
| "KeepAliveURLLoaderFactoriesBase::CreateLoaderAndStart(): " |
| "resource_request.keepalive must be true"); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, LoadTrustedRequestAndTerminate) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads trusted keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl), /*keepalive=*/true, |
| /*is_trusted=*/true), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/false); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE(renderer_loader_factory.is_remote_url_loader_connected()); |
| ExpectMojoBadMessage( |
| "Unexpected `resource_request` in " |
| "KeepAliveURLLoaderFactoriesBase::CreateLoaderAndStart(): " |
| "resource_request.trusted_params must not be set"); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, LoadRequestAfterPageIsUnloaded) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Deletes the current RenderFrameHost and then loads a keepalive request. |
| DeleteContents(); |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/true); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 1); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| } |
| |
| // This test initially provides an unbind factory to KeepAliveURLLoaderService. |
| // After that, provides a bound factory via UpdateFactory. |
| TEST_F(KeepAliveURLLoaderServiceTest, LoadRequestAfterUpdateFactory) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| |
| // First, bind the service with a PendingSharedURLLoaderFactory that connects |
| // to nothing. |
| auto unbound_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(); |
| scoped_refptr<PolicyContainerHost> policy_container_host = |
| static_cast<RenderFrameHostImpl*>(main_rfh()) |
| ->policy_container_host() |
| ->Clone(); |
| auto context = loader_service().BindFactory( |
| renderer_loader_factory.BindNewPipeAndPassReceiver(), |
| network::SharedURLLoaderFactory::Create(std::move(unbound_factory)), |
| policy_container_host); |
| GetNavigationRequest()->SetKeepAliveURLLoaderFactoryContextForTesting( |
| context); |
| pending_navigation_->Commit(); |
| |
| { |
| // Load a keepalive request. There should be no network loader created. |
| MockReceiverURLLoaderClient renderer_loader_client; |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/true); |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| } |
| |
| // Second, update the service with a PendingSharedURLLoaderFactory that |
| // connects to network loader factory as usual. |
| renderer_loader_factory.reset_remote_url_loader(); |
| mojo::Remote<network::mojom::URLLoaderFactory> factory; |
| network_url_loader_factory().Clone(factory.BindNewPipeAndPassReceiver()); |
| auto pending_factory = std::make_unique<blink::PendingURLLoaderFactoryBundle>( |
| factory.Unbind(), blink::PendingURLLoaderFactoryBundle::SchemeMap(), |
| blink::PendingURLLoaderFactoryBundle::OriginMap(), |
| /*local_resource_loader_config=*/nullptr, |
| /*bypass_redirect_checks=*/false); |
| context->UpdateFactory( |
| network::SharedURLLoaderFactory::Create(std::move(pending_factory))); |
| { |
| MockReceiverURLLoaderClient renderer_loader_client; |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote(), |
| /*expect_success=*/true); |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 1); |
| } |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, ForwardOnReceiveResponse) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnReceiveResponse: |
| // Expects underlying KeepAliveURLLoader forwards to `renderer_loader_client`. |
| EXPECT_CALL(renderer_loader_client, |
| OnReceiveResponse(ResponseHasHeader(kTestResponseHeaderName, |
| kTestResponseHeaderValue), |
| _, Eq(std::nullopt))) |
| .Times(1); |
| // Simluates receiving response in the network service. |
| GetLastPendingRequest()->client->OnReceiveResponse( |
| CreateResponseHead({{kTestResponseHeaderName, kTestResponseHeaderValue}}), |
| /*body=*/{}, std::nullopt); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| ForwardRedirectsAndResponseToAttributionRequestHelper) { |
| // The Attribution Manager uses the DataDecoder service, which, when an |
| // InProcessDataDecoer object exists, will route to an internal in-process |
| // instance. |
| data_decoder::test::InProcessDataDecoder in_process_data_decoder; |
| |
| // Set up the Attribution Manager. |
| auto mock_manager = std::make_unique<MockAttributionManager>(); |
| mock_manager->SetDataHostManager( |
| std::make_unique<AttributionDataHostManagerImpl>(mock_manager.get())); |
| MockAttributionManager* mock_attribution_manager = mock_manager.get(); |
| static_cast<StoragePartitionImpl*>( |
| browser_context()->GetDefaultStoragePartition()) |
| ->OverrideAttributionManagerForTesting(std::move(mock_manager)); |
| |
| // Loads keepalive request. |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| network::ResourceRequest request = |
| CreateResourceRequest(GURL(kTestRequestUrl)); |
| request.attribution_reporting_eligibility = |
| network::mojom::AttributionReportingEligibility::kEventSourceOrTrigger; |
| renderer_loader_factory.CreateLoaderAndStart( |
| std::move(request), renderer_loader_client.BindNewPipeAndPassRemote()); |
| |
| // Simluates receiving a redirect in the network service. |
| EXPECT_CALL(*mock_attribution_manager, HandleTrigger).Times(1); |
| constexpr char kRegisterTriggerJson[] = R"json({ })json"; |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead({{kAttributionReportingRegisterTriggerHeader, |
| kRegisterTriggerJson}})); |
| |
| // Simluates receiving response in the network service. |
| EXPECT_CALL(*mock_attribution_manager, HandleSource).Times(1); |
| constexpr char kRegisterSourceJson[] = |
| R"json({"destination":"https://destination.example"})json"; |
| GetLastPendingRequest()->client->OnReceiveResponse( |
| CreateResponseHead({{kAttributionReportingRegisterSourceHeader, |
| kRegisterSourceJson}}), |
| /*body=*/{}, /*cached_metadata=*/std::nullopt); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, ForwardErrorToAttributionRequestHelper) { |
| // Set up the Attribution Manager. |
| auto mock_manager = std::make_unique<MockAttributionManager>(); |
| auto mock_data_host_manager = |
| std::make_unique<MockAttributionDataHostManager>(); |
| MockAttributionDataHostManager* mock_data_host_manager_ptr = |
| mock_data_host_manager.get(); |
| mock_manager->SetDataHostManager(std::move(mock_data_host_manager)); |
| static_cast<StoragePartitionImpl*>( |
| browser_context()->GetDefaultStoragePartition()) |
| ->OverrideAttributionManagerForTesting(std::move(mock_manager)); |
| |
| // Loads keepalive request. |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| network::ResourceRequest request = |
| CreateResourceRequest(GURL(kTestRequestUrl)); |
| request.attribution_reporting_eligibility = |
| network::mojom::AttributionReportingEligibility::kEventSourceOrTrigger; |
| renderer_loader_factory.CreateLoaderAndStart( |
| std::move(request), renderer_loader_client.BindNewPipeAndPassRemote()); |
| |
| // Simluates receiving error in the network service. |
| EXPECT_CALL(*mock_data_host_manager_ptr, |
| NotifyBackgroundRegistrationCompleted) |
| .Times(1); |
| GetLastPendingRequest()->client->OnComplete( |
| network::URLLoaderCompletionStatus(-1)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F( |
| KeepAliveURLLoaderServiceTest, |
| OnReceiveRedirectWithErrorRedirectMode_NotForwardedToAttributionRequestHelper) { |
| // Set up the Attribution Manager. |
| auto mock_manager = std::make_unique<MockAttributionManager>(); |
| auto mock_data_host_manager = |
| std::make_unique<MockAttributionDataHostManager>(); |
| MockAttributionDataHostManager* mock_data_host_manager_ptr = |
| mock_data_host_manager.get(); |
| mock_manager->SetDataHostManager(std::move(mock_data_host_manager)); |
| static_cast<StoragePartitionImpl*>( |
| browser_context()->GetDefaultStoragePartition()) |
| ->OverrideAttributionManagerForTesting(std::move(mock_manager)); |
| |
| // Loads keepalive request that redirects first, with error redirect_mode: |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| network::ResourceRequest request = CreateResourceRequest( |
| GURL(kTestRequestUrl), /*keepalive=*/true, |
| /*is_trusted=*/false, network::mojom::RedirectMode::kError); |
| request.attribution_reporting_eligibility = |
| network::mojom::AttributionReportingEligibility::kEventSourceOrTrigger; |
| renderer_loader_factory.CreateLoaderAndStart( |
| std::move(request), renderer_loader_client.BindNewPipeAndPassRemote()); |
| |
| // Simluates receiving redirect in the network service. |
| EXPECT_CALL(*mock_data_host_manager_ptr, NotifyBackgroundRegistrationData) |
| .Times(0); |
| EXPECT_CALL(*mock_data_host_manager_ptr, |
| NotifyBackgroundRegistrationCompleted) |
| .Times(1); |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnReceiveResponseAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveResponse: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveResponse(_, _, _)).Times(0); |
| // Simluates receiving response in the network service. |
| GetLastPendingRequest()->client->OnReceiveResponse( |
| CreateResponseHead({{kTestResponseHeaderName, kTestResponseHeaderValue}}), |
| /*body=*/{}, std::nullopt); |
| base::RunLoop().RunUntilIdle(); |
| // The loader should have been deleted by the service. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, DoNotForwardOnReceiveRedirect) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnReceiveRedirect: |
| // Expects underlying KeepAliveURLLoader NOT forwards to |
| // `renderer_loader_client`: all redirects are processed in browser, and will |
| // only be forwarded after request completes/fails. |
| EXPECT_CALL(renderer_loader_client, |
| OnReceiveRedirect(_, ResponseHasHeader(kTestResponseHeaderName, |
| kTestResponseHeaderValue))) |
| .Times(0); |
| // Simluates receiving redirect in the network service. |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnReceiveRedirectAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request that redirects first: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl), /*keepalive=*/true, |
| /*is_trusted=*/false, |
| network::mojom::RedirectMode::kFollow), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| DeleteContents(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveRedirect: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(0); |
| // Simluates receiving redirect in the network service. |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verifies URLLoader::FollowRedirect() is sent to network service. |
| const auto& params = |
| GetLastPendingRequest()->test_url_loader->follow_redirect_params(); |
| EXPECT_THAT(params, SizeIs(1)); |
| EXPECT_EQ(params[0].new_url, std::nullopt); |
| EXPECT_THAT(params[0].removed_headers, IsEmpty()); |
| EXPECT_TRUE(params[0].modified_headers.IsEmpty()); |
| EXPECT_TRUE(params[0].modified_cors_exempt_headers.IsEmpty()); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnReceiveRedirectToUnSafeTargetAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request that redirects first: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl), /*keepalive=*/true, |
| /*is_trusted=*/false, |
| network::mojom::RedirectMode::kFollow), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| DeleteContents(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveRedirect: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(0); |
| // Simluates receiving unsafe redirect in the network service. |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestUnSafeRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verifies URLLoader::FollowRedirect() is NOT sent to network service. |
| const auto& params = |
| GetLastPendingRequest()->test_url_loader->follow_redirect_params(); |
| EXPECT_THAT(params, IsEmpty()); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnReceiveRedirectWithErrorRedirectModeAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request that redirects first, with error redirect_mode: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl), /*keepalive=*/true, |
| /*is_trusted=*/false, |
| network::mojom::RedirectMode::kError), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| DeleteContents(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveRedirect: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(0); |
| // Simluates receiving redirect in the network service. |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verifies URLLoader::FollowRedirect() is NOT sent to network service. |
| const auto& params = |
| GetLastPendingRequest()->test_url_loader->follow_redirect_params(); |
| EXPECT_THAT(params, IsEmpty()); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnReceiveRedirectViolatingCSPAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request that redirects first: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl), /*keepalive=*/true, |
| /*is_trusted=*/false, |
| network::mojom::RedirectMode::kFollow), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| DeleteContents(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveRedirect: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(0); |
| // Simluates receiving redirect in the network service. |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestViolatingCSPRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verifies URLLoader::FollowRedirect() is NOT sent to network service. |
| const auto& params = |
| GetLastPendingRequest()->test_url_loader->follow_redirect_params(); |
| EXPECT_THAT(params, IsEmpty()); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, ForwardOnReceiveEarlyHints) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnReceiveEarlyHints: |
| // Expects underlying KeepAliveURLLoader forwards to `renderer_loader_client`. |
| EXPECT_CALL(renderer_loader_client, OnReceiveEarlyHints(_)).Times(1); |
| // Simluates receiving early hints in the network service. |
| GetLastPendingRequest()->client->OnReceiveEarlyHints( |
| CreateEarlyHints(GURL(kTestRequestUrl))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnReceiveEarlyHintsAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveEarlyHints: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveEarlyHints(_)).Times(0); |
| // Simluates receiving early hints in the network service. |
| GetLastPendingRequest()->client->OnReceiveEarlyHints( |
| CreateEarlyHints(GURL(kTestRequestUrl))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, ForwardOnUploadProgress) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnUploadProgress: |
| const int64_t current_position = 5; |
| const int64_t total_size = 100; |
| base::OnceCallback<void()> callback; |
| // Expects underlying KeepAliveURLLoader forwards to `renderer_loader_client`. |
| EXPECT_CALL(renderer_loader_client, |
| OnUploadProgress(Eq(current_position), Eq(total_size), _)) |
| .Times(1) |
| .WillOnce(WithArg<2>([](base::OnceCallback<void()> callback) { |
| // must be consumed. |
| std::move(callback).Run(); |
| })); |
| // Simluates receiving upload progress in the network service. |
| GetLastPendingRequest()->client->OnUploadProgress( |
| current_position, total_size, std::move(callback)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, ForwardOnTransferSizeUpdated) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnTransferSizeUpdated: |
| const int32_t size_diff = 5; |
| // Expects underlying KeepAliveURLLoader forwards to `renderer_loader_client`. |
| EXPECT_CALL(renderer_loader_client, OnTransferSizeUpdated(Eq(size_diff))) |
| .Times(1); |
| // Simluates receiving transfer size update in the network service. |
| GetLastPendingRequest()->client->OnTransferSizeUpdated(size_diff); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| OnTransferSizeUpdatedAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnTransferSizeUpdated: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnTransferSizeUpdated(_)).Times(0); |
| // Simluates receiving transfer size update in the network service. |
| const int32_t size_diff = 5; |
| GetLastPendingRequest()->client->OnTransferSizeUpdated(size_diff); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, ForwardOnComplete) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnComplete: |
| const network::URLLoaderCompletionStatus status{net::OK}; |
| // Expects underlying KeepAliveURLLoader forwards to `renderer_loader_client`. |
| EXPECT_CALL(renderer_loader_client, OnComplete(Eq(status))).Times(1); |
| // Simluates receiving completion status in the network service. |
| GetLastPendingRequest()->client->OnComplete(status); |
| base::RunLoop().RunUntilIdle(); |
| // The KeepAliveURLLoader should have been deleted. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, OnCompleteAfterRendererIsDisconnected) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Disconnects and unbinds the receiver client & remote loader. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnComplete: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| // Simluates receiving completion status in the network service. |
| const network::URLLoaderCompletionStatus status{net::OK}; |
| GetLastPendingRequest()->client->OnComplete(status); |
| base::RunLoop().RunUntilIdle(); |
| // The KeepAliveURLLoader should have been deleted. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, RendererDisconnectedBeforeOnComplete) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // OnReceiveResponse |
| // Simluates receiving response in the network service. |
| GetLastPendingRequest()->client->OnReceiveResponse( |
| CreateResponseHead({{kTestResponseHeaderName, kTestResponseHeaderValue}}), |
| /*body=*/{}, std::nullopt); |
| |
| // Disconnects and unbinds the receiver client & remote loader before |
| // OnComplete is triggered. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The KeepAliveURLLoader should have been deleted, even if OnComplete is not |
| // triggered. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| RendererConnectedAndThrottleCancelLoaderBeforeStartRequest) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| loader_service().SetURLLoaderThrottlesGetterForTesting( |
| base::BindRepeating([]() { |
| std::vector<std::unique_ptr<blink::URLLoaderThrottle>> ret; |
| ret.emplace_back(std::make_unique<ConfigurableURLLoaderThrottle>( |
| /*deferring=*/false, /*canceling_before_start=*/true, |
| /*canceling_before_redirect=*/false)); |
| return ret; |
| })); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The KeepAliveURLLoader should NOT be cancelled by the in-browser throttle, |
| // as the loader is still connected to the renderer and thus should respect |
| // in-renderer throttles. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| RendererDisconnectedAndThrottleCancelLoaderBeforeStartRedirect) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| loader_service().SetURLLoaderThrottlesGetterForTesting( |
| base::BindRepeating([]() { |
| std::vector<std::unique_ptr<blink::URLLoaderThrottle>> ret; |
| ret.emplace_back(std::make_unique<ConfigurableURLLoaderThrottle>( |
| /*deferring=*/false, /*canceling_before_start=*/false, |
| /*canceling_before_redirect=*/true)); |
| return ret; |
| })); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| // Disconnects and unbinds the receiver client & remote loader to simulate |
| // the renderer gets disconnected before redirect. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| DeleteContents(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveRedirect: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(0); |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| // Simluates receiving redirect in the network service. |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| base::RunLoop().RunUntilIdle(); |
| |
| // The KeepAliveURLLoader should be cancelled by the in-browser throttle. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| } |
| |
| TEST_F(KeepAliveURLLoaderServiceTest, |
| RendererDisconnectedAndThrottleDeferLoaderBeforeStartRedirect) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| loader_service().SetURLLoaderThrottlesGetterForTesting( |
| base::BindRepeating([]() { |
| std::vector<std::unique_ptr<blink::URLLoaderThrottle>> ret; |
| ret.emplace_back(std::make_unique<ConfigurableURLLoaderThrottle>( |
| /*deferring=*/true, /*canceling_before_start=*/false, |
| /*canceling_before_redirect=*/false)); |
| return ret; |
| })); |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| CreateResourceRequest(GURL(kTestRequestUrl)), |
| renderer_loader_client.BindNewPipeAndPassRemote()); |
| // Disconnects and unbinds the receiver client & remote loader to simulate |
| // the renderer gets disconnected before redirect. |
| renderer_loader_client.ResetReceiver(); |
| renderer_loader_factory.reset_remote_url_loader(); |
| DeleteContents(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // OnReceiveRedirect: |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // Expects no forwarding. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(0); |
| EXPECT_CALL(renderer_loader_client, OnComplete(_)).Times(0); |
| |
| // As the request loading is deferred by `ConfigurableURLLoaderThrottle` from |
| // the beginning, there should be no requests to the network service. |
| EXPECT_THAT(GetPendingRequests(), IsEmpty()); |
| } |
| |
| class FetchLaterKeepAliveURLLoaderServiceTest |
| : public KeepAliveURLLoaderServiceTestBase { |
| protected: |
| static constexpr base::TimeDelta kDisconnectedLoaderTimeoutForTesting = |
| base::Seconds(15); |
| |
| void SetUp() override { |
| feature_list().InitWithFeaturesAndParameters( |
| {{blink::features::kFetchLaterAPI, {}}, |
| {blink::features::kAttributionReportingInBrowserMigration, {}}, |
| {blink::features::kKeepAliveInBrowserMigration, |
| {{"disconnected_loader_timeout_seconds", |
| base::NumberToString( |
| kDisconnectedLoaderTimeoutForTesting.InSeconds())}}}}, |
| {}); |
| KeepAliveURLLoaderServiceTestBase::SetUp(); |
| } |
| |
| // Asks KeepAliveURLLoaderService to bind a FetchLaterLoaderFactory to the |
| // given `remote_fetch_later_loader_factory`. |
| // More than one factory can be bound to the same service. |
| void BindFetchLaterLoaderFactory( |
| FakeRemoteFetchLaterLoaderFactory& remote_fetch_later_loader_factory) { |
| mojo::Remote<network::mojom::URLLoaderFactory> factory; |
| network_url_loader_factory().Clone(factory.BindNewPipeAndPassReceiver()); |
| auto pending_factory = |
| std::make_unique<network::WrapperPendingSharedURLLoaderFactory>( |
| factory.Unbind()); |
| |
| // Remote: `remote_fetch_later_loader_factory` |
| // Receiver: Held in `loader_service_`. |
| auto context = loader_service().BindFetchLaterLoaderFactory( |
| remote_fetch_later_loader_factory |
| .BindNewEndpointAndPassDedicatedReceiver(), |
| network::SharedURLLoaderFactory::Create(std::move(pending_factory)), |
| static_cast<RenderFrameHostImpl*>(main_rfh()) |
| ->policy_container_host() |
| ->Clone()); |
| |
| GetNavigationRequest()->SetFetchLaterLoaderFactoryContextForTesting( |
| context); |
| pending_navigation_->Commit(); |
| AddConnectSrcCSPToRFH(kTestRedirectRequestUrl); |
| } |
| }; |
| |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, |
| LoadFetchLaterRequestWithInvalidFeatureAndTerminate) { |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| |
| // Loads FetchLater request (which is also keepalive request) under invalid |
| // configuration: |
| feature_list().Reset(); |
| feature_list().InitAndDisableFeature(blink::features::kFetchLaterAPI); |
| renderer_loader_factory.CreateLoader( |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl)), |
| /*expect_success=*/false); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE( |
| renderer_loader_factory.is_remote_fetch_later_loader_connected()); |
| ExpectMojoBadMessage( |
| "Unexpected call to FetchLaterLoaderFactories::CreateLoader()"); |
| } |
| |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, |
| LoadNonFetchLaterRequestAndTerminate) { |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| |
| // Loads non-FetchLater keepalive request. |
| renderer_loader_factory.CreateLoader( |
| CreateResourceRequest(GURL(kTestRequestUrl)), /*expect_success=*/false); |
| |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE( |
| renderer_loader_factory.is_remote_fetch_later_loader_connected()); |
| ExpectMojoBadMessage( |
| "Unexpected `resource_request.is_fetch_later_api` in " |
| "FetchLaterLoaderFactories::CreateLoader(): must be set"); |
| } |
| |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, |
| LoadFetchLaterRequestAndDeferred) { |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| |
| // Loads FetchLater request (which is also keepalive request): |
| renderer_loader_factory.CreateLoader( |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl))); |
| |
| // The KeepAliveURLLoaderService holds a deferred KeepAliveURLLoader. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // As the request is deferred, the pending URLoader in network is 0. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| } |
| |
| // Creates a fetchLater request which is deferred by default. The mojo endpoints |
| // in renderer then gets disconnected, which should start the fetchLater |
| // request. |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, |
| LoadFetchLaterRequestAndLoaderStayAliveAfterRendererIsDisconnected) { |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| |
| // Loads FetchLater request (which is also keepalive request): |
| renderer_loader_factory.CreateLoader( |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl))); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // As the request is deferred, the pending URLoader in network is 0. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| |
| // Simulates a renderer disconnection: |
| // Disconnects and unbinds the remote loader, which should start all deferred |
| // KeepAliveURLLoader. |
| renderer_loader_factory.reset_remote_fetch_later_loader(); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Disconnected KeepAliveURLLoader is still alive. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 1u); |
| // The network should now have created pending URLLoader. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 1); |
| } |
| |
| // Creates a fetchLater request which is deferred by default. The mojo endpoints |
| // in renderer then gets disconnected, and then the loader gets dropped by |
| // browser due to exceeding internal timeout. |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, |
| LoadFetchLaterRequestAndLoaderKilledAfterRendererIsDisconnected) { |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| |
| // Loads FetchLater request (which is also keepalive request): |
| renderer_loader_factory.CreateLoader( |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl))); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // As the request is deferred, the pending URLoader in network is 0. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| |
| // Simulates a renderer disconnection: |
| // Disconnects and unbinds the remote loader, which should start all deferred |
| // KeepAliveURLLoader. |
| renderer_loader_factory.reset_remote_fetch_later_loader(); |
| base::RunLoop().RunUntilIdle(); |
| // Fast forwards to the keepalive disconnect timeout. |
| task_environment()->FastForwardBy(kDisconnectedLoaderTimeoutForTesting); |
| |
| // Disconnected KeepAliveURLLoader should be killed. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 0u); |
| // The network should not create pending URLLoader. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| } |
| |
| // Notifying KeepAliveURLLoaderService about shutdown should start any pending |
| // loaders. |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, Shutdown) { |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| |
| // Loads FetchLater request (which is also keepalive request): |
| renderer_loader_factory.CreateLoader( |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl))); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // As the request is deferred, the pending URLoader in network is 0. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| |
| loader_service().Shutdown(); |
| |
| // The pending loader should still exist. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // There should be no disconnected loader. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 0u); |
| // The network should now have created pending URLLoader. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 1); |
| } |
| |
| TEST_F(FetchLaterKeepAliveURLLoaderServiceTest, |
| ForwardRedirectsAndResponseToAttributionRequestHelper) { |
| // The Attribution Manager uses the DataDecoder service, which, when an |
| // InProcessDataDecoer object exists, will route to an internal in-process |
| // instance. |
| data_decoder::test::InProcessDataDecoder in_process_data_decoder; |
| |
| // Set up the Attribution Manager. |
| auto mock_manager = std::make_unique<MockAttributionManager>(); |
| mock_manager->SetDataHostManager( |
| std::make_unique<AttributionDataHostManagerImpl>(mock_manager.get())); |
| MockAttributionManager* mock_attribution_manager = mock_manager.get(); |
| static_cast<StoragePartitionImpl*>( |
| browser_context()->GetDefaultStoragePartition()) |
| ->OverrideAttributionManagerForTesting(std::move(mock_manager)); |
| |
| // Loads FetchLater request (which is also keepalive request): |
| FakeRemoteFetchLaterLoaderFactory renderer_loader_factory; |
| BindFetchLaterLoaderFactory(renderer_loader_factory); |
| network::ResourceRequest request = |
| CreateFetchLaterResourceRequest(GURL(kTestRequestUrl)); |
| request.attribution_reporting_eligibility = |
| network::mojom::AttributionReportingEligibility::kEventSourceOrTrigger; |
| renderer_loader_factory.CreateLoader(std::move(request)); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // As the request is deferred, the pending URLoader in network is 0. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 0); |
| // Simulate a shutdown to start the pending request. |
| loader_service().Shutdown(); |
| // The pending loader should still exist. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| // There should be no disconnected loader. |
| EXPECT_EQ(loader_service().NumDisconnectedLoadersForTesting(), 0u); |
| // The network should now have created pending URLLoader. |
| EXPECT_EQ(network_url_loader_factory().NumPending(), 1); |
| |
| base::RunLoop run_loop_1; |
| |
| // Simluates receiving a redirect in the network service. |
| EXPECT_CALL(*mock_attribution_manager, HandleTrigger) |
| .WillOnce([&](AttributionTrigger, GlobalRenderFrameHostId) { |
| run_loop_1.Quit(); |
| }); |
| constexpr char kRegisterTriggerJson[] = R"json({ })json"; |
| GetLastPendingRequest()->client->OnReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead({{kAttributionReportingRegisterTriggerHeader, |
| kRegisterTriggerJson}})); |
| run_loop_1.Run(); |
| |
| base::RunLoop run_loop_2; |
| |
| // Simluates receiving response in the network service. |
| EXPECT_CALL(*mock_attribution_manager, HandleSource) |
| .WillOnce( |
| [&](StorableSource, GlobalRenderFrameHostId) { run_loop_2.Quit(); }); |
| constexpr char kRegisterSourceJson[] = |
| R"json({"destination":"https://destination.example"})json"; |
| GetLastPendingRequest()->client->OnReceiveResponse( |
| CreateResponseHead( |
| {{kAttributionReportingRegisterSourceHeader, kRegisterSourceJson}}), |
| /*body=*/{}, /*cached_metadata=*/std::nullopt); |
| run_loop_2.Run(); |
| } |
| |
| class KeepAliveURLLoaderServiceRetryTest |
| : public KeepAliveURLLoaderServiceTestBase { |
| protected: |
| static constexpr int kMaxRetryCountForTesting = 10; |
| static constexpr base::TimeDelta kMinRetryDeltaForTesting = base::Seconds(10); |
| static constexpr double kMinRetryBackoffFactorForTesting = 10.0; |
| static constexpr base::TimeDelta kMaxRetryAgeForTesting = base::Days(1); |
| |
| void SetUp() override { |
| feature_list().InitWithFeaturesAndParameters( |
| {{blink::features::kKeepAliveInBrowserMigration, {}}, |
| {blink::features::kAttributionReportingInBrowserMigration, {}}, |
| {blink::features::kFetchRetry, |
| { |
| {"max_retry_count", |
| base::NumberToString(kMaxRetryCountForTesting)}, |
| {"min_retry_delta", |
| base::NumberToString(kMinRetryDeltaForTesting.InSeconds()) + |
| "s"}, |
| {"min_retry_backoff", |
| base::NumberToString(kMinRetryBackoffFactorForTesting)}, |
| {"max_retry_age", |
| base::NumberToString(kMaxRetryAgeForTesting.InDays()) + "d"}, |
| }}}, |
| {}); |
| KeepAliveURLLoaderServiceTestBase::SetUp(); |
| } |
| }; |
| |
| // Test that setting retry options above the feature-param controlled limits |
| // results in adjustment of some of the options. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, AboveRetryLimits) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = kMaxRetryCountForTesting + 10; |
| options.initial_delay = kMinRetryDeltaForTesting + base::Seconds(10); |
| options.backoff_factor = kMinRetryBackoffFactorForTesting + 10.0; |
| options.max_age = kMaxRetryAgeForTesting + base::Seconds(10); |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| // Max attempt will adjust to the feature param-controlled max attempt, |
| // instead of using the requested max attempt. |
| EXPECT_NE(loader->GetMaxAttemptsForRetry(), options.max_attempts); |
| EXPECT_EQ(loader->GetMaxAttemptsForRetry(), kMaxRetryCountForTesting); |
| |
| // Initial delay will follow the requested initial delay, since it's ok to |
| // exceed the feature param-controlled minimum initial delay. |
| EXPECT_EQ(loader->GetInitialTimeDeltaForRetry(), options.initial_delay); |
| EXPECT_NE(loader->GetInitialTimeDeltaForRetry(), kMinRetryDeltaForTesting); |
| |
| // Backoff factor will follow the requested backoff factor, since it's ok to |
| // exceed the feature param-controlled minimum backoff factor. |
| EXPECT_EQ(loader->GetBackoffFactorForRetry(), options.backoff_factor); |
| EXPECT_NE(loader->GetBackoffFactorForRetry(), |
| kMinRetryBackoffFactorForTesting); |
| |
| // Max age will adjust to the feature param-controlled max age, |
| // instead of using the requested max age. |
| EXPECT_NE(loader->GetMaxAgeForRetry(), options.max_age); |
| EXPECT_EQ(loader->GetMaxAgeForRetry(), kMaxRetryAgeForTesting); |
| } |
| |
| // Test that setting retry options below the feature-param controlled limits |
| // results in adjustment of some of the options. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, BelowRetryLimits) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = kMaxRetryCountForTesting - 1; |
| options.initial_delay = kMinRetryDeltaForTesting - base::Milliseconds(10); |
| options.backoff_factor = kMinRetryBackoffFactorForTesting - 1.0; |
| options.max_age = kMaxRetryAgeForTesting - base::Seconds(10); |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| // Max attempt will follow the requested options, since it's ok to go below |
| // the feature param-controlled minimum max attempt. |
| EXPECT_EQ(loader->GetMaxAttemptsForRetry(), options.max_attempts); |
| EXPECT_NE(loader->GetMaxAttemptsForRetry(), kMaxRetryCountForTesting); |
| |
| // Initial delay will adjust to the feature param-controlled min initial |
| // delay, instead of using the requested initial delay. |
| EXPECT_NE(loader->GetInitialTimeDeltaForRetry(), options.initial_delay); |
| EXPECT_EQ(loader->GetInitialTimeDeltaForRetry(), kMinRetryDeltaForTesting); |
| |
| // Backoff factor will adjust to the feature param-controlled min backoff |
| // factor, instead of using the requested backoff factor. |
| EXPECT_NE(loader->GetBackoffFactorForRetry(), options.backoff_factor); |
| EXPECT_EQ(loader->GetBackoffFactorForRetry(), |
| kMinRetryBackoffFactorForTesting); |
| |
| // Max age will follow the requested options, since it's ok to go below the |
| // feature param-controlled minimum max age. |
| EXPECT_EQ(loader->GetMaxAgeForRetry(), options.max_age); |
| EXPECT_NE(loader->GetMaxAgeForRetry(), kMaxRetryAgeForTesting); |
| } |
| |
| // Test that setting only the max attempt would cause the other options to use |
| // default values set by the feature params. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, RetryLimitsDefaults) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| // Max attempt will follow the requested options. |
| EXPECT_EQ(loader->GetMaxAttemptsForRetry(), options.max_attempts); |
| EXPECT_NE(loader->GetMaxAttemptsForRetry(), kMaxRetryCountForTesting); |
| |
| // All other options will use the feature param-controlled values. |
| EXPECT_EQ(loader->GetInitialTimeDeltaForRetry(), kMinRetryDeltaForTesting); |
| EXPECT_EQ(loader->GetBackoffFactorForRetry(), |
| kMinRetryBackoffFactorForTesting); |
| EXPECT_EQ(loader->GetMaxAgeForRetry(), kMaxRetryAgeForTesting); |
| } |
| |
| // Test which errors are eligible for retry. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, ErrorCodeRetryEligibility) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| net::Error eligible_errors[] = { |
| net::ERR_TIMED_OUT, net::ERR_CONNECTION_TIMED_OUT, |
| net::ERR_CONNECTION_CLOSED, net::ERR_CONNECTION_REFUSED, |
| net::ERR_CONNECTION_RESET, net::ERR_CONNECTION_FAILED, |
| net::ERR_ADDRESS_UNREACHABLE, net::ERR_NETWORK_CHANGED, |
| // Proxy/tunnel-specific connection issues. |
| net::ERR_TUNNEL_CONNECTION_FAILED, net::ERR_PROXY_CONNECTION_FAILED, |
| net::ERR_SOCKS_CONNECTION_FAILED, net::ERR_HTTP2_PING_FAILED, |
| net::ERR_HTTP2_PROTOCOL_ERROR, net::ERR_QUIC_PROTOCOL_ERROR, |
| // DNS failures. |
| net::ERR_NAME_NOT_RESOLVED, net::ERR_INTERNET_DISCONNECTED, |
| net::ERR_NAME_RESOLUTION_FAILED}; |
| for (net::Error error : eligible_errors) { |
| ASSERT_TRUE( |
| loader->IsEligibleForRetry(network::URLLoaderCompletionStatus(error))) |
| << " Should be eligible for retry: " << error; |
| } |
| // Not passing an error code is possible for disconnect loader timeout |
| // failure. |
| ASSERT_TRUE(loader->IsEligibleForRetry(std::nullopt)); |
| // Other error codes are not eligible for retry. Testing a sample here. |
| ASSERT_FALSE( |
| loader->IsEligibleForRetry(network::URLLoaderCompletionStatus(net::OK))); |
| ASSERT_FALSE(loader->IsEligibleForRetry( |
| network::URLLoaderCompletionStatus(net::ERR_ABORTED))); |
| } |
| |
| // Test failing with an eligible error with OnComplete causes the fetch to be |
| // retried. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, OnCompleteWillBeRetried) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| } |
| // Test which errors are eligible for retry when opting in to retry only if the |
| // server is not reached yet. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, |
| ErrorCodeRetryEligibility_OnlyIfServerUnreached) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| options.retry_only_if_server_unreached = true; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| net::Error eligible_errors[] = { |
| net::ERR_CONNECTION_REFUSED, net::ERR_ADDRESS_UNREACHABLE, |
| net::ERR_TUNNEL_CONNECTION_FAILED, net::ERR_PROXY_CONNECTION_FAILED, |
| net::ERR_SOCKS_CONNECTION_FAILED, net::ERR_NAME_NOT_RESOLVED, |
| net::ERR_NAME_RESOLUTION_FAILED}; |
| for (net::Error error : eligible_errors) { |
| ASSERT_TRUE( |
| loader->IsEligibleForRetry(network::URLLoaderCompletionStatus(error))) |
| << " Should be eligible for retry: " << error; |
| } |
| net::Error ineligible_errors[] = { |
| net::ERR_TIMED_OUT, net::ERR_CONNECTION_TIMED_OUT, |
| net::ERR_CONNECTION_CLOSED, net::ERR_CONNECTION_RESET, |
| net::ERR_CONNECTION_FAILED, net::ERR_NETWORK_CHANGED, |
| // Proxy/tunnel-specific connection issues. |
| net::ERR_HTTP2_PING_FAILED, net::ERR_HTTP2_PROTOCOL_ERROR, |
| net::ERR_QUIC_PROTOCOL_ERROR, |
| // DNS failures. |
| net::ERR_INTERNET_DISCONNECTED}; |
| for (net::Error error : ineligible_errors) { |
| ASSERT_FALSE( |
| loader->IsEligibleForRetry(network::URLLoaderCompletionStatus(error))) |
| << " Should not be eligible for retry: " << error; |
| } |
| // Not passing an error code is possible for disconnect loader timeout |
| // failure. We can't guarantee that the server has not been reached yet since |
| // there's no error information. |
| ASSERT_FALSE(loader->IsEligibleForRetry(std::nullopt)); |
| // Other error codes are also not eligible for retry. Testing a sample here. |
| ASSERT_FALSE( |
| loader->IsEligibleForRetry(network::URLLoaderCompletionStatus(net::OK))); |
| ASSERT_FALSE(loader->IsEligibleForRetry( |
| network::URLLoaderCompletionStatus(net::ERR_ABORTED))); |
| } |
| |
| // Test failing with an eligible error with CancelWithStatus causes the fetch to |
| // be retreid. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, CancelWithStatusWillBeRetried) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->CancelWithStatus( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| } |
| |
| // Test that failing a request with no retry options won't be retried. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, NoRetryOptionsWillNotBeRetried) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| // The loader is deleted as it can't be retried. |
| EXPECT_FALSE(loader.get()); |
| } |
| |
| // Test that failing a request to non-HTTPs will not be retried. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, NonHTTPSWillNotBeRetried) { |
| #if BUILDFLAG(IS_MAC) |
| // TODO(crbug.com/434660312): Re-enable on macOS 26 once issues with |
| // unexpected test timeout failures are resolved. |
| if (base::mac::MacOSMajorVersion() == 26) { |
| GTEST_SKIP() << "Disabled on macOS Tahoe."; |
| } |
| #endif |
| |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL("http://foo.com")); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| |
| // The loader is deleted after max age, as it can't be retried, and the error |
| // gets forwarded at that time. |
| EXPECT_FALSE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| EXPECT_CALL( |
| renderer_loader_client, |
| OnComplete(network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED))) |
| .Times(1); |
| task_environment()->FastForwardBy(loader->GetMaxAgeForRetry()); |
| EXPECT_FALSE(loader.get()); |
| } |
| |
| // Test that failing a request using a POST method will not be retried if the |
| // retry options doesn't specify it wants to retry non-idempotent failures. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, |
| POSTWillNotBeRetriedUnlessRequested) { |
| #if BUILDFLAG(IS_MAC) |
| // TODO(crbug.com/434660312): Re-enable on macOS 26 once issues with |
| // unexpected test timeout failures are resolved. |
| if (base::mac::MacOSMajorVersion() == 26) { |
| GTEST_SKIP() << "Disabled on macOS Tahoe."; |
| } |
| #endif |
| |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| resource_request.method = "POST"; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| |
| // The loader is deleted after max age, as it can't be retried, and the error |
| // gets forwarded at that time. |
| EXPECT_FALSE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| EXPECT_CALL( |
| renderer_loader_client, |
| OnComplete(network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED))) |
| .Times(1); |
| task_environment()->FastForwardBy(loader->GetMaxAgeForRetry()); |
| EXPECT_FALSE(loader.get()); |
| } |
| |
| // Test that fail all attempts will forward the last error. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, |
| FailedMaxAttemptWillForwardLastError) { |
| #if BUILDFLAG(IS_MAC) |
| // TODO(crbug.com/434660312): Re-enable on macOS 26 once issues with |
| // unexpected test timeout failures are resolved. |
| if (base::mac::MacOSMajorVersion() == 26) { |
| GTEST_SKIP() << "Disabled on macOS Tahoe."; |
| } |
| #endif |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| // First failure. |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| |
| // Second failure. |
| loader->OnComplete(network::URLLoaderCompletionStatus(net::ERR_TIMED_OUT)); |
| // We can only retry once, since that's the max attempt set. |
| EXPECT_FALSE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| EXPECT_FALSE(loader->IsForwardURLLoadStarted()); |
| |
| // The renderer should get the latest OnComplete call when we hit the max age. |
| EXPECT_CALL( |
| renderer_loader_client, |
| OnComplete(network::URLLoaderCompletionStatus(net::ERR_TIMED_OUT))) |
| .Times(1); |
| task_environment()->FastForwardBy(loader->GetMaxAgeForRetry()); |
| // Note that we can't check IsForwardURLLoadStarted() here as we delete the |
| // loader after the OnComplete is forwarded. |
| EXPECT_FALSE(loader.get()); |
| } |
| |
| // Test that failing a request after it has received a response will not be |
| // retried. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, ReceivedResponseWillNotBeRetried) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 1; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->OnReceiveResponse( |
| CreateResponseHead({{kTestResponseHeaderName, kTestResponseHeaderValue}}), |
| /*body=*/{}, std::nullopt); |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| |
| // The loader can't be retried. Note that it won't be immediately deleted like |
| // in other cases, because it will forward the response to the renderer. |
| EXPECT_TRUE(loader->IsForwardURLLoadStarted()); |
| } |
| |
| // Test that hitting the redirect limit won't trigger a retry. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, |
| ExceededRedirectLimitWillNotBeRetried) { |
| #if BUILDFLAG(IS_MAC) |
| // TODO(crbug.com/434660312): Re-enable on macOS 26 once issues with |
| // unexpected test timeout failures are resolved. |
| if (base::mac::MacOSMajorVersion() == 26) { |
| GTEST_SKIP() << "Disabled on macOS Tahoe."; |
| } |
| #endif |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 2; |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| |
| // Simulate hitting kMaxRedirects - 1 redirects, then failing the request. |
| for (int i = 1; i < net::URLRequest::kMaxRedirects; ++i) { |
| loader->EndReceiveRedirect( |
| CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead( |
| {{kTestResponseHeaderName, kTestResponseHeaderValue}})); |
| } |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| // The load should be eligible for retry still. |
| EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| EXPECT_FALSE(loader->IsForwardURLLoadStarted()); |
| |
| // But if we hit another redirect, the loader will fail with |
| // TOO_MANY_REDIRECTS, which is not retriable. |
| loader->EndReceiveRedirect(CreateRedirectInfo(GURL(kTestRedirectRequestUrl)), |
| CreateResponseHead({{kTestResponseHeaderName, |
| kTestResponseHeaderValue}})); |
| |
| // The loader can't be retried. Note that it won't be immediately deleted like |
| // in other cases, because it will forward the redirects to the renderer, but |
| // only after it reached the max age. |
| EXPECT_FALSE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| EXPECT_FALSE(loader->IsForwardURLLoadStarted()); |
| |
| // After hitting max age, the redirects will be forwarded. |
| EXPECT_CALL(renderer_loader_client, OnReceiveRedirect(_, _)).Times(1); |
| task_environment()->FastForwardBy(loader->GetMaxAgeForRetry()); |
| EXPECT_TRUE(loader->IsForwardURLLoadStarted()); |
| } |
| |
| // Check that a retrying loader will be deleted when it reaches max age. |
| TEST_F(KeepAliveURLLoaderServiceRetryTest, SelfDeletionOnMaxAge) { |
| FakeRemoteURLLoaderFactory renderer_loader_factory; |
| MockReceiverURLLoaderClient renderer_loader_client; |
| BindKeepAliveURLLoaderFactory(renderer_loader_factory); |
| |
| auto resource_request = CreateResourceRequest(GURL(kTestRequestUrl)); |
| network::FetchRetryOptions options; |
| options.max_attempts = 10; |
| options.max_age = base::Seconds(10); |
| resource_request.fetch_retry_options = options; |
| |
| // Loads keepalive request: |
| renderer_loader_factory.CreateLoaderAndStart( |
| resource_request, renderer_loader_client.BindNewPipeAndPassRemote()); |
| ASSERT_EQ(network_url_loader_factory().NumPending(), 1); |
| ASSERT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| |
| // Simmulate failure that will cause the loader to attempt retry. |
| base::WeakPtr<KeepAliveURLLoader> loader = |
| loader_service().GetLoaderWithRequestIdForTesting( |
| FakeRemoteURLLoaderFactory::kRequestId); |
| loader->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_NETWORK_CHANGED)); |
| EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| |
| // Fast forwards to just before the max age timeout fires. |
| task_environment()->FastForwardBy(options.max_age.value() - base::Seconds(5)); |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 1u); |
| EXPECT_TRUE(loader->IsAttemptingRetry(/*include_failed_retry=*/false)); |
| |
| // Fast forward to after the max age timeout fires. |
| task_environment()->FastForwardBy(base::Seconds(10)); |
| |
| // The loader should be deleted after hitting max age. |
| EXPECT_EQ(loader_service().NumLoadersForTesting(), 0u); |
| EXPECT_FALSE(loader.get()); |
| } |
| |
| } // namespace content |