| // 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/preloading/prefetch/prefetch_test_util_internal.h" |
| |
| #include "base/containers/span.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_view_util.h" |
| #include "base/time/time.h" |
| #include "base/timer/elapsed_timer.h" |
| #include "content/browser/preloading/prefetch/prefetch_container.h" |
| #include "content/browser/preloading/prefetch/prefetch_features.h" |
| #include "content/browser/preloading/prefetch/prefetch_key.h" |
| #include "content/browser/preloading/prefetch/prefetch_response_reader.h" |
| #include "content/browser/preloading/prefetch/prefetch_streaming_url_loader.h" |
| #include "content/browser/preloading/preloading.h" |
| #include "content/browser/preloading/preloading_data_impl.h" |
| #include "content/public/browser/prefetch_metrics.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/test/mock_navigation_handle.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| using ::testing::_; |
| |
| namespace { |
| |
| // Make some broadly reasonable redirect info. |
| net::RedirectInfo SyntheticRedirect(const GURL& new_url) { |
| net::RedirectInfo redirect_info; |
| redirect_info.status_code = 302; |
| redirect_info.new_method = "GET"; |
| redirect_info.new_url = new_url; |
| redirect_info.new_site_for_cookies = net::SiteForCookies::FromUrl(new_url); |
| redirect_info.new_referrer = std::string(); |
| return redirect_info; |
| } |
| |
| class TestPrefetchContainerObserver final : public PrefetchContainer::Observer { |
| public: |
| explicit TestPrefetchContainerObserver(PrefetchContainer& prefetch_container) |
| : prefetch_container_(prefetch_container.GetWeakPtr()) { |
| prefetch_container_->AddObserver(this); |
| } |
| ~TestPrefetchContainerObserver() override { |
| if (prefetch_container_) { |
| prefetch_container_->RemoveObserver(this); |
| } |
| } |
| |
| void WaitForComplete() { on_complete_loop_.Run(); } |
| |
| private: |
| void OnWillBeDestroyed(PrefetchContainer& prefetch_container) override {} |
| void OnGotInitialEligibility(PrefetchContainer& prefetch_container, |
| PreloadingEligibility eligibility) override {} |
| void OnDeterminedHead(PrefetchContainer& prefetch_container) override {} |
| void OnPrefetchCompletedOrFailed( |
| PrefetchContainer& prefetch_container, |
| const network::URLLoaderCompletionStatus& completion_status, |
| const std::optional<int>& response_code) override { |
| on_complete_loop_.Quit(); |
| } |
| |
| base::WeakPtr<PrefetchContainer> prefetch_container_; |
| base::RunLoop on_complete_loop_; |
| }; |
| |
| } // namespace |
| |
| std::tuple<scoped_refptr<PrefetchResponseReader>, |
| base::WeakPtr<PrefetchStreamingURLLoader>> |
| CreateStreamingURLLoaderWithoutPrefetchContainerForTests( |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| const network::ResourceRequest& prefetch_request, |
| NotReachedTagForTestsOr<base::RunLoop*> on_response_received, |
| NotReachedTagForTestsOr<OnPrefetchCompleteTestFuture*> on_complete, |
| NotReachedTagForTestsOr<OnPrefetchReceiveRedirectTestFuture*> |
| on_receive_redirect, |
| NotReachedTagForTestsOr<base::RunLoop*> on_head_received, |
| std::optional<PrefetchErrorOnResponseReceived> error_on_response_received, |
| base::TimeDelta timeout_duration) { |
| auto on_complete_callback = base::BindOnce( |
| [](NotReachedTagForTestsOr<OnPrefetchCompleteTestFuture*> on_complete, |
| const network::URLLoaderCompletionStatus& completion_status) { |
| if (std::holds_alternative<NotReachedTagForTests>(on_complete)) { |
| NOTREACHED(); |
| } |
| if (auto future = std::get<0>(on_complete)) { |
| future->SetValue(completion_status); |
| } |
| }, |
| on_complete); |
| |
| auto on_head_received_callback = base::BindOnce( |
| [](NotReachedTagForTestsOr<base::RunLoop*> on_head_received) { |
| if (std::holds_alternative<NotReachedTagForTests>(on_head_received)) { |
| NOTREACHED(); |
| } |
| if (auto run_loop = std::get<0>(on_head_received)) { |
| run_loop->Quit(); |
| } |
| }, |
| on_head_received); |
| |
| auto response_reader = base::MakeRefCounted<PrefetchResponseReader>( |
| std::move(on_head_received_callback), std::move(on_complete_callback)); |
| return std::make_tuple( |
| response_reader, |
| CreateStreamingURLLoaderForTests( |
| /*prefetch_container=*/nullptr, response_reader->GetWeakPtr(), |
| std::move(url_loader_factory), prefetch_request, on_response_received, |
| on_receive_redirect, error_on_response_received, timeout_duration)); |
| } |
| |
| base::WeakPtr<PrefetchStreamingURLLoader> CreateStreamingURLLoaderForTests( |
| base::WeakPtr<PrefetchContainer> prefetch_container, |
| base::WeakPtr<PrefetchResponseReader> response_reader, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| const network::ResourceRequest& prefetch_request, |
| NotReachedTagForTestsOr<base::RunLoop*> on_response_received, |
| NotReachedTagForTestsOr<OnPrefetchReceiveRedirectTestFuture*> |
| on_receive_redirect, |
| std::optional<PrefetchErrorOnResponseReceived> error_on_response_received, |
| base::TimeDelta timeout_duration) { |
| CHECK(response_reader); |
| auto on_receive_response_callback = base::BindOnce( |
| [](NotReachedTagForTestsOr<base::RunLoop*> on_response_received, |
| std::optional<PrefetchErrorOnResponseReceived> |
| error_on_response_received, |
| network::mojom::URLResponseHead* head) { |
| if (std::holds_alternative<NotReachedTagForTests>( |
| on_response_received)) { |
| NOTREACHED(); |
| } |
| if (auto run_loop = std::get<0>(on_response_received)) { |
| run_loop->Quit(); |
| } |
| return error_on_response_received; |
| }, |
| on_response_received, error_on_response_received); |
| |
| auto on_receive_redirect_callback = base::BindRepeating( |
| [](NotReachedTagForTestsOr<OnPrefetchReceiveRedirectTestFuture*> |
| on_receive_redirect, |
| const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr redirect_head) { |
| if (std::holds_alternative<NotReachedTagForTests>( |
| on_receive_redirect)) { |
| NOTREACHED(); |
| } |
| if (auto future = std::get<0>(on_receive_redirect)) { |
| future->SetValue(redirect_info, std::move(redirect_head)); |
| } |
| }, |
| on_receive_redirect); |
| |
| auto streaming_loader = PrefetchStreamingURLLoader::CreateAndStart( |
| std::move(url_loader_factory), prefetch_request, |
| TRAFFIC_ANNOTATION_FOR_TESTS, timeout_duration, |
| std::move(on_receive_response_callback), |
| std::move(on_receive_redirect_callback), std::move(response_reader), |
| // Because `browser_context_for_service_worker` is null, we don't test |
| // ServiceWorker-controlled prefetches (covered by WPTs instead). Still |
| // `OnServiceWorkerStateDetermined()` callback should be passed, to go |
| // through `PrefetchServiceWorkerState` transitions for |
| // `prefetch_container` if non-null. |
| prefetch_container ? prefetch_container->service_worker_state() |
| : PrefetchServiceWorkerState::kDisallowed, |
| /*browser_context_for_service_worker=*/nullptr, |
| base::BindOnce(&PrefetchContainer::OnServiceWorkerStateDetermined, |
| prefetch_container)); |
| |
| if (prefetch_container) { |
| prefetch_container->SetStreamingURLLoader(streaming_loader); |
| } |
| |
| return streaming_loader; |
| } |
| |
| void MakeServableStreamingURLLoaderForTest( |
| PrefetchContainer* prefetch_container, |
| network::mojom::URLResponseHeadPtr head, |
| const std::string body, |
| network::URLLoaderCompletionStatus status) { |
| prefetch_container->SimulatePrefetchStartedForTest(); |
| |
| const GURL kTestUrl = GURL("https://test.com"); |
| |
| network::TestURLLoaderFactory test_url_loader_factory; |
| std::unique_ptr<network::ResourceRequest> request = |
| std::make_unique<network::ResourceRequest>(); |
| request->url = kTestUrl; |
| request->method = "GET"; |
| |
| base::RunLoop on_response_received_loop; |
| TestPrefetchContainerObserver observer(*prefetch_container); |
| |
| base::WeakPtr<PrefetchResponseReader> weak_response_reader = |
| prefetch_container->GetResponseReaderForCurrentPrefetch(); |
| auto weak_streaming_loader = CreateStreamingURLLoaderForTests( |
| prefetch_container->GetWeakPtr(), weak_response_reader, |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory), |
| *request, &on_response_received_loop, |
| /*on_receive_redirect=*/NotReachedTagForTests()); |
| |
| test_url_loader_factory.AddResponse( |
| kTestUrl, std::move(head), body, status, |
| network::TestURLLoaderFactory::Redirects(), |
| network::TestURLLoaderFactory::kResponseDefault); |
| on_response_received_loop.Run(); |
| observer.WaitForComplete(); |
| |
| CHECK(weak_streaming_loader); |
| CHECK(weak_response_reader); |
| CHECK(weak_response_reader->Servable(base::TimeDelta::Max())); |
| } |
| |
| network::TestURLLoaderFactory::PendingRequest |
| MakeManuallyServableStreamingURLLoaderForTest( |
| PrefetchContainer* prefetch_container) { |
| prefetch_container->SimulatePrefetchStartedForTest(); |
| |
| const GURL kTestUrl = GURL("https://test.com"); |
| |
| network::TestURLLoaderFactory test_url_loader_factory; |
| std::unique_ptr<network::ResourceRequest> request = |
| std::make_unique<network::ResourceRequest>(); |
| request->url = kTestUrl; |
| request->method = "GET"; |
| |
| auto weak_streaming_loader = CreateStreamingURLLoaderForTests( |
| prefetch_container->GetWeakPtr(), |
| prefetch_container->GetResponseReaderForCurrentPrefetch(), |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory), |
| *request, /*on_response_received=*/nullptr, |
| /*on_receive_redirect=*/NotReachedTagForTests()); |
| |
| CHECK_EQ(test_url_loader_factory.pending_requests()->size(), 1u); |
| return std::move(test_url_loader_factory.pending_requests()->at(0)); |
| } |
| |
| void MakeServableStreamingURLLoaderWithRedirectForTest( |
| PrefetchContainer* prefetch_container, |
| const GURL& original_url, |
| const GURL& redirect_url) { |
| prefetch_container->SimulatePrefetchStartedForTest(); |
| |
| network::TestURLLoaderFactory test_url_loader_factory; |
| std::unique_ptr<network::ResourceRequest> request = |
| std::make_unique<network::ResourceRequest>(); |
| request->url = original_url; |
| request->method = "GET"; |
| |
| OnPrefetchReceiveRedirectTestFuture on_receive_redirect; |
| base::RunLoop on_response_received_loop; |
| TestPrefetchContainerObserver observer(*prefetch_container); |
| |
| auto weak_first_response_reader = |
| prefetch_container->GetResponseReaderForCurrentPrefetch(); |
| |
| auto weak_streaming_loader = CreateStreamingURLLoaderForTests( |
| prefetch_container->GetWeakPtr(), weak_first_response_reader, |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory), |
| *request, &on_response_received_loop, &on_receive_redirect); |
| |
| network::URLLoaderCompletionStatus status(net::OK); |
| |
| net::RedirectInfo original_redirect_info = SyntheticRedirect(redirect_url); |
| |
| network::TestURLLoaderFactory::Redirects redirects; |
| redirects.emplace_back(original_redirect_info, |
| network::mojom::URLResponseHead::New()); |
| |
| test_url_loader_factory.AddResponse( |
| original_url, network::mojom::URLResponseHead::New(), "test body", status, |
| std::move(redirects), network::TestURLLoaderFactory::kResponseDefault); |
| auto [redirect_info, redirect_head] = on_receive_redirect.Take(); |
| |
| prefetch_container->AddRedirectHop(redirect_info); |
| |
| CHECK(weak_streaming_loader); |
| weak_streaming_loader->HandleRedirect( |
| PrefetchRedirectStatus::kFollow, redirect_info, std::move(redirect_head)); |
| |
| // GetResponseReaderForCurrentPrefetch() now points to a new ResponseReader |
| // after `AddRedirectHop()` above. |
| CHECK(weak_streaming_loader); |
| auto weak_second_response_reader = |
| prefetch_container->GetResponseReaderForCurrentPrefetch(); |
| weak_streaming_loader->SetResponseReader(weak_second_response_reader); |
| |
| on_response_received_loop.Run(); |
| observer.WaitForComplete(); |
| |
| CHECK(weak_streaming_loader); |
| CHECK(weak_first_response_reader); |
| CHECK(weak_second_response_reader); |
| CHECK(weak_second_response_reader->Servable(base::TimeDelta::Max())); |
| } |
| |
| void MakeServableStreamingURLLoadersWithNetworkTransitionRedirectForTest( |
| PrefetchContainer* prefetch_container, |
| const GURL& original_url, |
| const GURL& redirect_url) { |
| prefetch_container->SimulatePrefetchStartedForTest(); |
| |
| network::TestURLLoaderFactory test_url_loader_factory; |
| std::unique_ptr<network::ResourceRequest> original_request = |
| std::make_unique<network::ResourceRequest>(); |
| original_request->url = original_url; |
| original_request->method = "GET"; |
| |
| OnPrefetchReceiveRedirectTestFuture on_receive_redirect; |
| |
| // Simulate a PrefetchStreamingURLLoader that receives a redirect that |
| // requires a change in a network context. When this happens, it will stop its |
| // request, but can be used to serve the redirect. A new |
| // PrefetchStreamingURLLoader will be started with a request to the redirect |
| // URL. |
| auto weak_first_streaming_loader = CreateStreamingURLLoaderForTests( |
| prefetch_container->GetWeakPtr(), |
| prefetch_container->GetResponseReaderForCurrentPrefetch(), |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory), |
| *original_request, /*on_response_received=*/NotReachedTagForTests(), |
| &on_receive_redirect); |
| |
| net::RedirectInfo original_redirect_info = SyntheticRedirect(redirect_url); |
| |
| network::TestURLLoaderFactory::Redirects redirects; |
| redirects.emplace_back(original_redirect_info, |
| network::mojom::URLResponseHead::New()); |
| |
| test_url_loader_factory.AddResponse( |
| original_url, nullptr, "", network::URLLoaderCompletionStatus(), |
| std::move(redirects), |
| network::TestURLLoaderFactory::kResponseOnlyRedirectsNoDestination); |
| auto [redirect_info, redirect_head] = on_receive_redirect.Take(); |
| |
| prefetch_container->AddRedirectHop(redirect_info); |
| |
| CHECK(weak_first_streaming_loader); |
| weak_first_streaming_loader->HandleRedirect( |
| PrefetchRedirectStatus::kSwitchNetworkContext, redirect_info, |
| std::move(redirect_head)); |
| |
| std::unique_ptr<network::ResourceRequest> redirect_request = |
| std::make_unique<network::ResourceRequest>(); |
| redirect_request->url = redirect_url; |
| redirect_request->method = "GET"; |
| |
| base::RunLoop on_response_received_loop; |
| TestPrefetchContainerObserver observer(*prefetch_container); |
| |
| // Starts the followup PrefetchStreamingURLLoader. |
| // GetResponseReaderForCurrentPrefetch() now points to a new ResponseReader |
| // after `AddRedirectHop()` above. |
| base::WeakPtr<PrefetchResponseReader> weak_second_response_reader = |
| prefetch_container->GetResponseReaderForCurrentPrefetch(); |
| auto weak_second_streaming_loader = CreateStreamingURLLoaderForTests( |
| prefetch_container->GetWeakPtr(), weak_second_response_reader, |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| &test_url_loader_factory), |
| *redirect_request, &on_response_received_loop, |
| /*on_receive_redirect=*/NotReachedTagForTests()); |
| |
| network::URLLoaderCompletionStatus status(net::OK); |
| test_url_loader_factory.AddResponse( |
| redirect_url, network::mojom::URLResponseHead::New(), "test body", status, |
| network::TestURLLoaderFactory::Redirects(), |
| network::TestURLLoaderFactory::kResponseDefault); |
| |
| on_response_received_loop.Run(); |
| observer.WaitForComplete(); |
| |
| // `weak_first_streaming_loader` should be deleted after |
| // `HandleRedirect(kSwitchNetworkContext)`. |
| CHECK(!weak_first_streaming_loader); |
| |
| CHECK(weak_second_streaming_loader); |
| CHECK(weak_second_response_reader); |
| CHECK(weak_second_response_reader->Servable(base::TimeDelta::Max())); |
| } |
| |
| PrefetchTestURLLoaderClient::PrefetchTestURLLoaderClient() = default; |
| PrefetchTestURLLoaderClient::~PrefetchTestURLLoaderClient() = default; |
| |
| mojo::PendingReceiver<network::mojom::URLLoader> |
| PrefetchTestURLLoaderClient::BindURLloaderAndGetReceiver() { |
| return remote_.BindNewPipeAndPassReceiver(); |
| } |
| |
| mojo::PendingRemote<network::mojom::URLLoaderClient> |
| PrefetchTestURLLoaderClient::BindURLLoaderClientAndGetRemote() { |
| return receiver_.BindNewPipeAndPassRemote(); |
| } |
| |
| void PrefetchTestURLLoaderClient::DisconnectMojoPipes() { |
| remote_.reset(); |
| receiver_.reset(); |
| } |
| |
| void PrefetchTestURLLoaderClient::OnReceiveEarlyHints( |
| network::mojom::EarlyHintsPtr early_hints) { |
| NOTREACHED(); |
| } |
| |
| void PrefetchTestURLLoaderClient::OnReceiveResponse( |
| network::mojom::URLResponseHeadPtr head, |
| mojo::ScopedDataPipeConsumerHandle body, |
| std::optional<mojo_base::BigBuffer> cached_metadata) { |
| CHECK(!cached_metadata); |
| CHECK(!body_); |
| body_ = std::move(body); |
| |
| if (auto_draining_) { |
| StartDraining(); |
| } |
| } |
| |
| void PrefetchTestURLLoaderClient::StartDraining() { |
| // Drains |body_| into |body_content_| |
| CHECK(body_); |
| CHECK(!pipe_drainer_); |
| pipe_drainer_ = |
| std::make_unique<mojo::DataPipeDrainer>(this, std::move(body_)); |
| } |
| |
| void PrefetchTestURLLoaderClient::OnReceiveRedirect( |
| const net::RedirectInfo& redirect_info, |
| network::mojom::URLResponseHeadPtr head) { |
| received_redirects_.emplace_back(redirect_info, std::move(head)); |
| } |
| |
| void PrefetchTestURLLoaderClient::OnUploadProgress( |
| int64_t current_position, |
| int64_t total_size, |
| OnUploadProgressCallback callback) { |
| NOTREACHED(); |
| } |
| |
| void PrefetchTestURLLoaderClient::OnTransferSizeUpdated( |
| int32_t transfer_size_diff) { |
| total_transfer_size_diff_ += transfer_size_diff; |
| } |
| |
| void PrefetchTestURLLoaderClient::OnComplete( |
| const network::URLLoaderCompletionStatus& status) { |
| completion_status_ = status; |
| } |
| |
| void PrefetchTestURLLoaderClient::OnDataAvailable( |
| base::span<const uint8_t> data) { |
| body_content_.append(base::as_string_view(data)); |
| total_bytes_read_ += data.size(); |
| } |
| |
| void PrefetchTestURLLoaderClient::OnDataComplete() { |
| body_finished_ = true; |
| } |
| |
| ScopedMockContentBrowserClient::ScopedMockContentBrowserClient() { |
| old_browser_client_ = SetBrowserClientForTesting(this); |
| |
| // Ignore `WillCreateURLLoaderFactory(kDocumentSubResource)` calls (triggered |
| // by e.g. `RenderFrameHostImpl::CommitNavigation()`) to suppress gmock |
| // warning/failures. This is safe to ignore, because prefetch-related tests |
| // are only for navigational prefetch and don't care about unrelated |
| // subresource URLLoaderFactories. |
| EXPECT_CALL( |
| *this, |
| WillCreateURLLoaderFactory( |
| _, _, _, |
| ContentBrowserClient::URLLoaderFactoryType::kDocumentSubResource, _, |
| _, _, _, _, _, _, _, _, _)) |
| .Times(::testing::AnyNumber()); |
| } |
| |
| ScopedMockContentBrowserClient::~ScopedMockContentBrowserClient() { |
| EXPECT_EQ(this, SetBrowserClientForTesting(old_browser_client_)); |
| } |
| |
| TestPrefetchService::TestPrefetchService(BrowserContext* browser_context) |
| : PrefetchService(browser_context) {} |
| |
| TestPrefetchService::~TestPrefetchService() = default; |
| |
| void TestPrefetchService::PrefetchUrl( |
| base::WeakPtr<PrefetchContainer> prefetch_container) { |
| prefetches_.push_back(prefetch_container); |
| } |
| |
| void TestPrefetchService::OnPrefetchCompletedOrFailed( |
| PrefetchContainer& prefetch_container, |
| const network::URLLoaderCompletionStatus& completion_status, |
| const std::optional<int>& response_code) { |
| // Skip `active_prefetch_` check and related prefetch queue processing in |
| // `PrefetchService`, because it's not set/used in `TestPrefetchService`. |
| } |
| |
| void TestPrefetchService::EvictPrefetch(size_t index) { |
| ASSERT_LT(index, prefetches_.size()); |
| ASSERT_TRUE(prefetches_[index]); |
| base::WeakPtr<PrefetchContainer> prefetch_container = prefetches_[index]; |
| prefetches_.erase(prefetches_.begin() + index); |
| MayReleasePrefetch(prefetch_container); |
| } |
| |
| PrefetchingMetricsTestBase::PrefetchingMetricsTestBase() |
| : RenderViewHostTestHarness( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| |
| PrefetchingMetricsTestBase::~PrefetchingMetricsTestBase() = default; |
| |
| void PrefetchingMetricsTestBase::SetUp() { |
| RenderViewHostTestHarness::SetUp(); |
| test_ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| attempt_entry_builder_ = |
| std::make_unique<test::PreloadingAttemptUkmEntryBuilder>( |
| content_preloading_predictor::kSpeculationRules); |
| } |
| void PrefetchingMetricsTestBase::TearDown() { |
| test_ukm_recorder_.reset(); |
| attempt_entry_builder_.reset(); |
| RenderViewHostTestHarness::TearDown(); |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectPrefetchNoNetErrorOrResponseReceived( |
| const base::HistogramTester& histogram_tester, |
| bool is_eligible, |
| bool browser_initiated_prefetch) { |
| histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode", |
| 0); |
| histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.NetError", |
| 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0); |
| |
| if (!browser_initiated_prefetch) { |
| std::optional<PrefetchReferringPageMetrics> referring_page_metrics = |
| PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh()); |
| EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, |
| is_eligible ? 1 : 0); |
| EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0); |
| } |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectPrefetchNotEligible( |
| const base::HistogramTester& histogram_tester, |
| PreloadingEligibility expected_eligibility, |
| bool is_accurate, |
| bool browser_initiated_prefetch) { |
| ExpectPrefetchNoNetErrorOrResponseReceived(histogram_tester, |
| /*is_eligible=*/false, |
| browser_initiated_prefetch); |
| |
| if (!browser_initiated_prefetch) { |
| ExpectCorrectUkmLogs({.eligibility = expected_eligibility, |
| .holdback = PreloadingHoldbackStatus::kUnspecified, |
| .outcome = PreloadingTriggeringOutcome::kUnspecified, |
| .is_accurate = is_accurate}); |
| } |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectPrefetchFailedBeforeResponseReceived( |
| const base::HistogramTester& histogram_tester, |
| PrefetchStatus expected_prefetch_status, |
| bool is_accurate) { |
| ExpectPrefetchNoNetErrorOrResponseReceived(histogram_tester, |
| /*is_eligible=*/true); |
| histogram_tester.ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus", |
| expected_prefetch_status, 1); |
| ExpectCorrectUkmLogs( |
| {.outcome = PreloadingTriggeringOutcome::kFailure, |
| .failure = ToPreloadingFailureReason(expected_prefetch_status), |
| .is_accurate = is_accurate}); |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectPrefetchFailedNetError( |
| const base::HistogramTester& histogram_tester, |
| int expected_net_error_code, |
| blink::mojom::SpeculationEagerness eagerness, |
| bool is_accurate_triggering, |
| bool browser_initiated_prefetch) { |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 1); |
| |
| histogram_tester.ExpectTotalCount("PrefetchProxy.Prefetch.Mainframe.RespCode", |
| 0); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.NetError", |
| std::abs(expected_net_error_code), 1); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.Prefetch.Mainframe.BodyLength", 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.Prefetch.Mainframe.TotalTime", 0); |
| histogram_tester.ExpectTotalCount( |
| "PrefetchProxy.Prefetch.Mainframe.ConnectTime", 0); |
| |
| histogram_tester.ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus", |
| PrefetchStatus::kPrefetchFailedNetError, |
| 1); |
| |
| if (!browser_initiated_prefetch) { |
| std::optional<PrefetchReferringPageMetrics> referring_page_metrics = |
| PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh()); |
| EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0); |
| |
| ExpectCorrectUkmLogs({.outcome = PreloadingTriggeringOutcome::kFailure, |
| .failure = ToPreloadingFailureReason( |
| PrefetchStatus::kPrefetchFailedNetError), |
| .is_accurate = is_accurate_triggering, |
| .eagerness = eagerness}); |
| } |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectPrefetchFailedAfterResponseReceived( |
| const base::HistogramTester& histogram_tester, |
| net::HttpStatusCode expected_response_code, |
| int expected_body_length, |
| PrefetchStatus expected_prefetch_status) { |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 1); |
| |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.RespCode", expected_response_code, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.BodyLength", expected_body_length, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1); |
| |
| std::optional<PrefetchReferringPageMetrics> referring_page_metrics = |
| PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh()); |
| EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 0); |
| |
| histogram_tester.ExpectUniqueSample("Preloading.Prefetch.PrefetchStatus", |
| expected_prefetch_status, 1); |
| ExpectCorrectUkmLogs( |
| {.outcome = PreloadingTriggeringOutcome::kFailure, |
| .failure = ToPreloadingFailureReason(expected_prefetch_status)}); |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectPrefetchSuccess( |
| const base::HistogramTester& histogram_tester, |
| int expected_body_length, |
| blink::mojom::SpeculationEagerness eagerness, |
| bool is_accurate) { |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.ExistingPrefetchWithMatchingURL", false, 1); |
| |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.RespCode", net::HTTP_OK, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.NetError", net::OK, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.BodyLength", expected_body_length, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.TotalTime", kTotalTimeDuration, 1); |
| histogram_tester.ExpectUniqueSample( |
| "PrefetchProxy.Prefetch.Mainframe.ConnectTime", kConnectTimeDuration, 1); |
| |
| std::optional<PrefetchReferringPageMetrics> referring_page_metrics = |
| PrefetchReferringPageMetrics::GetForCurrentDocument(main_rfh()); |
| EXPECT_EQ(referring_page_metrics->prefetch_attempted_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_eligible_count, 1); |
| EXPECT_EQ(referring_page_metrics->prefetch_successful_count, 1); |
| |
| ExpectCorrectUkmLogs({.is_accurate = is_accurate, .eagerness = eagerness}); |
| } |
| |
| ukm::SourceId PrefetchingMetricsTestBase::ForceLogsUploadAndGetUkmId( |
| GURL navigate_url) { |
| MockNavigationHandle mock_handle; |
| mock_handle.set_is_in_primary_main_frame(true); |
| mock_handle.set_is_same_document(false); |
| mock_handle.set_has_committed(true); |
| mock_handle.set_url(std::move(navigate_url)); |
| auto* preloading_data = |
| PreloadingData::GetOrCreateForWebContents(web_contents()); |
| // Sets the accurate bit, and records `TimeToNextNavigation`. |
| static_cast<PreloadingDataImpl*>(preloading_data) |
| ->DidStartNavigation(&mock_handle); |
| // Records the UKMs. |
| static_cast<PreloadingDataImpl*>(preloading_data) |
| ->DidFinishNavigation(&mock_handle); |
| return mock_handle.GetNextPageUkmSourceId(); |
| } |
| |
| void PrefetchingMetricsTestBase::ExpectCorrectUkmLogs( |
| ExpectCorrectUkmLogsArgs args, |
| GURL navigate_url) { |
| const auto source_id = ForceLogsUploadAndGetUkmId(std::move(navigate_url)); |
| auto actual_attempts = test_ukm_recorder()->GetEntries( |
| ukm::builders::Preloading_Attempt::kEntryName, |
| test::kPreloadingAttemptUkmMetrics); |
| EXPECT_EQ(actual_attempts.size(), 1u); |
| |
| std::optional<base::TimeDelta> ready_time = std::nullopt; |
| if (args.outcome == PreloadingTriggeringOutcome::kReady || |
| args.outcome == PreloadingTriggeringOutcome::kSuccess || |
| args.expect_ready_time) { |
| ready_time = base::ScopedMockElapsedTimersForTest::kMockElapsedTime; |
| } |
| |
| const auto expected_attempts = {attempt_entry_builder()->BuildEntry( |
| source_id, PreloadingType::kPrefetch, args.eligibility, args.holdback, |
| args.outcome, args.failure, args.is_accurate, ready_time, |
| args.eagerness)}; |
| |
| EXPECT_THAT(actual_attempts, |
| testing::UnorderedElementsAreArray(expected_attempts)) |
| << test::ActualVsExpectedUkmEntriesToString(actual_attempts, |
| expected_attempts); |
| // We do not test the `PreloadingPrediction` as it is added in |
| // `PreloadingDecider`. |
| } |
| |
| WithPrefetchRearchParam::WithPrefetchRearchParam(PrefetchRearchParam param) |
| : param_(param) {} |
| WithPrefetchRearchParam::~WithPrefetchRearchParam() = default; |
| |
| // static |
| std::vector<PrefetchRearchParam> PrefetchRearchParam::Params() { |
| return {PrefetchRearchParam{ |
| .prefetch_scheduler = false, |
| .prefetch_scheduler_progress_sync_best_effort = false, |
| }, |
| PrefetchRearchParam{ |
| .prefetch_scheduler = true, |
| .prefetch_scheduler_progress_sync_best_effort = false, |
| }, |
| PrefetchRearchParam{ |
| .prefetch_scheduler = true, |
| .prefetch_scheduler_progress_sync_best_effort = true, |
| }}; |
| } |
| |
| void WithPrefetchRearchParam::InitRearchFeatures() { |
| if (param_.prefetch_scheduler) { |
| feature_list_prefetch_scheduler_.InitWithFeaturesAndParameters( |
| {{ |
| features::kPrefetchScheduler, |
| { |
| {"kPrefetchSchedulerProgressSyncBestEffort", |
| param_.prefetch_scheduler_progress_sync_best_effort ? "true" |
| : "false"}, |
| }, |
| }}, |
| {}); |
| } |
| } |
| |
| PrefetchServiceInjectedEligibilityCheckFuture:: |
| PrefetchServiceInjectedEligibilityCheckFuture( |
| PrefetchService& prefetch_service) |
| : prefetch_service_(prefetch_service) { |
| prefetch_service_->SetInjectedEligibilityCheckForTesting(base::BindRepeating( |
| [](TestFutureType* result_callback_future, |
| PrefetchService::InjectedEligibilityCheckResultCallbackForTesting |
| callback) { |
| result_callback_future->SetValue(std::move(callback)); |
| }, |
| base::Unretained(&result_callback_future_))); |
| } |
| |
| PrefetchServiceInjectedEligibilityCheckFuture:: |
| ~PrefetchServiceInjectedEligibilityCheckFuture() { |
| prefetch_service_->SetInjectedEligibilityCheckForTesting( |
| base::NullCallback()); |
| } |
| |
| } // namespace content |