| // Copyright 2022 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/preloading/prefetch/prefetch_container.h" |
| |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "components/variations/net/variations_http_headers.h" |
| #include "components/variations/scoped_variations_ids_provider.h" |
| #include "components/variations/variations_ids_provider.h" |
| #include "content/browser/preloading/prefetch/prefetch_document_manager.h" |
| #include "content/browser/preloading/prefetch/prefetch_features.h" |
| #include "content/browser/preloading/prefetch/prefetch_probe_result.h" |
| #include "content/browser/preloading/prefetch/prefetch_status.h" |
| #include "content/browser/preloading/prefetch/prefetch_test_util_internal.h" |
| #include "content/browser/preloading/prefetch/prefetch_type.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/preload_pipeline_info.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/system/string_data_source.h" |
| #include "net/base/isolation_info.h" |
| #include "services/metrics/public/cpp/metrics_utils.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/network/public/cpp/loading_params.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| class PrefetchContainerTestBase : public RenderViewHostTestHarness { |
| public: |
| PrefetchContainerTestBase() |
| : RenderViewHostTestHarness( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| |
| void SetUp() override { |
| RenderViewHostTestHarness::SetUp(); |
| |
| browser_context() |
| ->GetDefaultStoragePartition() |
| ->GetNetworkContext() |
| ->GetCookieManager(cookie_manager_.BindNewPipeAndPassReceiver()); |
| } |
| |
| void TearDown() override { |
| scoped_feature_list_.Reset(); |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| network::mojom::CookieManager* cookie_manager() { |
| return cookie_manager_.get(); |
| } |
| |
| RenderFrameHostImpl* main_rfhi() { |
| return static_cast<RenderFrameHostImpl*>(main_rfh()); |
| } |
| |
| std::unique_ptr<PrefetchContainer> CreateSpeculationRulesPrefetchContainer( |
| const GURL& prefetch_url, |
| base::WeakPtr<PrefetchDocumentManager> prefetch_document_manager = |
| nullptr) { |
| return std::make_unique<PrefetchContainer>( |
| *main_rfhi(), blink::DocumentToken(), prefetch_url, |
| PrefetchType(PreloadingTriggerType::kSpeculationRule, |
| /*use_prefetch_proxy=*/true, |
| blink::mojom::SpeculationEagerness::kEager), |
| blink::mojom::Referrer(), |
| /*no_vary_search_hint=*/std::nullopt, prefetch_document_manager, |
| PreloadPipelineInfo::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrefetch)); |
| } |
| |
| std::unique_ptr<PrefetchContainer> CreateEmbedderPrefetchContainer( |
| const GURL& prefetch_url, |
| const std::optional<url::Origin> referring_origin = std::nullopt) { |
| return std::make_unique<PrefetchContainer>( |
| *web_contents(), prefetch_url, |
| PrefetchType(PreloadingTriggerType::kEmbedder, |
| /*use_prefetch_proxy=*/true), |
| blink::mojom::Referrer(), std::move(referring_origin), |
| /*no_vary_search_hint=*/std::nullopt, |
| PreloadPipelineInfo::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrefetch), |
| /*attempt=*/nullptr); |
| } |
| |
| bool SetCookie(const GURL& url, const std::string& value) { |
| std::unique_ptr<net::CanonicalCookie> cookie( |
| net::CanonicalCookie::CreateForTesting(url, value, base::Time::Now())); |
| |
| EXPECT_TRUE(cookie.get()); |
| |
| bool result = false; |
| base::RunLoop run_loop; |
| |
| net::CookieOptions options; |
| options.set_include_httponly(); |
| options.set_same_site_cookie_context( |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive()); |
| |
| cookie_manager_->SetCanonicalCookie( |
| *cookie.get(), url, options, |
| base::BindOnce( |
| [](bool* result, base::RunLoop* run_loop, |
| net::CookieAccessResult set_cookie_access_result) { |
| *result = set_cookie_access_result.status.IsInclude(); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| |
| // This will run until the cookie is set. |
| run_loop.Run(); |
| |
| // This will run until the cookie listener is updated. |
| task_environment()->RunUntilIdle(); |
| |
| return result; |
| } |
| |
| void UpdatePrefetchRequestMetrics( |
| PrefetchContainer* prefetch_container, |
| const std::optional<network::URLLoaderCompletionStatus>& |
| completion_status, |
| const network::mojom::URLResponseHead* head) { |
| prefetch_container->UpdatePrefetchRequestMetrics(completion_status, head); |
| } |
| |
| protected: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| private: |
| mojo::Remote<network::mojom::CookieManager> cookie_manager_; |
| }; |
| |
| namespace { |
| |
| // Add a redirect hop with dummy redirect info that should be good enough in |
| // most cases. |
| void AddRedirectHop(PrefetchContainer* container, const GURL& url) { |
| net::RedirectInfo redirect_info; |
| redirect_info.status_code = 302; |
| redirect_info.new_method = "GET"; |
| redirect_info.new_url = url; |
| redirect_info.new_site_for_cookies = net::SiteForCookies::FromUrl(url); |
| container->AddRedirectHop(redirect_info); |
| } |
| |
| } // namespace |
| |
| class PrefetchContainerTest |
| : public PrefetchContainerTestBase, |
| public ::testing::WithParamInterface<PrefetchReusableForTests> { |
| private: |
| void SetUp() override { |
| scoped_feature_list_.InitWithFeatureState( |
| features::kPrefetchReusable, |
| GetParam() == PrefetchReusableForTests::kEnabled); |
| PrefetchContainerTestBase::SetUp(); |
| } |
| |
| variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{ |
| variations::VariationsIdsProvider::Mode::kIgnoreSignedInState}; |
| }; |
| |
| class PrefetchContainerXClientDataHeaderTest |
| : public PrefetchContainerTestBase, |
| // In incognito or not. |
| public ::testing::WithParamInterface<bool> { |
| private: |
| void SetUp() override { |
| PrefetchContainerTestBase::SetUp(); |
| } |
| |
| variations::ScopedVariationsIdsProvider scoped_variations_ids_provider_{ |
| variations::VariationsIdsProvider::Mode::kIgnoreSignedInState}; |
| |
| protected: |
| std::unique_ptr<BrowserContext> CreateBrowserContext() override { |
| auto browser_context = std::make_unique<TestBrowserContext>(); |
| auto is_incognito = GetParam(); |
| browser_context->set_is_off_the_record(is_incognito); |
| return browser_context; |
| } |
| }; |
| |
| TEST_P(PrefetchContainerXClientDataHeaderTest, |
| AddHeaderForEligibleUrlOnlyWhenNotInIncognito) { |
| const GURL kTestEligibleUrl = GURL("https://google.com"); |
| |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(kTestEligibleUrl); |
| variations::VariationsIdsProvider::GetInstance()->ForceVariationIds({"1"}, |
| {"2"}); |
| |
| prefetch_container->MakeResourceRequest({}); |
| auto* request = prefetch_container->GetResourceRequest(); |
| bool is_incognito = GetParam(); |
| // Don't add the header when in incognito mode. |
| EXPECT_EQ( |
| request->cors_exempt_headers.HasHeader(variations::kClientDataHeader), |
| !is_incognito); |
| } |
| |
| TEST_P(PrefetchContainerXClientDataHeaderTest, |
| NeverAddHeaderForNonEligibleUrl) { |
| const GURL kTestNonEligibleUrl = GURL("https://non-eligible.com"); |
| |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(kTestNonEligibleUrl); |
| variations::VariationsIdsProvider::GetInstance()->ForceVariationIds({"1"}, |
| {"2"}); |
| |
| prefetch_container->MakeResourceRequest({}); |
| auto* request = prefetch_container->GetResourceRequest(); |
| // Don't ever add the header. |
| EXPECT_FALSE( |
| request->cors_exempt_headers.HasHeader(variations::kClientDataHeader)); |
| } |
| |
| TEST_P(PrefetchContainerXClientDataHeaderTest, |
| AddHeaderForEligibleRedirectUrlOnlyWhenNotInIncognito) { |
| const GURL kTestNonEligibleUrl = GURL("https://non-eligible.com"); |
| const GURL kTestEligibleUrl = GURL("https://google.com"); |
| |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(kTestNonEligibleUrl); |
| variations::VariationsIdsProvider::GetInstance()->ForceVariationIds({"1"}, |
| {"2"}); |
| |
| prefetch_container->MakeResourceRequest({}); |
| auto* request = prefetch_container->GetResourceRequest(); |
| // Don't ever add the header. |
| EXPECT_FALSE( |
| request->cors_exempt_headers.HasHeader(variations::kClientDataHeader)); |
| |
| AddRedirectHop(prefetch_container.get(), kTestEligibleUrl); |
| bool is_incognito = GetParam(); |
| EXPECT_EQ( |
| request->cors_exempt_headers.HasHeader(variations::kClientDataHeader), |
| !is_incognito); |
| } |
| |
| TEST_P(PrefetchContainerXClientDataHeaderTest, |
| NeverAddHeaderForNonEligibleRedirectUrl) { |
| const GURL kTestNonEligibleUrl1 = GURL("https://non-eligible1.com"); |
| const GURL kTestNonEligibleUrl2 = GURL("https://non-eligible2.com"); |
| |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(kTestNonEligibleUrl1); |
| variations::VariationsIdsProvider::GetInstance()->ForceVariationIds({"1"}, |
| {"2"}); |
| |
| prefetch_container->MakeResourceRequest({}); |
| auto* request = prefetch_container->GetResourceRequest(); |
| // Don't ever add the header. |
| EXPECT_FALSE( |
| request->cors_exempt_headers.HasHeader(variations::kClientDataHeader)); |
| |
| AddRedirectHop(prefetch_container.get(), kTestNonEligibleUrl2); |
| EXPECT_FALSE( |
| request->cors_exempt_headers.HasHeader(variations::kClientDataHeader)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(PrefetchContainerXClientDataTests, |
| PrefetchContainerXClientDataHeaderTest, |
| ::testing::Bool()); |
| |
| TEST_P(PrefetchContainerTest, CreatePrefetchContainer) { |
| blink::DocumentToken document_token; |
| PrefetchContainer prefetch_container( |
| *main_rfhi(), document_token, GURL("https://test.com"), |
| PrefetchType(PreloadingTriggerType::kSpeculationRule, |
| /*use_prefetch_proxy=*/true, |
| blink::mojom::SpeculationEagerness::kEager), |
| blink::mojom::Referrer(), |
| /*no_vary_search_hint=*/std::nullopt, |
| /*prefetch_document_manager=*/nullptr, |
| PreloadPipelineInfo::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrefetch)); |
| |
| EXPECT_EQ(prefetch_container.GetReferringRenderFrameHostId(), |
| main_rfh()->GetGlobalId()); |
| EXPECT_EQ(prefetch_container.GetURL(), GURL("https://test.com")); |
| EXPECT_EQ(prefetch_container.GetPrefetchType(), |
| PrefetchType(PreloadingTriggerType::kSpeculationRule, |
| /*use_prefetch_proxy=*/true, |
| blink::mojom::SpeculationEagerness::kEager)); |
| EXPECT_TRUE( |
| prefetch_container.IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| EXPECT_EQ(prefetch_container.key(), |
| PrefetchContainer::Key(document_token, GURL("https://test.com"))); |
| EXPECT_FALSE(prefetch_container.GetNonRedirectHead()); |
| } |
| |
| TEST_P(PrefetchContainerTest, CreatePrefetchContainer_Embedder) { |
| base::test::ScopedFeatureList scoped_feature_list( |
| features::kPrefetchBrowserInitiatedTriggers); |
| PrefetchContainer prefetch_container( |
| *web_contents(), GURL("https://test.com"), |
| PrefetchType(PreloadingTriggerType::kEmbedder, |
| /*use_prefetch_proxy=*/false), |
| blink::mojom::Referrer(), /*referring_origin=*/std::nullopt, |
| /*no_vary_search_hint=*/std::nullopt, |
| PreloadPipelineInfo::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrefetch), |
| /*attempt=*/nullptr); |
| |
| EXPECT_EQ(prefetch_container.GetReferringRenderFrameHostId(), |
| GlobalRenderFrameHostId()); |
| EXPECT_EQ(prefetch_container.GetURL(), GURL("https://test.com")); |
| EXPECT_EQ(prefetch_container.GetPrefetchType(), |
| PrefetchType(PreloadingTriggerType::kEmbedder, |
| /*use_prefetch_proxy=*/false)); |
| EXPECT_FALSE( |
| prefetch_container.IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| EXPECT_EQ(prefetch_container.key(), |
| PrefetchContainer::Key(std::nullopt, GURL("https://test.com"))); |
| EXPECT_FALSE(prefetch_container.GetNonRedirectHead()); |
| } |
| |
| TEST_P(PrefetchContainerTest, PrefetchStatus) { |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| |
| EXPECT_FALSE(prefetch_container->HasPrefetchStatus()); |
| |
| prefetch_container->SetPrefetchStatus(PrefetchStatus::kPrefetchNotStarted); |
| |
| EXPECT_TRUE(prefetch_container->HasPrefetchStatus()); |
| EXPECT_EQ(prefetch_container->GetPrefetchStatus(), |
| PrefetchStatus::kPrefetchNotStarted); |
| } |
| |
| TEST_P(PrefetchContainerTest, IsDecoy) { |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| |
| EXPECT_FALSE(prefetch_container->IsDecoy()); |
| |
| prefetch_container->SetIsDecoy(true); |
| EXPECT_TRUE(prefetch_container->IsDecoy()); |
| } |
| |
| TEST_P(PrefetchContainerTest, Servable) { |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| |
| prefetch_container->SimulatePrefetchEligibleForTest(); |
| MakeServableStreamingURLLoaderForTest(prefetch_container.get(), |
| network::mojom::URLResponseHead::New(), |
| "test body"); |
| |
| task_environment()->FastForwardBy(base::Minutes(2)); |
| |
| EXPECT_NE(prefetch_container->GetServableState(base::Minutes(1)), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_EQ(prefetch_container->GetServableState(base::Minutes(3)), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_TRUE(prefetch_container->GetNonRedirectHead()); |
| } |
| |
| TEST_P(PrefetchContainerTest, CookieListener) { |
| const GURL kTestUrl1 = GURL("https://test1.com"); |
| const GURL kTestUrl2 = GURL("https://test2.com"); |
| const GURL kTestUrl3 = GURL("https://test3.com"); |
| |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer(kTestUrl1); |
| |
| prefetch_container->MakeResourceRequest({}); |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| |
| // Add redirect hops, and register its own cookie listener for each hop. |
| AddRedirectHop(prefetch_container.get(), kTestUrl2); |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| AddRedirectHop(prefetch_container.get(), kTestUrl3); |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| |
| // Check the cookies for `kTestUrl1`, `kTestUrl2` and `kTestUrl3`, |
| // respectively. AdvanceCurrentURLToServe() is used to set the current hop to |
| // check the cookies. |
| { |
| auto reader = prefetch_container->CreateReader(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| } |
| |
| { |
| auto reader = prefetch_container->CreateReader(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| } |
| |
| prefetch_container->PauseAllCookieListeners(); |
| ASSERT_TRUE(SetCookie(kTestUrl1, "test-cookie0")); |
| { |
| auto reader = prefetch_container->CreateReader(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| } |
| |
| prefetch_container->ResumeAllCookieListeners(); |
| ASSERT_TRUE(SetCookie(kTestUrl1, "test-cookie1")); |
| |
| { |
| auto reader = prefetch_container->CreateReader(); |
| EXPECT_TRUE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| } |
| |
| ASSERT_TRUE(SetCookie(kTestUrl2, "test-cookie2")); |
| |
| { |
| auto reader = prefetch_container->CreateReader(); |
| EXPECT_TRUE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_TRUE(reader.HaveDefaultContextCookiesChanged()); |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| } |
| } |
| |
| TEST_P(PrefetchContainerTest, CookieCopy) { |
| const GURL kTestUrl = GURL("https://test.com"); |
| base::HistogramTester histogram_tester; |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer(kTestUrl); |
| |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| |
| auto reader = prefetch_container->CreateReader(); |
| |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| reader.OnIsolatedCookieCopyStart(); |
| |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| // Once the cookie copy process has started, we should stop the cookie |
| // listener. |
| ASSERT_TRUE(SetCookie(kTestUrl, "test-cookie")); |
| EXPECT_FALSE(reader.HaveDefaultContextCookiesChanged()); |
| |
| task_environment()->FastForwardBy(base::Milliseconds(10)); |
| reader.OnIsolatedCookiesReadCompleteAndWriteStart(); |
| task_environment()->FastForwardBy(base::Milliseconds(20)); |
| |
| // The URL interceptor checks on the cookie copy status when trying to serve a |
| // prefetch. If its still in progress, it registers a callback to be called |
| // once the copy is complete. |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| reader.OnInterceptorCheckCookieCopy(); |
| task_environment()->FastForwardBy(base::Milliseconds(40)); |
| bool callback_called = false; |
| reader.SetOnCookieCopyCompleteCallback( |
| base::BindOnce([](bool* callback_called) { *callback_called = true; }, |
| &callback_called)); |
| |
| reader.OnIsolatedCookieCopyComplete(); |
| |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| EXPECT_TRUE(callback_called); |
| |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieReadTime", |
| base::Milliseconds(10), 1); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieWriteTime", |
| base::Milliseconds(60), 1); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieCopyStartToInterceptorCheck", |
| base::Milliseconds(30), 1); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieCopyTime", |
| base::Milliseconds(70), 1); |
| } |
| |
| TEST_P(PrefetchContainerTest, CookieCopyWithRedirects) { |
| const GURL kTestUrl = GURL("https://test.com"); |
| const GURL kRedirectUrl1 = GURL("https://redirect1.com"); |
| const GURL kRedirectUrl2 = GURL("https://redirect2.com"); |
| base::HistogramTester histogram_tester; |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer(kTestUrl); |
| prefetch_container->MakeResourceRequest({}); |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| |
| AddRedirectHop(prefetch_container.get(), kRedirectUrl1); |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| |
| AddRedirectHop(prefetch_container.get(), kRedirectUrl2); |
| prefetch_container->RegisterCookieListener(cookie_manager()); |
| |
| auto reader = prefetch_container->CreateReader(); |
| |
| EXPECT_EQ(reader.GetCurrentURLToServe(), kTestUrl); |
| |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| reader.OnIsolatedCookieCopyStart(); |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| // Once the cookie copy process has started, all cookie listeners are stopped. |
| ASSERT_TRUE(SetCookie(kTestUrl, "test-cookie")); |
| ASSERT_TRUE(SetCookie(kRedirectUrl1, "test-cookie")); |
| ASSERT_TRUE(SetCookie(kRedirectUrl2, "test-cookie")); |
| |
| // Check the cookies for `kTestUrl`, `kRedirectUrl1` and `kRedirectUrl2`, |
| // respectively. AdvanceCurrentURLToServe() is used to set the current |
| // hop to check the cookies. |
| { |
| auto reader1 = prefetch_container->CreateReader(); |
| EXPECT_FALSE(reader1.HaveDefaultContextCookiesChanged()); |
| reader1.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader1.HaveDefaultContextCookiesChanged()); |
| reader1.AdvanceCurrentURLToServe(); |
| EXPECT_FALSE(reader1.HaveDefaultContextCookiesChanged()); |
| } |
| |
| task_environment()->FastForwardBy(base::Milliseconds(10)); |
| reader.OnIsolatedCookiesReadCompleteAndWriteStart(); |
| task_environment()->FastForwardBy(base::Milliseconds(20)); |
| |
| // The URL interceptor checks on the cookie copy status when trying to serve a |
| // prefetch. If its still in progress, it registers a callback to be called |
| // once the copy is complete. |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| reader.OnInterceptorCheckCookieCopy(); |
| task_environment()->FastForwardBy(base::Milliseconds(40)); |
| bool callback_called = false; |
| reader.SetOnCookieCopyCompleteCallback( |
| base::BindOnce([](bool* callback_called) { *callback_called = true; }, |
| &callback_called)); |
| |
| reader.OnIsolatedCookieCopyComplete(); |
| |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| EXPECT_TRUE(callback_called); |
| |
| // Simulate copying cookies for the next redirect hop. |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_EQ(reader.GetCurrentURLToServe(), kRedirectUrl1); |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| reader.OnIsolatedCookieCopyStart(); |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| task_environment()->FastForwardBy(base::Milliseconds(10)); |
| |
| reader.OnIsolatedCookiesReadCompleteAndWriteStart(); |
| task_environment()->FastForwardBy(base::Milliseconds(20)); |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| reader.OnInterceptorCheckCookieCopy(); |
| task_environment()->FastForwardBy(base::Milliseconds(40)); |
| |
| callback_called = false; |
| reader.SetOnCookieCopyCompleteCallback( |
| base::BindOnce([](bool* callback_called) { *callback_called = true; }, |
| &callback_called)); |
| |
| reader.OnIsolatedCookieCopyComplete(); |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| EXPECT_TRUE(callback_called); |
| |
| // Simulate copying cookies for the last redirect hop. |
| reader.AdvanceCurrentURLToServe(); |
| EXPECT_EQ(reader.GetCurrentURLToServe(), kRedirectUrl2); |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| reader.OnIsolatedCookieCopyStart(); |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| task_environment()->FastForwardBy(base::Milliseconds(10)); |
| |
| reader.OnIsolatedCookiesReadCompleteAndWriteStart(); |
| task_environment()->FastForwardBy(base::Milliseconds(20)); |
| EXPECT_TRUE(reader.IsIsolatedCookieCopyInProgress()); |
| |
| reader.OnInterceptorCheckCookieCopy(); |
| task_environment()->FastForwardBy(base::Milliseconds(40)); |
| |
| callback_called = false; |
| reader.SetOnCookieCopyCompleteCallback( |
| base::BindOnce([](bool* callback_called) { *callback_called = true; }, |
| &callback_called)); |
| |
| reader.OnIsolatedCookieCopyComplete(); |
| EXPECT_FALSE(reader.IsIsolatedCookieCopyInProgress()); |
| EXPECT_TRUE(callback_called); |
| |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieReadTime", |
| base::Milliseconds(10), 3); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieWriteTime", |
| base::Milliseconds(60), 3); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieCopyStartToInterceptorCheck", |
| base::Milliseconds(30), 3); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.Mainframe.CookieCopyTime", |
| base::Milliseconds(70), 3); |
| } |
| |
| TEST_P(PrefetchContainerTest, PrefetchProxyPrefetchedResourceUkm) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| |
| prefetch_container->SimulatePrefetchEligibleForTest(); |
| |
| network::URLLoaderCompletionStatus completion_status; |
| completion_status.encoded_data_length = 100; |
| completion_status.completion_time = |
| base::TimeTicks() + base::Milliseconds(200); |
| |
| network::mojom::URLResponseHeadPtr head = |
| network::mojom::URLResponseHead::New(); |
| head->load_timing.request_start = base::TimeTicks(); |
| |
| UpdatePrefetchRequestMetrics(prefetch_container.get(), completion_status, |
| head.get()); |
| |
| MakeServableStreamingURLLoaderForTest(prefetch_container.get(), |
| network::mojom::URLResponseHead::New(), |
| "test body"); |
| |
| // Simulates the URL of the prefetch being navigated to and the prefetch being |
| // considered for serving. |
| prefetch_container->OnUnregisterCandidate(GURL("https://test.com"), |
| /*is_served=*/true, |
| /*blocked_duration=*/std::nullopt); |
| |
| // Simulate a successful DNS probe for this prefetch. Not this will also |
| // update the status of the prefetch to |
| // |PrefetchStatus::kPrefetchUsedProbeSuccess|. |
| prefetch_container->CreateReader().OnPrefetchProbeResult( |
| PrefetchProbeResult::kDNSProbeSuccess); |
| |
| // Deleting the prefetch container will trigger the recording of the |
| // PrefetchProxy_PrefetchedResource UKM event. |
| prefetch_container.reset(); |
| |
| auto ukm_entries = ukm_recorder.GetEntries( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kEntryName, |
| { |
| ukm::builders::PrefetchProxy_PrefetchedResource::kResourceTypeName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkClickedName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kDataLengthName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kFetchDurationMSName, |
| ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kISPFilteringStatusName, |
| ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kNavigationStartToFetchStartMSName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkPositionName, |
| }); |
| |
| ASSERT_EQ(ukm_entries.size(), 1U); |
| EXPECT_EQ(ukm_entries[0].source_id, ukm::kInvalidSourceId); |
| |
| const auto& ukm_metrics = ukm_entries[0].metrics; |
| |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kResourceTypeName) != |
| ukm_metrics.end()); |
| EXPECT_EQ( |
| ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kResourceTypeName), |
| /*mainfrmae*/ 1); |
| |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName) != |
| ukm_metrics.end()); |
| EXPECT_EQ(ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName), |
| static_cast<int>(PrefetchStatus::kPrefetchResponseUsed)); |
| |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkClickedName) != |
| ukm_metrics.end()); |
| EXPECT_EQ( |
| ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkClickedName), |
| 1); |
| |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kDataLengthName) != |
| ukm_metrics.end()); |
| EXPECT_EQ( |
| ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kDataLengthName), |
| ukm::GetExponentialBucketMinForBytes(100)); |
| |
| ASSERT_TRUE(ukm_metrics.find(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kFetchDurationMSName) != ukm_metrics.end()); |
| EXPECT_EQ(ukm_metrics.at(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kFetchDurationMSName), |
| 200); |
| |
| ASSERT_TRUE(ukm_metrics.find(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kISPFilteringStatusName) != |
| ukm_metrics.end()); |
| EXPECT_EQ(ukm_metrics.at(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kISPFilteringStatusName), |
| static_cast<int>(PrefetchProbeResult::kDNSProbeSuccess)); |
| |
| // These fields are not set and should not be in the UKM event. |
| EXPECT_TRUE(ukm_metrics.find(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kNavigationStartToFetchStartMSName) == |
| ukm_metrics.end()); |
| EXPECT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkPositionName) == |
| ukm_metrics.end()); |
| } |
| |
| TEST_P(PrefetchContainerTest, PrefetchProxyPrefetchedResourceUkm_NothingSet) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| prefetch_container.reset(); |
| |
| auto ukm_entries = ukm_recorder.GetEntries( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kEntryName, |
| { |
| ukm::builders::PrefetchProxy_PrefetchedResource::kResourceTypeName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkClickedName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kDataLengthName, |
| ukm::builders::PrefetchProxy_PrefetchedResource::kFetchDurationMSName, |
| ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kISPFilteringStatusName, |
| }); |
| |
| ASSERT_EQ(ukm_entries.size(), 1U); |
| EXPECT_EQ(ukm_entries[0].source_id, ukm::kInvalidSourceId); |
| |
| const auto& ukm_metrics = ukm_entries[0].metrics; |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kResourceTypeName) != |
| ukm_metrics.end()); |
| EXPECT_EQ( |
| ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kResourceTypeName), |
| /*mainfrmae*/ 1); |
| |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName) != |
| ukm_metrics.end()); |
| EXPECT_EQ(ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kStatusName), |
| static_cast<int>(PrefetchStatus::kPrefetchNotStarted)); |
| |
| ASSERT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkClickedName) != |
| ukm_metrics.end()); |
| EXPECT_EQ( |
| ukm_metrics.at( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kLinkClickedName), |
| 0); |
| |
| EXPECT_TRUE( |
| ukm_metrics.find( |
| ukm::builders::PrefetchProxy_PrefetchedResource::kDataLengthName) == |
| ukm_metrics.end()); |
| EXPECT_TRUE(ukm_metrics.find(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kFetchDurationMSName) == ukm_metrics.end()); |
| EXPECT_TRUE(ukm_metrics.find(ukm::builders::PrefetchProxy_PrefetchedResource:: |
| kISPFilteringStatusName) == |
| ukm_metrics.end()); |
| } |
| |
| TEST_P(PrefetchContainerTest, EligibilityCheck) { |
| const GURL kTestUrl1 = GURL("https://test1.com"); |
| const GURL kTestUrl2 = GURL("https://test2.com"); |
| |
| base::HistogramTester histogram_tester; |
| |
| auto* prefetch_document_manager = |
| PrefetchDocumentManager::GetOrCreateForCurrentDocument( |
| &web_contents()->GetPrimaryPage().GetMainDocument()); |
| |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer( |
| kTestUrl1, prefetch_document_manager->GetWeakPtr()); |
| |
| prefetch_container->MakeResourceRequest({}); |
| |
| // Mark initial prefetch as eligible |
| prefetch_container->OnEligibilityCheckComplete( |
| PreloadingEligibility::kEligible); |
| |
| EXPECT_EQ(prefetch_document_manager->GetReferringPageMetrics() |
| .prefetch_eligible_count, |
| 1); |
| |
| // Add a redirect, register a callback for it, and then mark it as eligible. |
| AddRedirectHop(prefetch_container.get(), kTestUrl2); |
| prefetch_container->OnEligibilityCheckComplete( |
| PreloadingEligibility::kEligible); |
| |
| // Referring page metrics is only incremented for the original prefetch URL |
| // and not any redirects. |
| EXPECT_EQ(prefetch_document_manager->GetReferringPageMetrics() |
| .prefetch_eligible_count, |
| 1); |
| } |
| |
| TEST_P(PrefetchContainerTest, IneligibleRedirect) { |
| const GURL kTestUrl1 = GURL("https://test1.com"); |
| const GURL kTestUrl2 = GURL("https://test2.com"); |
| |
| base::HistogramTester histogram_tester; |
| |
| auto* prefetch_document_manager = |
| PrefetchDocumentManager::GetOrCreateForCurrentDocument( |
| &web_contents()->GetPrimaryPage().GetMainDocument()); |
| |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer( |
| kTestUrl1, prefetch_document_manager->GetWeakPtr()); |
| |
| prefetch_container->MakeResourceRequest({}); |
| |
| // Mark initial prefetch as eligible |
| prefetch_container->OnEligibilityCheckComplete( |
| PreloadingEligibility::kEligible); |
| |
| EXPECT_EQ(prefetch_document_manager->GetReferringPageMetrics() |
| .prefetch_eligible_count, |
| 1); |
| |
| // Add a redirect, register a callback for it, and then mark it as ineligible. |
| AddRedirectHop(prefetch_container.get(), kTestUrl2); |
| prefetch_container->OnEligibilityCheckComplete( |
| PreloadingEligibility::kUserHasCookies); |
| |
| // Ineligible redirects are treated as failed prefetches, and not ineligible |
| // prefetches. |
| EXPECT_EQ(prefetch_document_manager->GetReferringPageMetrics() |
| .prefetch_eligible_count, |
| 1); |
| EXPECT_EQ(prefetch_container->GetPrefetchStatus(), |
| PrefetchStatus::kPrefetchFailedIneligibleRedirect); |
| } |
| |
| TEST_P(PrefetchContainerTest, BlockUntilHeadHistograms2) { |
| struct TestCase { |
| blink::mojom::SpeculationEagerness eagerness; |
| bool is_served; |
| std::optional<base::TimeDelta> prefetch_match_resolver_wait_duration; |
| }; |
| |
| std::vector<TestCase> test_cases{ |
| {blink::mojom::SpeculationEagerness::kEager, true, std::nullopt}, |
| {blink::mojom::SpeculationEagerness::kModerate, true, |
| base::Milliseconds(10)}, |
| {blink::mojom::SpeculationEagerness::kConservative, false, |
| base::Milliseconds(20)}}; |
| |
| base::HistogramTester histogram_tester; |
| for (const auto& test_case : test_cases) { |
| PrefetchContainer prefetch_container( |
| *main_rfhi(), blink::DocumentToken(), |
| GURL("https://test.com/?nvsparam=1"), |
| PrefetchType(PreloadingTriggerType::kSpeculationRule, |
| /*use_prefetch_proxy=*/true, test_case.eagerness), |
| blink::mojom::Referrer(), |
| /*no_vary_search_hint=*/std::nullopt, |
| /*prefetch_document_manager=*/nullptr, |
| PreloadPipelineInfo::Create( |
| /*planned_max_preloading_type=*/PreloadingType::kPrefetch)); |
| |
| prefetch_container.OnUnregisterCandidate( |
| GURL("https://test.com/"), test_case.is_served, |
| test_case.prefetch_match_resolver_wait_duration); |
| } |
| |
| histogram_tester.ExpectBucketCount( |
| "PrefetchProxy.AfterClick.PrefetchMatchingBlockedNavigationWithPrefetch." |
| "Eager", |
| true, 0); |
| histogram_tester.ExpectBucketCount( |
| "PrefetchProxy.AfterClick.PrefetchMatchingBlockedNavigationWithPrefetch." |
| "Eager", |
| false, 1); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2NoBias.Served.Eager", |
| base::Milliseconds(0), 1); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2NoBias.NotServed.Eager", |
| 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2.Served.Eager", 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2.NotServed.Eager", 0); |
| |
| histogram_tester.ExpectBucketCount( |
| "PrefetchProxy.AfterClick.PrefetchMatchingBlockedNavigationWithPrefetch." |
| "Moderate", |
| true, 1); |
| histogram_tester.ExpectBucketCount( |
| "PrefetchProxy.AfterClick.PrefetchMatchingBlockedNavigationWithPrefetch." |
| "Moderate", |
| false, 0); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2NoBias.Served.Moderate", |
| base::Milliseconds(10), 1); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2NoBias.NotServed." |
| "Moderate", |
| 0); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2.Served.Moderate", |
| base::Milliseconds(10), 1); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2.NotServed.Moderate", 0); |
| |
| histogram_tester.ExpectBucketCount( |
| "PrefetchProxy.AfterClick.PrefetchMatchingBlockedNavigationWithPrefetch." |
| "Conservative", |
| true, 1); |
| histogram_tester.ExpectBucketCount( |
| "PrefetchProxy.AfterClick.PrefetchMatchingBlockedNavigationWithPrefetch." |
| "Conservative", |
| false, 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2NoBias.Served." |
| "Conservative", |
| 0); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2NoBias.NotServed." |
| "Conservative", |
| base::Milliseconds(20), 1); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2.Served.Conservative", |
| 0); |
| histogram_tester.ExpectUniqueTimeSample( |
| "PrefetchProxy.AfterClick.BlockUntilHeadDuration2.NotServed.Conservative", |
| base::Milliseconds(20), 1); |
| } |
| |
| TEST_P(PrefetchContainerTest, RecordRedirectChainSize) { |
| base::HistogramTester histogram_tester; |
| |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| prefetch_container->MakeResourceRequest({}); |
| |
| prefetch_container->SetPrefetchStatus( |
| PrefetchStatus::kPrefetchNotFinishedInTime); |
| |
| AddRedirectHop(prefetch_container.get(), GURL("https://redirect1.com")); |
| AddRedirectHop(prefetch_container.get(), GURL("https://redirect2.com")); |
| prefetch_container->OnPrefetchComplete(network::URLLoaderCompletionStatus()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.RedirectChainSize", 3, 1); |
| } |
| |
| TEST_P(PrefetchContainerTest, IsIsolatedNetworkRequired) { |
| NavigationSimulator::NavigateAndCommitFromBrowser( |
| web_contents(), GURL("https://test.com/referrer")); |
| auto prefetch_container_same_origin = CreateSpeculationRulesPrefetchContainer( |
| GURL("https://test.com/prefetch")); |
| prefetch_container_same_origin->MakeResourceRequest({}); |
| EXPECT_FALSE(prefetch_container_same_origin |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| NavigationSimulator::NavigateAndCommitFromBrowser( |
| web_contents(), GURL("https://other.com/referrer")); |
| auto prefetch_container_cross_origin = |
| CreateSpeculationRulesPrefetchContainer( |
| GURL("https://test.com/prefetch")); |
| prefetch_container_cross_origin->MakeResourceRequest({}); |
| EXPECT_TRUE(prefetch_container_cross_origin |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| } |
| |
| TEST_P(PrefetchContainerTest, IsIsolatedNetworkRequired_Embedder) { |
| base::test::ScopedFeatureList scoped_feature_list( |
| features::kPrefetchBrowserInitiatedTriggers); |
| auto prefetch_container_default = CreateEmbedderPrefetchContainer( |
| GURL("https://test.com/prefetch"), std::nullopt); |
| prefetch_container_default->MakeResourceRequest({}); |
| EXPECT_FALSE(prefetch_container_default |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| auto prefetch_container_same_origin = CreateEmbedderPrefetchContainer( |
| GURL("https://test.com/prefetch"), |
| url::Origin::Create(GURL("https://test.com/referrer"))); |
| prefetch_container_same_origin->MakeResourceRequest({}); |
| EXPECT_FALSE(prefetch_container_same_origin |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| auto prefetch_container_cross_origin = CreateEmbedderPrefetchContainer( |
| GURL("https://test.com/prefetch"), |
| url::Origin::Create(GURL("https://other.com/referrer"))); |
| prefetch_container_cross_origin->MakeResourceRequest({}); |
| EXPECT_TRUE(prefetch_container_cross_origin |
| ->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| } |
| |
| TEST_P(PrefetchContainerTest, IsIsolatedNetworkRequiredWithRedirect) { |
| NavigationSimulator::NavigateAndCommitFromBrowser( |
| web_contents(), GURL("https://test.com/referrer")); |
| |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer( |
| GURL("https://test.com/prefetch")); |
| |
| prefetch_container->MakeResourceRequest({}); |
| |
| EXPECT_FALSE( |
| prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| |
| AddRedirectHop(prefetch_container.get(), GURL("https://test.com/redirect")); |
| |
| EXPECT_FALSE( |
| prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| EXPECT_FALSE(prefetch_container |
| ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()); |
| |
| AddRedirectHop(prefetch_container.get(), GURL("https://m.test.com/redirect")); |
| |
| EXPECT_FALSE( |
| prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| EXPECT_FALSE(prefetch_container |
| ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()); |
| |
| AddRedirectHop(prefetch_container.get(), GURL("https://other.com/redirect1")); |
| |
| EXPECT_TRUE( |
| prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| EXPECT_FALSE(prefetch_container |
| ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()); |
| |
| AddRedirectHop(prefetch_container.get(), GURL("https://other.com/redirect2")); |
| |
| EXPECT_TRUE( |
| prefetch_container->IsIsolatedNetworkContextRequiredForCurrentPrefetch()); |
| EXPECT_TRUE(prefetch_container |
| ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()); |
| } |
| |
| TEST_P(PrefetchContainerTest, MultipleStreamingURLLoaders) { |
| const GURL kTestUrl1 = GURL("https://test1.com"); |
| const GURL kTestUrl2 = GURL("https://test2.com"); |
| |
| base::HistogramTester histogram_tester; |
| |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer(kTestUrl1); |
| |
| prefetch_container->MakeResourceRequest({}); |
| |
| EXPECT_FALSE(prefetch_container->GetStreamingURLLoader()); |
| |
| EXPECT_NE(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_FALSE(prefetch_container->GetNonRedirectHead()); |
| |
| prefetch_container->SimulatePrefetchEligibleForTest(); |
| MakeServableStreamingURLLoadersWithNetworkTransitionRedirectForTest( |
| prefetch_container.get(), kTestUrl1, kTestUrl2); |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_TRUE(prefetch_container->GetNonRedirectHead()); |
| |
| // As the prefetch is already completed, the streaming loader is deleted |
| // asynchronously. |
| EXPECT_TRUE( |
| prefetch_container->IsStreamingURLLoaderDeletionScheduledForTesting()); |
| task_environment()->RunUntilIdle(); |
| EXPECT_FALSE(prefetch_container->GetStreamingURLLoader()); |
| |
| PrefetchContainer::Reader reader = prefetch_container->CreateReader(); |
| |
| base::WeakPtr<PrefetchResponseReader> weak_first_response_reader = |
| reader.GetCurrentResponseReaderToServeForTesting(); |
| PrefetchRequestHandler first_request_handler = reader.CreateRequestHandler(); |
| |
| base::WeakPtr<PrefetchResponseReader> weak_second_response_reader = |
| reader.GetCurrentResponseReaderToServeForTesting(); |
| PrefetchRequestHandler second_request_handler = reader.CreateRequestHandler(); |
| |
| // `CreateRequestHandler()` itself doesn't make the PrefetchContainer |
| // non-servable. |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_TRUE(prefetch_container->GetNonRedirectHead()); |
| |
| std::unique_ptr<PrefetchTestURLLoaderClient> first_serving_url_loader_client = |
| std::make_unique<PrefetchTestURLLoaderClient>(); |
| network::ResourceRequest first_serving_request; |
| first_serving_request.url = kTestUrl1; |
| first_serving_request.method = "GET"; |
| |
| std::move(first_request_handler) |
| .Run(first_serving_request, |
| first_serving_url_loader_client->BindURLloaderAndGetReceiver(), |
| first_serving_url_loader_client->BindURLLoaderClientAndGetRemote()); |
| |
| std::unique_ptr<PrefetchTestURLLoaderClient> |
| second_serving_url_loader_client = |
| std::make_unique<PrefetchTestURLLoaderClient>(); |
| network::ResourceRequest second_serving_request; |
| second_serving_request.url = kTestUrl2; |
| second_serving_request.method = "GET"; |
| |
| std::move(second_request_handler) |
| .Run(second_serving_request, |
| second_serving_url_loader_client->BindURLloaderAndGetReceiver(), |
| second_serving_url_loader_client->BindURLLoaderClientAndGetRemote()); |
| |
| prefetch_container.reset(); |
| |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_EQ(first_serving_url_loader_client->received_redirects().size(), 1u); |
| |
| EXPECT_EQ(second_serving_url_loader_client->body_content(), "test body"); |
| EXPECT_TRUE( |
| second_serving_url_loader_client->completion_status().has_value()); |
| |
| EXPECT_TRUE(weak_first_response_reader); |
| EXPECT_TRUE(weak_second_response_reader); |
| |
| first_serving_url_loader_client->DisconnectMojoPipes(); |
| second_serving_url_loader_client->DisconnectMojoPipes(); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_FALSE(weak_first_response_reader); |
| EXPECT_FALSE(weak_second_response_reader); |
| } |
| |
| TEST_P(PrefetchContainerTest, CancelAndClearStreamingLoader) { |
| const GURL kTestUrl1 = GURL("https://test1.com"); |
| const GURL kTestUrl2 = GURL("https://test2.com"); |
| |
| base::HistogramTester histogram_tester; |
| |
| auto prefetch_container = CreateSpeculationRulesPrefetchContainer(kTestUrl1); |
| |
| prefetch_container->MakeResourceRequest({}); |
| |
| prefetch_container->SimulatePrefetchEligibleForTest(); |
| auto pending_request = |
| MakeManuallyServableStreamingURLLoaderForTest(prefetch_container.get()); |
| |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| CHECK_EQ(mojo::CreateDataPipe(1024, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| pending_request.client->OnReceiveResponse( |
| network::mojom::URLResponseHead::New(), std::move(consumer_handle), |
| std::nullopt); |
| task_environment()->RunUntilIdle(); |
| |
| // Prefetching is ongoing. |
| ASSERT_TRUE(prefetch_container->GetStreamingURLLoader()); |
| base::WeakPtr<PrefetchStreamingURLLoader> streaming_loader = |
| prefetch_container->GetStreamingURLLoader(); |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| |
| prefetch_container->CancelStreamingURLLoaderIfNotServing(); |
| |
| // `streaming_loader` is still alive and working. |
| EXPECT_FALSE(prefetch_container->GetStreamingURLLoader()); |
| EXPECT_TRUE(streaming_loader); |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| |
| task_environment()->RunUntilIdle(); |
| |
| // `streaming_loader` is deleted asynchronously and its prefetching URL loader |
| // is canceled. This itself doesn't make PrefetchContainer non-servable. |
| EXPECT_FALSE(streaming_loader); |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* no prefix */, |
| PrefetchContainerTest, |
| testing::ValuesIn(PrefetchReusableValuesForTests())); |
| |
| // To test lifetime and ownership issues, all possible event orderings for |
| // successful prefetching and serving are tested. |
| enum class Event { |
| // Call OnComplete(). |
| kPrefetchOnComplete, |
| |
| // Call CreateRequestHandler(). |
| kCreateRequestHandler, |
| |
| // Call the PrefetchRequestHandler returned by CreateRequestHandler(). |
| kRequestHandler, |
| |
| // Disconnect `serving_url_loader_client`. |
| kDisconnectServingClient, |
| |
| // Completely read the body mojo pipe. |
| kCompleteBody, |
| |
| // Destruct PrefetchContainer. |
| kDestructPrefetchContainer, |
| |
| // Serve for the second serving client, when |
| // `features::kPrefetchReusable` is enabled. |
| // All steps (corresponding to `kCreateRequestHandler`, `kRequestHandler`, |
| // `kDisconnectServingClient` and `kCompleteBody`) are merged in order to |
| // reduce the number of tests. |
| kSecondClient, |
| }; |
| |
| std::ostream& operator<<(std::ostream& ostream, Event event) { |
| switch (event) { |
| case Event::kPrefetchOnComplete: |
| return ostream << "kPrefetchOnComplete"; |
| case Event::kCreateRequestHandler: |
| return ostream << "kCreateRequestHandler"; |
| case Event::kRequestHandler: |
| return ostream << "kRequestHandler"; |
| case Event::kDisconnectServingClient: |
| return ostream << "kDisconnectServingClient"; |
| case Event::kCompleteBody: |
| return ostream << "kCompleteBody"; |
| case Event::kDestructPrefetchContainer: |
| return ostream << "kDestructPrefetchContainer"; |
| case Event::kSecondClient: |
| return ostream << "kSecondClient"; |
| } |
| } |
| |
| enum class BodySize { kSmall, kLarge }; |
| std::ostream& operator<<(std::ostream& ostream, BodySize body_size) { |
| switch (body_size) { |
| case BodySize::kSmall: |
| return ostream << "Small"; |
| case BodySize::kLarge: |
| return ostream << "Large"; |
| } |
| } |
| |
| // To detect corner cases around lifetime and ownership, test all possible |
| // permutations of the order of events. |
| class PrefetchContainerLifetimeTest |
| : public PrefetchContainerTestBase, |
| public ::testing::WithParamInterface< |
| std::tuple<std::vector<Event>, BodySize, PrefetchReusableForTests>> { |
| private: |
| void SetUp() override { |
| switch (std::get<2>(GetParam())) { |
| case PrefetchReusableForTests::kDisabled: |
| scoped_feature_list_.InitAndDisableFeature(features::kPrefetchReusable); |
| break; |
| case PrefetchReusableForTests::kEnabled: |
| scoped_feature_list_.InitAndEnableFeature(features::kPrefetchReusable); |
| break; |
| } |
| PrefetchContainerTestBase::SetUp(); |
| } |
| }; |
| |
| TEST_P(PrefetchContainerLifetimeTest, Lifetime) { |
| auto prefetch_container = |
| CreateSpeculationRulesPrefetchContainer(GURL("https://test.com")); |
| |
| prefetch_container->SimulatePrefetchEligibleForTest(); |
| |
| auto pending_request = |
| MakeManuallyServableStreamingURLLoaderForTest(prefetch_container.get()); |
| |
| const auto producer_pipe_capacity = network::GetDataPipeDefaultAllocationSize( |
| network::DataPipeAllocationSize::kLargerSizeIfPossible); |
| |
| const BodySize body_size = std::get<1>(GetParam()); |
| std::string content; |
| switch (body_size) { |
| case BodySize::kSmall: |
| content = "Body"; |
| break; |
| case BodySize::kLarge: |
| // Set the minimum data size that makes write to data pipe fail |
| // incomplete. |
| // |
| // - `features::kPrefetchReusableBodySizeLimit` (multiple of |
| // `producer_pipe_capacity`) is used to fill buffer of |
| // `PrefetchDataPipeTee`. `PrefetchDataPipeTee` doesn't close the |
| // producer pipe at this stage and tries to read one more time (but it |
| // will discard the next one as the limit exceeded). |
| // - `producer_pipe_capacity` is used to fill data pipe after the above |
| // buffer becomes full. The data is sent to `PrefetchDataPipeTee` (*) |
| // and `PrefetchDataPipeTee` tries to read it, but discards it. At this |
| // timing, `PrefetchDataPipeTee` closes the producer pipe (**). |
| // - 1 is used to make the write incomplete as the producer tries to send |
| // one more byte after (**). |
| size_t limit = |
| static_cast<size_t>(features::kPrefetchReusableBodySizeLimit.Get()) + |
| producer_pipe_capacity; |
| content = std::string(limit + 1, '-'); |
| break; |
| } |
| |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| bool producer_completed = false; |
| |
| { |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(producer_pipe_capacity, producer_handle, |
| consumer_handle), |
| MOJO_RESULT_OK); |
| auto producer = |
| std::make_unique<mojo::DataPipeProducer>(std::move(producer_handle)); |
| mojo::DataPipeProducer* raw_producer = producer.get(); |
| raw_producer->Write(std::make_unique<mojo::StringDataSource>( |
| content, mojo::StringDataSource::AsyncWritingMode:: |
| STRING_STAYS_VALID_UNTIL_COMPLETION), |
| base::BindOnce( |
| [](std::unique_ptr<mojo::DataPipeProducer> producer, |
| bool* producer_completed, MojoResult result) { |
| *producer_completed = true; |
| CHECK_EQ(result, MOJO_RESULT_OK); |
| // `producer` is deleted here. |
| }, |
| std::move(producer), &producer_completed)); |
| } |
| |
| EXPECT_NE(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_FALSE(prefetch_container->GetNonRedirectHead()); |
| |
| pending_request.client->OnReceiveResponse( |
| network::mojom::URLResponseHead::New(), std::move(consumer_handle), |
| std::nullopt); |
| task_environment()->RunUntilIdle(); |
| |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| EXPECT_TRUE(prefetch_container->GetNonRedirectHead()); |
| |
| PrefetchContainer::Reader reader = prefetch_container->CreateReader(); |
| |
| base::WeakPtr<PrefetchResponseReader> weak_response_reader = |
| reader.GetCurrentResponseReaderToServeForTesting(); |
| ASSERT_TRUE(prefetch_container->GetStreamingURLLoader()); |
| base::WeakPtr<PrefetchStreamingURLLoader> weak_streaming_loader = |
| prefetch_container->GetStreamingURLLoader(); |
| |
| PrefetchRequestHandler request_handler; |
| std::unique_ptr<PrefetchTestURLLoaderClient> serving_url_loader_client; |
| |
| PrefetchContainer::Reader reader2 = prefetch_container->CreateReader(); |
| ASSERT_EQ(weak_response_reader.get(), |
| reader2.GetCurrentResponseReaderToServeForTesting().get()); |
| |
| network::ResourceRequest serving_request; |
| serving_request.url = GURL("https://test.com"); |
| serving_request.method = "GET"; |
| |
| // `PrefetchStreamingURLLoader` and `PrefetchResponseReader` are initially |
| // both expected alive, because they are needed for serving `request_handler`. |
| |
| std::set<Event> done; |
| |
| for (const Event event : std::get<0>(GetParam())) { |
| switch (event) { |
| case Event::kPrefetchOnComplete: |
| pending_request.client->OnComplete( |
| network::URLLoaderCompletionStatus(net::OK)); |
| break; |
| |
| case Event::kCreateRequestHandler: |
| ASSERT_FALSE(request_handler); |
| ASSERT_TRUE(prefetch_container); |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| request_handler = reader.CreateRequestHandler(); |
| ASSERT_TRUE(request_handler); |
| break; |
| |
| // Call the PrefetchRequestHandler returned by CreateRequestHandler(). |
| case Event::kRequestHandler: { |
| ASSERT_TRUE(request_handler); // NOLINT(bugprone-use-after-move) |
| ASSERT_FALSE(serving_url_loader_client); |
| |
| serving_url_loader_client = |
| std::make_unique<PrefetchTestURLLoaderClient>(); |
| serving_url_loader_client->SetAutoDraining(false); |
| |
| // NOLINT(bugprone-use-after-move) |
| std::move(request_handler) |
| .Run(serving_request, |
| serving_url_loader_client->BindURLloaderAndGetReceiver(), |
| serving_url_loader_client->BindURLLoaderClientAndGetRemote()); |
| break; |
| } |
| |
| // Disconnect `serving_url_loader_client`. |
| case Event::kDisconnectServingClient: |
| ASSERT_TRUE(serving_url_loader_client); |
| serving_url_loader_client->DisconnectMojoPipes(); |
| break; |
| |
| // Completely read the body mojo pipe. |
| case Event::kCompleteBody: { |
| if (body_size == BodySize::kLarge) { |
| // The body is sufficiently large to fill the data pipes and thus the |
| // producer should still have pending data to write before |
| // `StartDraining()`. |
| EXPECT_FALSE(producer_completed); |
| } |
| // Wait until the URLLoaderClient completion. |
| // `base::RunLoop().RunUntilIdle()` is not sufficient here, because |
| // `mojo::DataPipeProducer` uses thread pool. |
| serving_url_loader_client->StartDraining(); |
| task_environment()->RunUntilIdle(); |
| EXPECT_TRUE(producer_completed); |
| break; |
| } |
| |
| case Event::kSecondClient: |
| ASSERT_TRUE(prefetch_container); |
| EXPECT_EQ(prefetch_container->GetServableState(base::TimeDelta::Max()), |
| PrefetchContainer::ServableState::kServable); |
| |
| // The second request is servable if the body data pipe is finished and |
| // the whole body fits within the data pipe tee size limit. |
| if (!done.count(Event::kPrefetchOnComplete) || |
| body_size == BodySize::kLarge) { |
| // Not servable. |
| ASSERT_FALSE(reader2.CreateRequestHandler()); |
| } else { |
| // As the first client is already served, the body pipe producer |
| // should be also completed. |
| EXPECT_TRUE(producer_completed); |
| |
| auto request_handler2 = reader2.CreateRequestHandler(); |
| ASSERT_TRUE(request_handler2); |
| |
| auto serving_url_loader_client2 = |
| std::make_unique<PrefetchTestURLLoaderClient>(); |
| |
| std::move(request_handler2) |
| .Run(serving_request, |
| serving_url_loader_client2->BindURLloaderAndGetReceiver(), |
| serving_url_loader_client2 |
| ->BindURLLoaderClientAndGetRemote()); |
| |
| task_environment()->RunUntilIdle(); |
| serving_url_loader_client2->DisconnectMojoPipes(); |
| |
| EXPECT_TRUE( |
| serving_url_loader_client2->completion_status().has_value()); |
| EXPECT_EQ(serving_url_loader_client2->body_content().size(), |
| content.size()); |
| EXPECT_EQ(serving_url_loader_client2->body_content(), content); |
| } |
| break; |
| |
| case Event::kDestructPrefetchContainer: |
| ASSERT_TRUE(prefetch_container); |
| prefetch_container.reset(); |
| break; |
| } |
| done.insert(event); |
| |
| task_environment()->RunUntilIdle(); |
| |
| // `PrefetchResponseReader` should be kept alive as long as |
| // `PrefetchContainer` is alive or serving URLLoaderClients are not |
| // finished. |
| // The second client is not alive here because it is created and finished |
| // within `kSecondClient`. |
| EXPECT_EQ(!!weak_response_reader, |
| !done.count(Event::kDisconnectServingClient) || |
| !done.count(Event::kDestructPrefetchContainer)); |
| |
| // `PrefetchStreamingURLLoader` is kept alive until prefetching is |
| // completed. |
| EXPECT_EQ(!!weak_streaming_loader, !done.count(Event::kPrefetchOnComplete)); |
| |
| if (done.count(Event::kPrefetchOnComplete) && |
| done.count(Event::kCompleteBody)) { |
| EXPECT_TRUE(producer_completed); |
| } |
| if (done.count(Event::kRequestHandler)) { |
| EXPECT_EQ(serving_url_loader_client->completion_status().has_value(), |
| done.count(Event::kPrefetchOnComplete)); |
| } |
| if (done.count(Event::kCompleteBody)) { |
| EXPECT_EQ(serving_url_loader_client->body_content().size(), |
| content.size()); |
| EXPECT_EQ(serving_url_loader_client->body_content(), content); |
| } |
| } |
| } |
| |
| std::vector<std::vector<Event>> ValidEventPermutations(bool has_second_client) { |
| std::vector<Event> events({ |
| Event::kPrefetchOnComplete, |
| Event::kCreateRequestHandler, |
| Event::kRequestHandler, |
| Event::kDisconnectServingClient, |
| Event::kCompleteBody, |
| Event::kDestructPrefetchContainer, |
| }); |
| if (has_second_client) { |
| events.push_back(Event::kSecondClient); |
| } |
| |
| std::vector<std::vector<Event>> params; |
| do { |
| const auto it_prefetch_on_complete = |
| std::find(events.begin(), events.end(), Event::kPrefetchOnComplete); |
| const auto it_create_request_handler = |
| std::find(events.begin(), events.end(), Event::kCreateRequestHandler); |
| const auto it_request_handler = |
| std::find(events.begin(), events.end(), Event::kRequestHandler); |
| const auto it_disconnect_serving_client = std::find( |
| events.begin(), events.end(), Event::kDisconnectServingClient); |
| const auto it_complete_body = |
| std::find(events.begin(), events.end(), Event::kCompleteBody); |
| const auto it_destruct_prefetch_container = std::find( |
| events.begin(), events.end(), Event::kDestructPrefetchContainer); |
| |
| // Ordering requirements due to direct data dependencies: |
| |
| // `kCreateRequestHandler` -> `kRequestHandler` (`request_handler`) |
| if (it_create_request_handler > it_request_handler) { |
| continue; |
| } |
| // `kRequestHandler` -> `kDisconnectServingClient` |
| // (`serving_url_loader_client`) |
| if (it_request_handler > it_disconnect_serving_client) { |
| continue; |
| } |
| // `kCreateRequestHandler` -> `kDestructPrefetchContainer` |
| // (`prefetch_container`) |
| if (it_create_request_handler > it_destruct_prefetch_container) { |
| continue; |
| } |
| // `kRequestHandler` -> `kCompleteBody` (body data pipe) |
| if (it_request_handler > it_complete_body) { |
| continue; |
| } |
| |
| // `kPrefetchOnComplete` -> `kCompleteBody` and successful |
| // `kDisconnectServingClient` (prefetch completion) |
| if (it_prefetch_on_complete > it_complete_body) { |
| continue; |
| } |
| if (it_prefetch_on_complete > it_disconnect_serving_client) { |
| continue; |
| } |
| |
| if (has_second_client) { |
| const auto it_second_client = |
| std::find(events.begin(), events.end(), Event::kSecondClient); |
| |
| // `kPrefetchOnComplete` -> `kSecondClient` -> |
| // `kDestructPrefetchContainer` |
| if (it_prefetch_on_complete > it_second_client || |
| it_second_client > it_destruct_prefetch_container) { |
| continue; |
| } |
| |
| // `kCreateRequestHandler` -> `kSecondClient` (the second request |
| // starts after the first request, but doesn't necessarily complete |
| // subsequent steps after those of the first request). |
| if (it_create_request_handler > it_second_client) { |
| continue; |
| } |
| } |
| |
| params.push_back(events); |
| } while (std::next_permutation(events.begin(), events.end())); |
| |
| // Make sure some particular sequences are tested, where: |
| |
| if (!has_second_client) { |
| // - `PrefetchContainer` is destructed before prefetch is completed: |
| CHECK(base::Contains( |
| params, |
| std::vector<Event>{Event::kCreateRequestHandler, Event::kRequestHandler, |
| Event::kDestructPrefetchContainer, |
| Event::kPrefetchOnComplete, Event::kCompleteBody, |
| Event::kDisconnectServingClient})); |
| |
| // - `PrefetchContainer` is destructed before PrefetchRequestHandler is |
| // invoked and prefetch is completed: |
| CHECK(base::Contains( |
| params, |
| std::vector<Event>{ |
| Event::kCreateRequestHandler, Event::kDestructPrefetchContainer, |
| Event::kRequestHandler, Event::kPrefetchOnComplete, |
| Event::kCompleteBody, Event::kDisconnectServingClient})); |
| |
| // - `PrefetchContainer` is destructed before PrefetchRequestHandler is |
| // invoked but after prefetch is completed: |
| CHECK(base::Contains( |
| params, std::vector<Event>{ |
| Event::kPrefetchOnComplete, Event::kCreateRequestHandler, |
| Event::kDestructPrefetchContainer, Event::kRequestHandler, |
| Event::kCompleteBody, Event::kDisconnectServingClient})); |
| } |
| |
| return params; |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| SingleClient, |
| PrefetchContainerLifetimeTest, |
| testing::Combine(testing::ValuesIn(ValidEventPermutations(false)), |
| testing::Values(BodySize::kSmall, BodySize::kLarge), |
| testing::Values(PrefetchReusableForTests::kDisabled, |
| PrefetchReusableForTests::kEnabled))); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| TwoClients, |
| PrefetchContainerLifetimeTest, |
| testing::Combine(testing::ValuesIn(ValidEventPermutations(true)), |
| testing::Values(BodySize::kSmall), |
| testing::Values(PrefetchReusableForTests::kEnabled))); |
| |
| } // namespace content |