// 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/prefetcher.h" #include #include "base/test/scoped_feature_list.h" #include "content/browser/preloading/prefetch/prefetch_document_manager.h" #include "content/browser/preloading/prefetch/prefetch_features.h" #include "content/browser/preloading/prefetch/prefetch_service.h" #include "content/public/browser/speculation_host_delegate.h" #include "content/public/common/content_client.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_renderer_host.h" #include "content/test/test_content_browser_client.h" #include "content/test/test_web_contents.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { class MockSpeculationHostDelegate : public SpeculationHostDelegate { public: explicit MockSpeculationHostDelegate(RenderFrameHost& render_frame_host) {} ~MockSpeculationHostDelegate() override = default; void ProcessCandidates( std::vector& candidates) override { for (auto&& candidate : candidates) { candidates_.push_back(std::move(candidate)); } } std::vector& Candidates() { return candidates_; } private: std::vector candidates_; }; class TestPrefetchService : public PrefetchService { public: explicit TestPrefetchService(BrowserContext* browser_context) : PrefetchService(browser_context) {} void PrefetchUrl( base::WeakPtr prefetch_container) override { prefetch_container->DisablePrecogLoggingForTest(); prefetches_.push_back(prefetch_container); const auto& devtools_observer = prefetch_container->GetDevToolsObserver(); std::unique_ptr request = std::make_unique(); network::ResourceRequest::TrustedParams trusted_params; request->trusted_params = trusted_params; request->trusted_params->devtools_observer = devtools_observer->MakeSelfOwnedNetworkServiceDevToolsObserver(); devtools_observer->OnStartSinglePrefetch(prefetch_container->RequestId(), *request, absl::nullopt); network::mojom::URLResponseHead response_head; devtools_observer->OnPrefetchResponseReceived( prefetch_container->GetURL(), prefetch_container->RequestId(), response_head); devtools_observer->OnPrefetchRequestComplete( prefetch_container->RequestId(), network::URLLoaderCompletionStatus{net::ERR_NOT_IMPLEMENTED}); devtools_observer->OnPrefetchBodyDataReceived( prefetch_container->RequestId(), /*body*/ std::string{}, /*is_base64_encoded=*/false); } std::vector> prefetches_; }; class MockContentBrowserClient : public TestContentBrowserClient { public: MockContentBrowserClient() { old_browser_client_ = SetBrowserClientForTesting(this); } ~MockContentBrowserClient() override { EXPECT_EQ(this, SetBrowserClientForTesting(old_browser_client_)); } std::unique_ptr CreateSpeculationHostDelegate( RenderFrameHost& render_frame_host) override { auto delegate = std::make_unique(render_frame_host); delegate_ = delegate.get(); return delegate; } MockSpeculationHostDelegate* GetDelegate() { return delegate_; } private: raw_ptr old_browser_client_; raw_ptr delegate_; }; class PrefetcherTest : public RenderViewHostTestHarness { public: PrefetcherTest() = default; void SetUp() override { RenderViewHostTestHarness::SetUp(); browser_context_ = std::make_unique(); web_contents_ = TestWebContents::Create( browser_context_.get(), SiteInstanceImpl::Create(browser_context_.get())); web_contents_->NavigateAndCommit(GetSameOriginUrl("/")); prefetch_service_ = std::make_unique(GetBrowserContext()); PrefetchDocumentManager::SetPrefetchServiceForTesting( prefetch_service_.get()); } void TearDown() override { web_contents_.reset(); browser_context_.reset(); PrefetchDocumentManager::SetPrefetchServiceForTesting(nullptr); RenderViewHostTestHarness::TearDown(); } RenderFrameHostImpl& GetPrimaryMainFrame() { return web_contents_->GetPrimaryPage().GetMainDocument(); } GURL GetSameOriginUrl(const std::string& path) { return GURL("https://example.com" + path); } GURL GetCrossOriginUrl(const std::string& path) { return GURL("https://other.example.com" + path); } TestPrefetchService* GetPrefetchService() { return prefetch_service_.get(); } private: std::unique_ptr browser_context_; std::unique_ptr web_contents_; std::unique_ptr prefetch_service_; }; TEST_F(PrefetcherTest, ProcessCandidatesForPrefetch) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeatureWithParameters( features::kPrefetchUseContentRefactor, {{"proxy_host", "https://testproxyhost.com"}}); MockContentBrowserClient browser_client; auto prefetcher = Prefetcher(GetPrimaryMainFrame()); auto* delegate = browser_client.GetDelegate(); EXPECT_TRUE(delegate != nullptr); // Create list of SpeculationCandidatePtrs. std::vector candidates; auto candidate1 = blink::mojom::SpeculationCandidate::New(); candidate1->action = blink::mojom::SpeculationAction::kPrefetch; candidate1->requires_anonymous_client_ip_when_cross_origin = true; candidate1->url = GetCrossOriginUrl("/candidate1.html"); candidate1->referrer = blink::mojom::Referrer::New(); candidates.push_back(std::move(candidate1)); prefetcher.ProcessCandidatesForPrefetch(candidates); EXPECT_TRUE(delegate->Candidates().empty()); EXPECT_EQ(1u, GetPrefetchService()->prefetches_.size()); EXPECT_FALSE(prefetcher.IsPrefetchAttemptFailedOrDiscarded( GetCrossOriginUrl("/candidate1.html"))); GetPrefetchService()->prefetches_[0]->SetPrefetchStatus( PrefetchStatus::kPrefetchFailedNetError); EXPECT_TRUE(prefetcher.IsPrefetchAttemptFailedOrDiscarded( GetCrossOriginUrl("/candidate1.html"))); } class MockPrefetcher : public Prefetcher { public: explicit MockPrefetcher(RenderFrameHost& render_frame_host) : Prefetcher(render_frame_host) {} void OnStartSinglePrefetch( const std::string& request_id, const network::ResourceRequest& request, absl::optional< std::pair> redirect_info) override { on_start_signle_prefetch_was_called_ = true; dev_tools_observer_is_valid_ = request.trusted_params->devtools_observer.is_valid(); } void OnPrefetchResponseReceived( const GURL& url, const std::string& request_id, const network::mojom::URLResponseHead& response) override { on_prefetch_response_received_was_called_ = true; } void OnPrefetchRequestComplete( const std::string& request_id, const network::URLLoaderCompletionStatus& status) override { on_prefetch_request_complete_was_called_ = true; } void OnPrefetchBodyDataReceived(const std::string& request_id, const std::string& body, bool is_base64_encoded) override { on_prefetch_body_data_received_was_called_ = true; } bool on_start_signle_prefetch_was_called_ = false; bool on_prefetch_response_received_was_called_ = false; bool on_prefetch_request_complete_was_called_ = false; bool on_prefetch_body_data_received_was_called_ = false; bool dev_tools_observer_is_valid_ = false; }; TEST_F(PrefetcherTest, MockPrefetcher) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitAndEnableFeatureWithParameters( features::kPrefetchUseContentRefactor, {{"proxy_host", "https://testproxyhost.com"}}); MockContentBrowserClient browser_client; auto prefetcher = MockPrefetcher(GetPrimaryMainFrame()); auto* delegate = browser_client.GetDelegate(); EXPECT_TRUE(delegate != nullptr); // Create list of SpeculationCandidatePtrs. std::vector candidates; auto candidate1 = blink::mojom::SpeculationCandidate::New(); candidate1->action = blink::mojom::SpeculationAction::kPrefetch; candidate1->requires_anonymous_client_ip_when_cross_origin = true; candidate1->url = GetCrossOriginUrl("/candidate1.html"); candidate1->referrer = blink::mojom::Referrer::New(); candidates.push_back(std::move(candidate1)); prefetcher.ProcessCandidatesForPrefetch(candidates); EXPECT_TRUE(delegate->Candidates().empty()); EXPECT_EQ(1u, GetPrefetchService()->prefetches_.size()); EXPECT_TRUE(prefetcher.on_start_signle_prefetch_was_called_); EXPECT_TRUE(prefetcher.on_prefetch_response_received_was_called_); EXPECT_TRUE(prefetcher.on_prefetch_request_complete_was_called_); EXPECT_TRUE(prefetcher.on_prefetch_body_data_received_was_called_); EXPECT_TRUE(prefetcher.dev_tools_observer_is_valid_); } } // namespace } // namespace content