| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/barrier_closure.h" |
| #include "base/base_switches.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/scoped_observation.h" |
| #include "base/synchronization/lock.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/thread_annotations.h" |
| #include "build/build_config.h" |
| #include "components/services/storage/public/mojom/storage_service.mojom.h" |
| #include "components/services/storage/public/mojom/test_api.test-mojom.h" |
| #include "content/browser/file_system_access/file_system_chooser_test_helpers.h" |
| #include "content/browser/prerender/prerender_host.h" |
| #include "content/browser/prerender/prerender_host_registry.h" |
| #include "content/browser/prerender/prerender_metrics.h" |
| #include "content/browser/renderer_host/back_forward_cache_impl.h" |
| #include "content/browser/renderer_host/frame_tree_node.h" |
| #include "content/browser/renderer_host/input/synthetic_tap_gesture.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_document_host_user_data.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/speculation_host_delegate.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/navigation_handle_observer.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_navigation_throttle.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/shell/browser/shell_content_browser_client.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "content/test/mock_commit_deferring_condition.h" |
| #include "content/test/test_content_browser_client.h" |
| #include "content/test/test_mojo_binder_policy_applier_unittest.mojom.h" |
| #include "mojo/public/cpp/bindings/receiver_set.h" |
| #include "mojo/public/cpp/bindings/remote_set.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/embedded_test_server/request_handler_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/browser_interface_broker.mojom.h" |
| #include "ui/shell_dialogs/select_file_dialog.h" |
| #include "ui/shell_dialogs/select_file_dialog_factory.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| namespace { |
| |
| enum class BackForwardCacheType { |
| kDisabled, |
| kEnabled, |
| kEnabledWithSameSite, |
| }; |
| |
| std::string ToString(const testing::TestParamInfo<BackForwardCacheType>& info) { |
| switch (info.param) { |
| case BackForwardCacheType::kDisabled: |
| return "Disabled"; |
| case BackForwardCacheType::kEnabled: |
| return "Enabled"; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| return "EnabledWithSameSite"; |
| } |
| } |
| |
| RenderFrameHost* FindRenderFrameHost(RenderFrameHost& root, const GURL& url) { |
| std::vector<RenderFrameHost*> rfhs = root.GetFramesInSubtree(); |
| for (auto* rfh : rfhs) { |
| if (rfh->GetLastCommittedURL() == url) |
| return rfh; |
| } |
| return nullptr; |
| } |
| |
| // Example class which inherits the RenderDocumentHostUserData, all the data is |
| // associated to the lifetime of the document. |
| class DocumentData : public RenderDocumentHostUserData<DocumentData> { |
| public: |
| ~DocumentData() override = default; |
| |
| base::WeakPtr<DocumentData> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| explicit DocumentData(RenderFrameHost* render_frame_host) {} |
| |
| friend class content::RenderDocumentHostUserData<DocumentData>; |
| |
| base::WeakPtrFactory<DocumentData> weak_ptr_factory_{this}; |
| |
| RENDER_DOCUMENT_HOST_USER_DATA_KEY_DECL(); |
| }; |
| |
| RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(DocumentData) |
| |
| class PrerenderBrowserTest : public ContentBrowserTest { |
| public: |
| using LifecycleStateImpl = RenderFrameHostImpl::LifecycleStateImpl; |
| |
| PrerenderBrowserTest() { |
| prerender_helper_ = |
| std::make_unique<test::PrerenderTestHelper>(base::BindRepeating( |
| &PrerenderBrowserTest::web_contents, base::Unretained(this))); |
| } |
| ~PrerenderBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ssl_server_.AddDefaultHandlers(GetTestDataFilePath()); |
| ssl_server_.SetSSLConfig( |
| net::test_server::EmbeddedTestServer::CERT_TEST_NAMES); |
| prerender_helper_->SetUpOnMainThread(&ssl_server_); |
| ASSERT_TRUE(ssl_server_.Start()); |
| } |
| |
| void TearDownOnMainThread() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| EXPECT_TRUE(ssl_server_.ShutdownAndWaitUntilComplete()); |
| } |
| |
| // Waits until the request count for `url` reaches `count`. |
| void WaitForRequest(const GURL& url, int count) { |
| prerender_helper_->WaitForRequest(url, count); |
| } |
| |
| int AddPrerender(const GURL& prerendering_url) { |
| return prerender_helper_->AddPrerender(prerendering_url); |
| } |
| |
| void AddPrerenderAsync(const GURL& prerendering_url) { |
| prerender_helper_->AddPrerenderAsync(prerendering_url); |
| } |
| |
| bool AddTestUtilJS(RenderFrameHost* host) { |
| bool success = false; |
| std::string js = R"( |
| const script = document.createElement("script"); |
| script.addEventListener('load', () => { |
| window.domAutomationController.send(true); |
| }); |
| script.addEventListener('error', () => { |
| window.domAutomationController.send(false); |
| }); |
| script.src = "/prerender/test_utils.js"; |
| document.body.appendChild(script); |
| )"; |
| EXPECT_TRUE(ExecuteScriptAndExtractBool(host, js, &success)); |
| return success; |
| } |
| |
| void NavigatePrimaryPage(const GURL& url) { |
| prerender_helper_->NavigatePrimaryPage(url); |
| } |
| |
| int GetHostForUrl(const GURL& url) { |
| return prerender_helper_->GetHostForUrl(url); |
| } |
| |
| RenderFrameHostImpl* GetPrerenderedMainFrameHost(int host_id) { |
| return static_cast<RenderFrameHostImpl*>( |
| prerender_helper_->GetPrerenderedMainFrameHost(host_id)); |
| } |
| |
| void NavigatePrerenderedPage(int host_id, const GURL& url) { |
| return prerender_helper_->NavigatePrerenderedPage(host_id, url); |
| } |
| |
| bool HasHostForUrl(const GURL& url) { |
| int host_id = GetHostForUrl(url); |
| return host_id != RenderFrameHost::kNoFrameTreeNodeId; |
| } |
| |
| void WaitForPrerenderLoadCompleted(int host_id) { |
| prerender_helper_->WaitForPrerenderLoadCompletion(host_id); |
| } |
| |
| void WaitForPrerenderLoadCompletion(const GURL& url) { |
| prerender_helper_->WaitForPrerenderLoadCompletion(url); |
| } |
| |
| GURL GetUrl(const std::string& path) { |
| return ssl_server_.GetURL("a.test", path); |
| } |
| |
| GURL GetCrossOriginUrl(const std::string& path) { |
| return ssl_server_.GetURL("b.test", path); |
| } |
| |
| int GetRequestCount(const GURL& url) { |
| return prerender_helper_->GetRequestCount(url); |
| } |
| |
| WebContents* web_contents() const { return shell()->web_contents(); } |
| |
| WebContentsImpl* web_contents_impl() const { |
| return static_cast<WebContentsImpl*>(web_contents()); |
| } |
| |
| RenderFrameHostImpl* current_frame_host() { |
| return web_contents_impl()->GetMainFrame(); |
| } |
| |
| void TestHostPrerenderingState(const GURL& prerender_url) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // The initial page should not be in prerendered state. |
| RenderFrameHostImpl* initiator_render_frame_host = current_frame_host(); |
| EXPECT_EQ(initiator_render_frame_host->frame_tree()->type(), |
| FrameTree::Type::kPrimary); |
| EXPECT_EQ(initiator_render_frame_host->lifecycle_state(), |
| LifecycleStateImpl::kActive); |
| |
| // Start a prerender. |
| AddPrerender(prerender_url); |
| |
| EXPECT_TRUE(prerender_helper_->VerifyPrerenderingState(prerender_url)); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(prerender_url); |
| EXPECT_EQ(web_contents()->GetURL(), prerender_url); |
| |
| // The activated page should no longer be in the prerendering state. |
| RenderFrameHostImpl* navigated_render_frame_host = current_frame_host(); |
| // The new page shouldn't be in the prerendering state. |
| std::vector<RenderFrameHost*> frames = |
| navigated_render_frame_host->GetFramesInSubtree(); |
| for (auto* frame : frames) { |
| auto* rfhi = static_cast<RenderFrameHostImpl*>(frame); |
| // All the subframes should be transitioned to LifecycleStateImpl::kActive |
| // state after activation. |
| EXPECT_EQ(rfhi->lifecycle_state(), |
| RenderFrameHostImpl::LifecycleStateImpl::kActive); |
| EXPECT_FALSE(rfhi->frame_tree()->is_prerendering()); |
| } |
| } |
| |
| test::PrerenderTestHelper* prerender_helper() { |
| return prerender_helper_.get(); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Useful for testing CSP:prefetch-src |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| } |
| |
| private: |
| net::test_server::EmbeddedTestServer ssl_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| |
| std::unique_ptr<test::PrerenderTestHelper> prerender_helper_; |
| }; |
| |
| // Tests that the speculationrules trigger works. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SpeculationRulesPrerender) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // The prerender host should be consumed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Activating the prerendered page should not issue a request. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| |
| // Tests that the speculationrules-triggered prerender would be destroyed after |
| // its initiator navigates away. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SpeculationInitiatorNavigateAway) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // TODO(https://crbug.com/1186893): PrerenderHost is not deleted when the |
| // page enters BackForwardCache, though it should be. While this functionality |
| // is not implemented, disable BackForwardCache for testing and wait for the |
| // old RenderFrameHost to be deleted after we navigate away from it. |
| DisableBackForwardCacheForTesting( |
| web_contents(), BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Navigate the initiator page to a non-prerendered page. This destroys the |
| // prerendered page. |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| NavigatePrimaryPage(GetUrl("/empty.html?elsewhere")); |
| host_observer.WaitForDestroyed(); |
| |
| // The prerender host should be destroyed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ResponseHeaders) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/set-header?X-Foo: bar"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl` and check if `X-Foo` header is |
| // observed. |
| NavigationHandleObserver observer1(web_contents(), kPrerenderingUrl); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_TRUE(observer1.has_committed()); |
| EXPECT_EQ("bar", observer1.GetNormalizedResponseHeader("x-foo")); |
| |
| // Activate the page and check if `X-Foo` header is observed again. |
| NavigationHandleObserver observer2(web_contents(), kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_TRUE(observer2.has_committed()); |
| EXPECT_EQ("bar", observer2.GetNormalizedResponseHeader("x-foo")); |
| } |
| |
| // Tests that prerendering is cancelled if a network request for the |
| // navigation results in 404. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderNotFoundPage) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| // Specify a URL for which we don't have a corresponding file in the data dir. |
| const GURL kPrerenderingUrl = GetUrl("/404"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| AddPrerenderAsync(kPrerenderingUrl); |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kNavigationRequestFailure, 1); |
| } |
| |
| // Tests that prerendering triggered by prerendered pages is deferred until |
| // activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderChain) { |
| // kInitialUrl prerenders kPrerenderChain1, then kPrerenderChain1 prerenders |
| // kPrerenderChain2. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderChain1 = |
| GetUrl("/prerender/page_with_trigger_function.html?1"); |
| const GURL kPrerenderChain2 = |
| GetUrl("/prerender/page_with_trigger_function.html?2"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| const int host_id = AddPrerender(kPrerenderChain1); |
| |
| EXPECT_EQ(GetRequestCount(kPrerenderChain1), 1); |
| EXPECT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| RenderFrameHost* prerender_host = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(AddTestUtilJS(prerender_host)); |
| |
| // Add a prerender trigger to the prerendering page. |
| EXPECT_TRUE(ExecJs(prerender_host, |
| JsReplace("add_speculation_rules($1)", kPrerenderChain2))); |
| |
| // Speculation rules is processed by the idle task runner in Blink. To ensure |
| // the speculation candidates has been sent by renderer processes, we should |
| // wait until this runner finishes all tasks. |
| EXPECT_TRUE(ExecJs(prerender_host, R"( |
| const idlePromise = new Promise(resolve => requestIdleCallback(resolve)); |
| idlePromise; |
| )")); |
| |
| // Start a navigation request that should not be deferred, and wait it to |
| // reach the server. If the prerender request for kPrerenderChain2 is not |
| // deferred, the navigation request for kPrerenderChain2 will reach the server |
| // earlier than the non-deferred one, so we can wait until the latest request |
| // reaches the sever to prove that the prerender request for kPrerenderChain2 |
| // is deferred. |
| EXPECT_TRUE(ExecJs(prerender_host, "add_iframe_async('/title1.html')", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| WaitForRequest(GetUrl("/title1.html"), 1); |
| |
| // The prerender requests were deferred by Mojo capability control, so |
| // prerendering pages should not trigger prerendering. |
| EXPECT_EQ(GetRequestCount(kPrerenderChain2), 0); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderChain2)); |
| |
| // Activate the prerendering page to grant the deferred prerender requests. |
| NavigatePrimaryPage(kPrerenderChain1); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderChain1); |
| |
| // The prerendered page was activated. The prerender requests should be |
| // processed. |
| WaitForPrerenderLoadCompletion(kPrerenderChain2); |
| EXPECT_EQ(GetRequestCount(kPrerenderChain2), 1); |
| EXPECT_TRUE(HasHostForUrl(kPrerenderChain2)); |
| } |
| |
| // Tests that sub-frames cannot trigger prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, IgnoreSubFrameInitiatedPrerender) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kSubFrameUrl = |
| GetUrl("/prerender/page_with_trigger_function.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| RenderFrameHostImpl* main_frame_host = current_frame_host(); |
| EXPECT_TRUE(AddTestUtilJS(main_frame_host)); |
| EXPECT_EQ("LOADED", |
| EvalJs(web_contents(), JsReplace("add_iframe($1)", kSubFrameUrl))); |
| RenderFrameHost* child_frame_host = ChildFrameAt(main_frame_host, 0); |
| ASSERT_NE(child_frame_host, nullptr); |
| ASSERT_EQ(child_frame_host->GetLastCommittedURL(), kSubFrameUrl); |
| |
| // Add a prerender trigger to the subframe. |
| EXPECT_TRUE(ExecJs(child_frame_host, |
| JsReplace("add_speculation_rules($1)", kPrerenderingUrl))); |
| |
| // Speculation rules is processed by the idle task runner in Blink. To ensure |
| // the speculation candidates has been sent by renderer processes, we should |
| // wait until this runner finishes all tasks. |
| EXPECT_TRUE(ExecJs(child_frame_host, R"( |
| const idlePromise = new Promise(resolve => requestIdleCallback(resolve)); |
| idlePromise; |
| )")); |
| |
| // Start a navigation request that should not be ignored, and wait it to |
| // reach the server. If the prerender request is not ignored, the navigation |
| // request for kPrerenderingUrl will reach the server earlier than the |
| // non-ignored one, so we can wait until the latest request reaches the sever |
| // to prove that the prerender request for kPrerenderingUrl is ignored. |
| EXPECT_TRUE(ExecJs(main_frame_host, "add_iframe_async('/title1.html')", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| WaitForRequest(GetUrl("/title1.html"), 1); |
| |
| // The prerender requests were ignored by SpeculationHostImpl. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| } |
| |
| // Regression test for https://crbug.com/1194865. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CloseOnPrerendering) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A prerender host for the URL should be registered. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Should not crash. |
| shell()->Close(); |
| } |
| |
| namespace { |
| |
| class RedirectChainObserver : public WebContentsObserver { |
| public: |
| RedirectChainObserver(WebContents& web_contents, const GURL& url) |
| : WebContentsObserver(&web_contents), url_(url) {} |
| std::vector<GURL>& redirect_chain() { return redirect_chain_; } |
| |
| private: |
| void DidFinishNavigation(NavigationHandle* handle) override { |
| if (handle->GetURL() != url_) |
| return; |
| redirect_chain_ = handle->GetRedirectChain(); |
| } |
| |
| const GURL url_; |
| std::vector<GURL> redirect_chain_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SameOriginRedirection) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering a URL that causes same-origin redirection. |
| const GURL kRedirectedUrl = GetUrl("/empty.html?prerender"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/server-redirect?" + kRedirectedUrl.spec()); |
| RedirectChainObserver redirect_chain_observer(*shell()->web_contents(), |
| kRedirectedUrl); |
| AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1); |
| |
| ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size()); |
| EXPECT_EQ(kPrerenderingUrl, redirect_chain_observer.redirect_chain()[0]); |
| EXPECT_EQ(kRedirectedUrl, redirect_chain_observer.redirect_chain()[1]); |
| |
| // The prerender host should be registered for the initial request URL, not |
| // the redirected URL. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_FALSE(HasHostForUrl(kRedirectedUrl)); |
| |
| // Regression test for https://crbug.com/1211274. Make sure that we don't |
| // crash when activating a prerendered page which performed a same-origin |
| // redirect. |
| RedirectChainObserver activation_redirect_chain_observer( |
| *shell()->web_contents(), kRedirectedUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(1u, activation_redirect_chain_observer.redirect_chain().size()); |
| EXPECT_EQ(kRedirectedUrl, |
| activation_redirect_chain_observer.redirect_chain()[0]); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CrossOriginRedirection) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering a URL that causes cross-origin redirection. The |
| // cross-origin redirection should fail prerendering. |
| const GURL kRedirectedUrl = GetCrossOriginUrl("/empty.html?prerender"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/server-redirect?" + kRedirectedUrl.spec()); |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| AddPrerenderAsync(kPrerenderingUrl); |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ(GetRequestCount(kRedirectedUrl), 0); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_FALSE(HasHostForUrl(kRedirectedUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kCrossOriginRedirect, 1); |
| } |
| |
| // Makes sure that activation on navigation for an iframes doesn't happen. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_iFrame) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE(AddTestUtilJS(current_frame_host())); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Attempt to activate the prerendered page for an iframe. This should fail |
| // and fallback to network request. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ("LOADED", EvalJs(web_contents(), |
| JsReplace("add_iframe($1)", kPrerenderingUrl))); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| |
| // Activation shouldn't happen, so the prerender host should not be consumed. |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id); |
| } |
| |
| // Makes sure that cross-origin subframe navigations are deferred during |
| // prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DeferCrossOriginSubframeNavigation) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = |
| GetUrl("/prerender/cross_origin_prerender.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE(AddTestUtilJS(current_frame_host())); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/cross_origin_prerender.html?prerender"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| const GURL kSameOriginSubframeUrl = |
| GetUrl("/prerender/cross_origin_prerender.html?same_origin_iframe"); |
| const GURL kCrossOriginSubframeUrl = GetCrossOriginUrl( |
| "/prerender/cross_origin_prerender.html?cross_origin_iframe"); |
| |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 0); |
| ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0); |
| |
| // Add a cross-origin iframe to the prerendering page. |
| RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(AddTestUtilJS(prerender_frame_host)); |
| |
| // Use ExecuteScriptAsync instead of EvalJs as inserted cross-origin iframe |
| // navigation would be deferred and script execution does not finish until |
| // the activation. |
| ExecuteScriptAsync(prerender_frame_host, JsReplace("add_iframe_async($1)", |
| kCrossOriginSubframeUrl)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Add a same-origin iframe to the prerendering page. |
| ASSERT_EQ("LOADED", |
| EvalJs(prerender_frame_host, |
| JsReplace("add_iframe($1)", kSameOriginSubframeUrl))); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 1); |
| ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| ASSERT_EQ("LOADED", |
| EvalJs(prerender_frame_host, JsReplace("wait_iframe_async($1)", |
| kCrossOriginSubframeUrl))); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 1); |
| EXPECT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 1); |
| |
| const char kInitialDocumentPrerenderingScript[] = |
| "initial_document_prerendering"; |
| const char kCurrentDocumentPrerenderingScript[] = "document.prerendering"; |
| const char kOnprerenderingchangeObservedScript[] = |
| "onprerenderingchange_observed"; |
| const char kActivationStartScript[] = |
| "performance.getEntriesByType('navigation')[0].activationStart"; |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, |
| EvalJs(prerender_frame_host, kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, kOnprerenderingchangeObservedScript)); |
| EXPECT_NE(0, EvalJs(prerender_frame_host, kActivationStartScript)); |
| |
| RenderFrameHost* same_origin_render_frame_host = |
| FindRenderFrameHost(*prerender_frame_host, kSameOriginSubframeUrl); |
| DCHECK(same_origin_render_frame_host); |
| EXPECT_EQ(true, EvalJs(same_origin_render_frame_host, |
| kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(same_origin_render_frame_host, |
| kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(true, EvalJs(same_origin_render_frame_host, |
| kOnprerenderingchangeObservedScript)); |
| EXPECT_NE(0, EvalJs(same_origin_render_frame_host, kActivationStartScript)); |
| |
| RenderFrameHost* cross_origin_render_frame_host = |
| FindRenderFrameHost(*prerender_frame_host, kCrossOriginSubframeUrl); |
| DCHECK(cross_origin_render_frame_host); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kOnprerenderingchangeObservedScript)); |
| EXPECT_EQ(0, EvalJs(cross_origin_render_frame_host, kActivationStartScript)); |
| } |
| |
| // Makes sure that subframe navigations are deferred if cross-origin redirects |
| // are observed in a prerendering page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DeferCrossOriginRedirectsOnSubframeNavigation) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = |
| GetUrl("/prerender/cross_origin_prerender.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/cross_origin_prerender.html?prerender"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| const GURL kCrossOriginSubframeUrl = GetCrossOriginUrl( |
| "/prerender/cross_origin_prerender.html?cross_origin_iframe"); |
| const GURL kServerRedirectSubframeUrl = |
| GetUrl("/server-redirect?" + kCrossOriginSubframeUrl.spec()); |
| |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 0); |
| ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0); |
| |
| // Add an iframe pointing to a server redirect page to the prerendering page. |
| RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(AddTestUtilJS(prerender_frame_host)); |
| // Use ExecuteScriptAsync instead of EvalJs as inserted iframe redirect |
| // navigation would be deferred and script execution does not finish until |
| // the activation. |
| ExecuteScriptAsync( |
| prerender_frame_host, |
| JsReplace("add_iframe_async($1)", kServerRedirectSubframeUrl)); |
| WaitForRequest(kServerRedirectSubframeUrl, 1); |
| ASSERT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 1); |
| ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| ASSERT_EQ("LOADED", EvalJs(prerender_frame_host, |
| JsReplace("wait_iframe_async($1)", |
| kServerRedirectSubframeUrl))); |
| EXPECT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 1); |
| EXPECT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 1); |
| |
| const char kInitialDocumentPrerenderingScript[] = |
| "initial_document_prerendering"; |
| const char kCurrentDocumentPrerenderingScript[] = "document.prerendering"; |
| const char kOnprerenderingchangeObservedScript[] = |
| "onprerenderingchange_observed"; |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, |
| EvalJs(prerender_frame_host, kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, kOnprerenderingchangeObservedScript)); |
| |
| RenderFrameHost* cross_origin_render_frame_host = |
| FindRenderFrameHost(*prerender_frame_host, kCrossOriginSubframeUrl); |
| DCHECK(cross_origin_render_frame_host); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kOnprerenderingchangeObservedScript)); |
| } |
| |
| // Test main frame navigation in prerendering page cancels the prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MainFrameNavigationCancelsPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| const GURL kHungUrl = GetUrl("/hung"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // Start a navigation in the prerender frame tree that will cancel the |
| // initiator's prerendering. |
| test::PrerenderHostObserver observer(*web_contents_impl(), host_id); |
| |
| NavigatePrerenderedPage(host_id, kHungUrl); |
| |
| observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMainFrameNavigation, 1); |
| } |
| |
| // Regression test for https://crbug.com/1198051 |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MainFrameSamePageNavigation) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/navigation_controller/hash_anchor_with_iframe.html"); |
| const GURL kAnchorUrl = |
| GetUrl("/navigation_controller/hash_anchor_with_iframe.html#Test"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| WaitForPrerenderLoadCompleted(host_id); |
| |
| // Do a same document navigation |
| NavigatePrerenderedPage(host_id, kAnchorUrl); |
| WaitForPrerenderLoadCompleted(host_id); |
| |
| RedirectChainObserver redirect_chain_observer(*shell()->web_contents(), |
| kAnchorUrl); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| // Regression test for https://crbug.com/1211274. Make sure that we don't |
| // crash when activating a prerendered page which performed a same-document |
| // navigation. |
| ASSERT_EQ(1u, redirect_chain_observer.redirect_chain().size()); |
| EXPECT_EQ(kAnchorUrl, redirect_chain_observer.redirect_chain()[0]); |
| |
| // Make sure the render is not dead by doing a same page navigation. |
| NavigatePrimaryPage(kAnchorUrl); |
| |
| // Make sure we did activate the page and issued no network requests |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| |
| // Makes sure that activation on navigation for a pop-up window doesn't happen. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PopUpWindow) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE(AddTestUtilJS(current_frame_host())); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Attempt to activate the prerendered page for a pop-up window. This should |
| // fail and fallback to network request. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ("LOADED", EvalJs(web_contents(), |
| JsReplace("open_window($1)", kPrerenderingUrl))); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| |
| // Activation shouldn't happen, so the prerender host should not be consumed. |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id); |
| } |
| |
| // Makes sure that activation on navigation for a page that has a pop-up window |
| // doesn't happen. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PageWithPopUpWindow) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE(AddTestUtilJS(current_frame_host())); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender_next"); |
| AddPrerender(kPrerenderingUrl); |
| ASSERT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Open a pop-up window. |
| const GURL kWindowUrl = GetUrl("/empty.html?prerender_window"); |
| EXPECT_EQ("LOADED", |
| EvalJs(web_contents(), JsReplace("open_window($1)", kWindowUrl))); |
| |
| // Attempt to activate the prerendered page for the top-level frame. This |
| // should fail and fallback to network request because the pop-up window |
| // exists. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| |
| // Activation shouldn't happen, so the prerender host should not be consumed. |
| // However, we don't check the existence of the prerender host here unlike |
| // other activation tests because navigating the frame that triggered |
| // prerendering abandons the prerendered page regardless of activation. |
| } |
| |
| // Tests that all RenderFrameHostImpls in the prerendering page know the |
| // prerendering state. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderIframe) { |
| TestHostPrerenderingState(GetUrl("/page_with_iframe.html")); |
| } |
| |
| // Blank <iframe> is a special case. Tests that the blank iframe knows the |
| // prerendering state as well. |
| // TODO(https://crbug.com/1185965): This test is disabled for flakiness. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DISABLED_PrerenderBlankIframe) { |
| TestHostPrerenderingState(GetUrl("/page_with_blank_iframe.html")); |
| } |
| |
| // Tests that an inner WebContents can be attached in a prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivatePageWithInnerContents) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html"); |
| const GURL kInnerContentsUrl = GetUrl("/empty.html?prerender"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| WebContentsImpl* inner_contents = |
| static_cast<WebContentsImpl*>(CreateAndAttachInnerContents( |
| prerendered_render_frame_host->child_at(0)->current_frame_host())); |
| ASSERT_TRUE(NavigateToURLFromRenderer(inner_contents, kInnerContentsUrl)); |
| |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ(GetRequestCount(kInnerContentsUrl), 1); |
| } |
| |
| // Ensure that whether or not a NavigationRequest is for a prerender activation |
| // is available in WebContentsObserver::DidStartNavigation. |
| class IsActivationObserver : public WebContentsObserver { |
| public: |
| IsActivationObserver(WebContents& web_contents, const GURL& url) |
| : WebContentsObserver(&web_contents), url_(url) {} |
| bool did_navigate() { return did_navigate_; } |
| bool was_activation() { return was_activation_; } |
| |
| private: |
| void DidStartNavigation(NavigationHandle* handle) override { |
| if (handle->GetURL() != url_) |
| return; |
| did_navigate_ = true; |
| was_activation_ = handle->IsPrerenderedPageActivation(); |
| } |
| |
| const GURL url_; |
| bool did_navigate_ = false; |
| bool was_activation_ = false; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| NavigationRequestIsPrerenderedPageActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| test::PrerenderHostObserver prerender_observer(*shell()->web_contents(), |
| kPrerenderingUrl); |
| |
| // Navigate to an initial page and start a prerender. Note, AddPrerender will |
| // wait until the prerendered page has finished navigating. |
| { |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| } |
| |
| IsActivationObserver is_activation_observer(*shell()->web_contents(), |
| kPrerenderingUrl); |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. |
| { |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| prerender_observer.WaitForActivation(); |
| } |
| |
| // Ensure that WebContentsObservers see the correct value for |
| // IsPrerenderedPageActivation in DidStartNavigation. |
| ASSERT_TRUE(is_activation_observer.did_navigate()); |
| EXPECT_TRUE(is_activation_observer.was_activation()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivationDoesntRunThrottles) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| test::PrerenderHostObserver prerender_observer(*shell()->web_contents(), |
| kPrerenderingUrl); |
| |
| // Navigate to the initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| ASSERT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| NavigationThrottle* throttle = nullptr; |
| // This will attempt to insert a throttle that DEFERs the navigation at |
| // WillStartRequest into all new navigations. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&throttle](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| std::vector<std::unique_ptr<content::NavigationThrottle>> throttles; |
| |
| auto throttle_ptr = |
| std::make_unique<TestNavigationThrottle>(handle); |
| DCHECK(!throttle); |
| throttle = throttle_ptr.get(); |
| throttle_ptr->SetResponse( |
| TestNavigationThrottle::WILL_START_REQUEST, |
| TestNavigationThrottle::SYNCHRONOUS, NavigationThrottle::DEFER); |
| |
| throttles.push_back(std::move(throttle_ptr)); |
| return throttles; |
| })); |
| |
| // Start a prerender and ensure that a NavigationThrottle can defer the |
| // prerendering navigation. Then resume the navigation so the prerender |
| // navigation and load completes. |
| { |
| TestNavigationManager prerender_manager(shell()->web_contents(), |
| kPrerenderingUrl); |
| AddPrerenderAsync(kPrerenderingUrl); |
| prerender_manager.WaitForFirstYieldAfterDidStartNavigation(); |
| ASSERT_NE(throttle, nullptr); |
| |
| auto* request = |
| NavigationRequest::From(prerender_manager.GetNavigationHandle()); |
| ASSERT_TRUE(request->IsDeferredForTesting()); |
| EXPECT_EQ(request->GetDeferringThrottleForTesting(), throttle); |
| throttle = nullptr; |
| |
| request->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting(); |
| prerender_manager.WaitForNavigationFinished(); |
| |
| auto host_id = GetHostForUrl(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| EXPECT_EQ(GetPrerenderedMainFrameHost(host_id)->GetLastCommittedURL(), |
| kPrerenderingUrl); |
| } |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. The throttle should not have been registered for the |
| // activating navigation. |
| { |
| NavigatePrimaryPage(kPrerenderingUrl); |
| prerender_observer.WaitForActivation(); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| EXPECT_EQ(throttle, nullptr); |
| } |
| } |
| |
| // Ensures that if we attempt to open a URL while prerendering with a window |
| // disposition other than CURRENT_TAB, we fail. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SuppressOpenURL) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender1"); |
| const GURL kSecondUrl = GetUrl("/empty.html?prerender2"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| auto* web_contents = |
| WebContents::FromRenderFrameHost(prerendered_render_frame_host); |
| OpenURLParams params(kSecondUrl, Referrer(), |
| prerendered_render_frame_host->GetFrameTreeNodeId(), |
| WindowOpenDisposition::NEW_WINDOW, |
| ui::PAGE_TRANSITION_LINK, true); |
| params.initiator_origin = |
| prerendered_render_frame_host->GetLastCommittedOrigin(); |
| params.source_render_process_id = |
| prerendered_render_frame_host->GetProcess()->GetID(); |
| params.source_render_frame_id = prerendered_render_frame_host->GetRoutingID(); |
| auto* new_web_contents = web_contents->OpenURL(params); |
| EXPECT_EQ(nullptr, new_web_contents); |
| } |
| |
| // Tests that |RenderFrameHost::ForEachRenderFrameHost| and |
| // |WebContents::ForEachRenderFrameHost| behave correctly when prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ForEachRenderFrameHost) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| // All frames are same-origin due to prerendering restrictions for |
| // cross-origin. |
| const GURL kPrerenderingUrl = |
| GetUrl("/cross_site_iframe_factory.html?a.test(a.test(a.test),a.test)"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| RenderFrameHostImpl* initiator_render_frame_host = current_frame_host(); |
| |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| RenderFrameHostImpl* rfh_sub_1 = |
| prerendered_render_frame_host->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_sub_1_1 = |
| rfh_sub_1->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_sub_2 = |
| prerendered_render_frame_host->child_at(1)->current_frame_host(); |
| |
| EXPECT_THAT(CollectAllRenderFrameHosts(prerendered_render_frame_host), |
| testing::ElementsAre(prerendered_render_frame_host, rfh_sub_1, |
| rfh_sub_2, rfh_sub_1_1)); |
| |
| // When iterating over all RenderFrameHosts in a WebContents, we should see |
| // the RFHs of both the primary page and the prerendered page. |
| EXPECT_THAT(CollectAllRenderFrameHosts(web_contents_impl()), |
| testing::UnorderedElementsAre(initiator_render_frame_host, |
| prerendered_render_frame_host, |
| rfh_sub_1, rfh_sub_2, rfh_sub_1_1)); |
| } |
| |
| class MojoCapabilityControlTestContentBrowserClient |
| : public TestContentBrowserClient, |
| mojom::TestInterfaceForDefer, |
| mojom::TestInterfaceForGrant, |
| mojom::TestInterfaceForCancel, |
| mojom::TestInterfaceForUnexpected { |
| public: |
| void RegisterBrowserInterfaceBindersForFrame( |
| RenderFrameHost* render_frame_host, |
| mojo::BinderMapWithContext<RenderFrameHost*>* map) override { |
| map->Add<mojom::TestInterfaceForDefer>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindDeferInterface, |
| base::Unretained(this))); |
| map->Add<mojom::TestInterfaceForGrant>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindGrantInterface, |
| base::Unretained(this))); |
| map->Add<mojom::TestInterfaceForCancel>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindCancelInterface, |
| base::Unretained(this))); |
| map->Add<mojom::TestInterfaceForUnexpected>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindUnexpectedInterface, |
| base::Unretained(this))); |
| } |
| |
| void RegisterMojoBinderPoliciesForSameOriginPrerendering( |
| MojoBinderPolicyMap& policy_map) override { |
| policy_map.SetPolicy<mojom::TestInterfaceForGrant>( |
| MojoBinderPolicy::kGrant); |
| policy_map.SetPolicy<mojom::TestInterfaceForCancel>( |
| MojoBinderPolicy::kCancel); |
| policy_map.SetPolicy<mojom::TestInterfaceForUnexpected>( |
| MojoBinderPolicy::kUnexpected); |
| } |
| |
| void BindDeferInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<content::mojom::TestInterfaceForDefer> receiver) { |
| defer_receiver_set_.Add(this, std::move(receiver)); |
| } |
| |
| void BindGrantInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::TestInterfaceForGrant> receiver) { |
| grant_receiver_set_.Add(this, std::move(receiver)); |
| } |
| |
| void BindCancelInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::TestInterfaceForCancel> receiver) { |
| cancel_receiver_.Bind(std::move(receiver)); |
| } |
| |
| void BindUnexpectedInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::TestInterfaceForUnexpected> receiver) { |
| unexpected_receiver_.Bind(std::move(receiver)); |
| } |
| |
| // mojom::TestInterfaceForDefer implementation. |
| void Ping(PingCallback callback) override { std::move(callback).Run(); } |
| |
| size_t GetDeferReceiverSetSize() { return defer_receiver_set_.size(); } |
| |
| size_t GetGrantReceiverSetSize() { return grant_receiver_set_.size(); } |
| |
| private: |
| mojo::ReceiverSet<mojom::TestInterfaceForDefer> defer_receiver_set_; |
| mojo::ReceiverSet<mojom::TestInterfaceForGrant> grant_receiver_set_; |
| mojo::Receiver<mojom::TestInterfaceForCancel> cancel_receiver_{this}; |
| mojo::Receiver<mojom::TestInterfaceForUnexpected> unexpected_receiver_{this}; |
| }; |
| |
| // Tests that binding requests are handled according to MojoBinderPolicyMap |
| // during prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MojoCapabilityControl) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| std::vector<RenderFrameHost*> frames = |
| prerendered_render_frame_host->GetFramesInSubtree(); |
| |
| // A barrier closure to wait until a deferred interface is granted on all |
| // frames. |
| base::RunLoop run_loop; |
| auto barrier_closure = |
| base::BarrierClosure(frames.size(), run_loop.QuitClosure()); |
| |
| mojo::RemoteSet<mojom::TestInterfaceForDefer> defer_remote_set; |
| mojo::RemoteSet<mojom::TestInterfaceForGrant> grant_remote_set; |
| for (auto* frame : frames) { |
| auto* rfhi = static_cast<RenderFrameHostImpl*>(frame); |
| EXPECT_TRUE(rfhi->frame_tree()->is_prerendering()); |
| EXPECT_EQ(rfhi->lifecycle_state(), LifecycleStateImpl::kPrerendering); |
| EXPECT_EQ(rfhi->GetLifecycleState(), |
| RenderFrameHost::LifecycleState::kPrerendering); |
| |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| rfhi->browser_interface_broker_receiver_for_testing(); |
| blink::mojom::BrowserInterfaceBroker* prerender_broker = |
| bib.internal_state()->impl(); |
| |
| // Try to bind a kDefer interface. |
| mojo::Remote<mojom::TestInterfaceForDefer> prerender_defer_remote; |
| prerender_broker->GetInterface( |
| prerender_defer_remote.BindNewPipeAndPassReceiver()); |
| // The barrier closure will be called after the deferred interface is |
| // granted. |
| prerender_defer_remote->Ping(barrier_closure); |
| defer_remote_set.Add(std::move(prerender_defer_remote)); |
| |
| // Try to bind a kGrant interface. |
| mojo::Remote<mojom::TestInterfaceForGrant> prerender_grant_remote; |
| prerender_broker->GetInterface( |
| prerender_grant_remote.BindNewPipeAndPassReceiver()); |
| grant_remote_set.Add(std::move(prerender_grant_remote)); |
| } |
| // Verify that BrowserInterfaceBrokerImpl defers running binders whose |
| // policies are kDefer until the prerendered page is activated. |
| EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), 0U); |
| // Verify that BrowserInterfaceBrokerImpl executes kGrant binders immediately. |
| EXPECT_EQ(test_browser_client.GetGrantReceiverSetSize(), frames.size()); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // Wait until the deferred interface is granted on all frames. |
| run_loop.Run(); |
| EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), frames.size()); |
| |
| SetBrowserClientForTesting(old_browser_client); |
| } |
| |
| // Tests that mojo capability control will cancel prerendering if the main frame |
| // receives a request for a kCancel interface. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MojoCapabilityControl_CancelMainFrame) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| prerendered_render_frame_host |
| ->browser_interface_broker_receiver_for_testing(); |
| blink::mojom::BrowserInterfaceBroker* prerender_broker = |
| bib.internal_state()->impl(); |
| |
| // Send a kCancel request to cancel prerendering. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| mojo::Remote<mojom::TestInterfaceForCancel> remote; |
| prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver()); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| // `TestInterfaceForCancel` doesn't have a enum value because it is not used |
| // in production, so histogram_tester should log |
| // PrerenderCancelledInterface::kUnkown here. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kUnknown, 1); |
| SetBrowserClientForTesting(old_browser_client); |
| } |
| |
| // Tests that mojo capability control will cancel prerendering if child frames |
| // receive a request for a kCancel interface. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MojoCapabilityControl_CancelIframe) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| ASSERT_GE(main_render_frame_host->child_count(), 1U); |
| RenderFrameHostImpl* child_render_frame_host = |
| main_render_frame_host->child_at(0U)->current_frame_host(); |
| EXPECT_NE(main_render_frame_host->GetLastCommittedURL(), |
| child_render_frame_host->GetLastCommittedURL()); |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| child_render_frame_host->browser_interface_broker_receiver_for_testing(); |
| blink::mojom::BrowserInterfaceBroker* prerender_broker = |
| bib.internal_state()->impl(); |
| |
| // Send a kCancel request to cancel prerendering. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| |
| mojo::Remote<mojom::TestInterfaceForCancel> remote; |
| prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver()); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| SetBrowserClientForTesting(old_browser_client); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| // `TestInterfaceForCancel` doesn't have a enum value because it is not used |
| // in production, so histogram_tester should log |
| // PrerenderCancelledInterface::kUnkown here. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kUnknown, 1); |
| } |
| |
| // Tests that mojo capability control will crash the prerender if the browser |
| // process receives a kUnexpected interface. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MojoCapabilityControl_HandleUnexpected) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender1"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Set up the error handler for bad mojo messages. |
| std::string bad_message_error; |
| mojo::SetDefaultProcessErrorHandler( |
| base::BindLambdaForTesting([&](const std::string& error) { |
| EXPECT_FALSE(error.empty()); |
| EXPECT_TRUE(bad_message_error.empty()); |
| bad_message_error = error; |
| })); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Rebind a receiver for testing. |
| // mojo::ReportBadMessage must be called within the stack frame derived from |
| // mojo IPC calls, so this browser test should call the |
| // remote<blink::mojom::BrowserInterfaceBroker>::GetInterface() to test |
| // unexpected interfaces. But its remote end is in renderer processes and |
| // inaccessible, so the test code has to create another BrowserInterfaceBroker |
| // pipe and rebind the receiver end so as to send the request from the remote. |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| main_render_frame_host->browser_interface_broker_receiver_for_testing(); |
| auto broker_receiver_of_previous_document = bib.Unbind(); |
| ASSERT_TRUE(broker_receiver_of_previous_document); |
| mojo::Remote<blink::mojom::BrowserInterfaceBroker> remote_broker; |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> fake_receiver = |
| remote_broker.BindNewPipeAndPassReceiver(); |
| main_render_frame_host->BindBrowserInterfaceBrokerReceiver( |
| std::move(fake_receiver)); |
| |
| // Send a kUnexpected request. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| mojo::Remote<mojom::TestInterfaceForUnexpected> remote; |
| remote_broker->GetInterface(remote.BindNewPipeAndPassReceiver()); |
| remote_broker.FlushForTesting(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_EQ(bad_message_error, |
| "MBPA_BAD_INTERFACE: content.mojom.TestInterfaceForUnexpected"); |
| |
| SetBrowserClientForTesting(old_browser_client); |
| } |
| |
| // TODO(https://crbug.com/1132746): Test canceling prerendering when its |
| // initiator is no longer interested in prerending this page. |
| |
| // TODO(https://crbug.com/1132746): Test prerendering for auth error, etc. |
| |
| // Tests for feature restrictions in prerendered pages ========================= |
| |
| // Tests that window.open() in a prerendering page fails. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FeatureRestriction_WindowOpen) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_frame = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(AddTestUtilJS(prerender_frame)); |
| |
| // Attempt to open a window in the prerendered page. This should fail. |
| const GURL kWindowOpenUrl = GetUrl("/empty.html?prerender"); |
| |
| EXPECT_EQ("FAILED", EvalJs(prerender_frame, |
| JsReplace("open_window($1)", kWindowOpenUrl))); |
| EXPECT_EQ(GetRequestCount(kWindowOpenUrl), 0); |
| |
| // Opening a window shouldn't cancel prerendering. |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RenderFrameHostLifecycleState) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_EQ(current_frame_host()->lifecycle_state(), |
| LifecycleStateImpl::kActive); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Open an iframe in the prerendered page. |
| RenderFrameHostImpl* rfh_a = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(AddTestUtilJS(rfh_a)); |
| EXPECT_EQ("LOADED", |
| EvalJs(rfh_a, JsReplace("add_iframe($1)", |
| GetUrl("/empty.html?prerender")))); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| |
| // Both rfh_a and rfh_b lifecycle state's should be kPrerendering. |
| EXPECT_EQ(LifecycleStateImpl::kPrerendering, rfh_a->lifecycle_state()); |
| EXPECT_EQ(LifecycleStateImpl::kPrerendering, rfh_b->lifecycle_state()); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // Both rfh_a and rfh_b lifecycle state's should be kActive after activation. |
| EXPECT_EQ(LifecycleStateImpl::kActive, rfh_a->lifecycle_state()); |
| EXPECT_EQ(LifecycleStateImpl::kActive, rfh_b->lifecycle_state()); |
| |
| // "Navigation.TimeToActivatePrerender" histogram should be recorded on every |
| // prerender activation. |
| histogram_tester.ExpectTotalCount("Navigation.TimeToActivatePrerender", 1u); |
| } |
| |
| // Test that prerender activation is deferred and resumed after the ongoing |
| // (in-flight) main-frame navigation in the prerendering frame tree commits. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| SupportActivationWithOngoingMainFrameNavigation) { |
| base::HistogramTester histogram_tester; |
| |
| // Create a HTTP response to control prerendering main-frame navigation. |
| net::test_server::ControllableHttpResponse main_document_response( |
| embedded_test_server(), "/main_document"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/main_document"); |
| |
| // Navigate to an initial page in primary frame tree. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender, and navigate to a page that doesn't commit navigation. |
| { |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| AddPrerenderAsync(kPrerenderingUrl); |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| } |
| |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id); |
| EXPECT_FALSE(prerender_observer.was_activated()); |
| |
| // Defer the activation until the ongoing main-frame navigation in prerender |
| // frame tree commits. |
| { |
| // Start navigation in primary page to kPrerenderingUrl. |
| TestNavigationManager primary_page_manager(shell()->web_contents(), |
| kPrerenderingUrl); |
| ASSERT_TRUE(ExecJs(shell()->web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| |
| // Continue the navigation until it reaches WillCommitNavigation where the |
| // navigation is deferred by a commit deferral. |
| EXPECT_TRUE(primary_page_manager.WaitForResponse()); |
| primary_page_manager.ResumeNavigation(); |
| |
| NavigationRequest* request = static_cast<NavigationRequest*>( |
| primary_page_manager.GetNavigationHandle()); |
| |
| // The navigation should be deferred. |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| |
| // The navigation should not have proceeded past WillProcessResponse |
| // because the PrerenderCommitDeferringCondition is deferring it. |
| EXPECT_EQ(request->state(), NavigationRequest::WILL_PROCESS_RESPONSE); |
| |
| // Complete the prerender response and finish ongoing prerender main frame |
| // navigation. |
| main_document_response.WaitForRequest(); |
| main_document_response.Send(net::HTTP_OK, "main_document"); |
| main_document_response.Done(); |
| |
| // The URL should still point to the kInitialUrl until the activation is |
| // completed. |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Make sure that the prerender was not activated yet. |
| EXPECT_FALSE(prerender_observer.was_activated()); |
| |
| primary_page_manager.WaitForNavigationFinished(); |
| prerender_observer.WaitForActivation(); |
| } |
| |
| // Prerender should be activated and the URL should point to kPrerenderingUrl. |
| { |
| EXPECT_TRUE(prerender_observer.was_activated()); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| } |
| |
| // "Navigation.Prerender.ActivationCommitDeferTime" histogram should be |
| // recorded as PrerenderCommitDeferringCondition defers the navigation. |
| histogram_tester.ExpectTotalCount( |
| "Navigation.Prerender.ActivationCommitDeferTime", 1u); |
| } |
| |
| // Tests that prerendering is gated behind CSP:prefetch-src |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CSPPrefetchSrc) { |
| base::HistogramTester histogram_tester; |
| |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| const std::string kCSPScript = R"( |
| const meta = document.createElement('meta'); |
| meta.httpEquiv = "Content-Security-Policy"; |
| meta.content = "prefetch-src https://a.test:*/title1.html"; |
| document.getElementsByTagName('head')[0].appendChild(meta); |
| )"; |
| |
| // Add CSP:prefetch-src */title1.html |
| EXPECT_TRUE(ExecJs(current_frame_host(), kCSPScript)); |
| |
| const char* kConsolePattern = |
| "Refused to prefetch content from " |
| "'https://a.test:*/*.html' because it violates the " |
| "following Content Security Policy directive: \"prefetch-src " |
| "https://a.test:*/title1.html\"*"; |
| |
| // Check what happens when a prerendering is blocked: |
| { |
| GURL disallowed_url = GetUrl("/title2.html"); |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| |
| // Prerender will fail. Then FindHostByUrlForTesting() should return null. |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| AddPrerenderAsync(disallowed_url); |
| observer.WaitForTrigger(disallowed_url); |
| EXPECT_FALSE(HasHostForUrl(disallowed_url)); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(disallowed_url), 0); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kNavigationRequestBlockedByCsp, 1); |
| } |
| |
| // TODO(https://crbug.com/1215031): Remove this reload after fixing the issue. |
| // Now a document cannot trigger prerendering twice, even if the first started |
| // one is canceled. So we have to reload the initiator page to get a new |
| // document instance. |
| ReloadBlockUntilNavigationsComplete(shell(), 1); |
| EXPECT_TRUE(ExecJs(current_frame_host(), kCSPScript)); |
| |
| // Check what happens when prerendering isn't blocked. |
| { |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| GURL kAllowedUrl = GetUrl("/title1.html"); |
| AddPrerender(kAllowedUrl); |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(kAllowedUrl), 1); |
| } |
| } |
| |
| // Tests that prerendering is gated behind CSP:default-src. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CSPDefaultSrc) { |
| base::HistogramTester histogram_tester; |
| |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| std::string kCSPScript = R"( |
| const meta = document.createElement('meta'); |
| meta.httpEquiv = "Content-Security-Policy"; |
| meta.content = |
| "default-src https://a.test:*/title1.html; script-src 'unsafe-inline'"; |
| document.getElementsByTagName('head')[0].appendChild(meta); |
| )"; |
| |
| // Add CSP:prefetch-src */title1.html |
| EXPECT_TRUE(ExecJs(current_frame_host(), kCSPScript)); |
| |
| const char* kConsolePattern = |
| "Refused to prefetch content from " |
| "'https://a.test:*/*.html' because it violates the " |
| "following Content Security Policy directive: \"default-src " |
| "https://a.test:*/title1.html\"*"; |
| |
| // Check what happens when a prerendering is blocked: |
| { |
| GURL disallowed_url = GetUrl("/title2.html"); |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| AddPrerenderAsync(disallowed_url); |
| observer.WaitForTrigger(disallowed_url); |
| EXPECT_FALSE(HasHostForUrl(disallowed_url)); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(disallowed_url), 0); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kNavigationRequestBlockedByCsp, 1); |
| } |
| |
| // TODO(https://crbug.com/1215031): Remove this reload after fixing the issue. |
| // Now a document cannot trigger prerendering twice, even if the first started |
| // one is canceled. So we have to reload the initiator page to get a new |
| // document instance. |
| ReloadBlockUntilNavigationsComplete(shell(), 1); |
| EXPECT_TRUE(ExecJs(current_frame_host(), kCSPScript)); |
| |
| // Check what happens when prerendering isn't blocked. |
| { |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| GURL kAllowedUrl = GetUrl("/title1.html"); |
| AddPrerender(kAllowedUrl); |
| EXPECT_EQ(0u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(kAllowedUrl), 1); |
| } |
| } |
| |
| // TODO(https://crbug.com/1182032): Now the File System Access API is not |
| // supported on Android. Enable this browser test after |
| // https://crbug.com/1011535 is fixed. |
| #if defined(OS_ANDROID) |
| #define MAYBE_DeferPrivateOriginFileSystem DISABLED_DeferPrivateOriginFileSystem |
| #else |
| #define MAYBE_DeferPrivateOriginFileSystem DeferPrivateOriginFileSystem |
| #endif |
| |
| // Tests that access to the origin private file system via the File System |
| // Access API is deferred until activating the prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MAYBE_DeferPrivateOriginFileSystem) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/restriction_file_system.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| EXPECT_EQ( |
| true, |
| ExecJs(prerender_render_frame_host, "accessOriginPrivateFileSystem();", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE | |
| EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| // Run a event loop so the page can fail the test. |
| EXPECT_TRUE(ExecJs(prerender_render_frame_host, "runLoop();")); |
| |
| // Activate the page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // Wait for the completion of `accessOriginPrivateFileSystem`. |
| EXPECT_EQ(true, EvalJs(prerender_render_frame_host, "result;")); |
| // Check the event sequence seen in the prerendered page. |
| EvalJsResult results = EvalJs(prerender_render_frame_host, "eventsSeen"); |
| std::vector<std::string> eventsSeen; |
| const base::Value resultsList = results.ExtractList(); |
| for (auto& result : resultsList.GetList()) |
| eventsSeen.push_back(result.GetString()); |
| EXPECT_THAT(eventsSeen, |
| testing::ElementsAreArray( |
| {"accessOriginPrivateFileSystem (prerendering: true)", |
| "prerenderingchange (prerendering: false)", |
| "getDirectory (prerendering: false)"})); |
| } |
| |
| // Tests that RenderDocumentHostUserData object is not cleared on activating a |
| // prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RenderDocumentHostUserData) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Get the DocumentData associated with prerender RenderFrameHost. |
| DocumentData::CreateForCurrentDocument(prerender_render_frame_host); |
| base::WeakPtr<DocumentData> data = |
| DocumentData::GetForCurrentDocument(prerender_render_frame_host) |
| ->GetWeakPtr(); |
| EXPECT_TRUE(data); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // The prerender host should be consumed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // DocumentData associated with document shouldn't have been cleared on |
| // activating prerendered page. |
| base::WeakPtr<DocumentData> data_after_activation = |
| DocumentData::GetForCurrentDocument(current_frame_host())->GetWeakPtr(); |
| EXPECT_TRUE(data_after_activation); |
| |
| // Both the instances of DocumentData before and after activation should point |
| // to the same object and make sure they aren't null. |
| EXPECT_EQ(data_after_activation.get(), data.get()); |
| } |
| |
| // Tests that executing the GamepadMonitor API on a prerendering before |
| // navigating to the prerendered page causes cancel prerendering. |
| // This test cannot be a web test because web tests handles the GamepadMonitor |
| // interface on the renderer side. See GamepadController::Install(). |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, GamepadMonitorCancelPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Executing `navigator.getGamepads()` to start binding the GamepadMonitor |
| // interface. |
| ignore_result(EvalJs(prerender_render_frame_host, "navigator.getGamepads()", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| // Verify Mojo capability control cancels prerendering. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kGamepadMonitor, 1); |
| } |
| |
| // TODO(https://crbug.com/1201980) LaCrOS binds the HidManager interface, which |
| // might be required by Gamepad Service, in a different way. Disable this test |
| // before figuring out how to set the test context correctly. |
| #if !BUILDFLAG(IS_CHROMEOS_LACROS) |
| // Tests that requesting to bind the GamepadMonitor interface after the |
| // prerenderingchange event dispatched does not cancel prerendering. |
| // This test cannot be a web test because web tests handles the GamepadMonitor |
| // interface on the renderer side. See GamepadController::Install(). |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, GamepadMonitorAfterNavigation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/restriction-gamepad.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| AddPrerender(kPrerenderingUrl); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // Activate the prerendered page to dispatch the prerenderingchange event and |
| // run the Gamepad API in the event. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| // Wait for the completion of the prerenderingchange event to make sure the |
| // API is called. |
| EXPECT_EQ(true, EvalJs(shell()->web_contents(), "prerenderingChanged")); |
| // The API call shouldn't discard the prerendered page and shouldn't restart |
| // navigation. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| // Tests that accessing the clipboard via the execCommand API fails because the |
| // page does not has any user activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ClipboardByExecCommandFail) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Access the clipboard and fail. |
| EXPECT_EQ(false, |
| EvalJs(prerender_render_frame_host, "document.execCommand('copy');", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| EXPECT_EQ(false, EvalJs(prerender_render_frame_host, |
| "document.execCommand('paste');", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| } |
| |
| void LoadAndWaitForPrerenderDestroyed(WebContents* const web_contents, |
| const GURL prerendering_url, |
| test::PrerenderTestHelper* helper) { |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents); |
| helper->AddPrerenderAsync(prerendering_url); |
| registry_observer.WaitForTrigger(prerendering_url); |
| int host_id = helper->GetHostForUrl(prerendering_url); |
| test::PrerenderHostObserver host_observer(*web_contents, host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(helper->GetHostForUrl(prerendering_url), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| } |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| // Tests that we will cancel the prerendering if the prerendering page attempts |
| // to use plugins. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PluginsCancelPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| LoadAndWaitForPrerenderDestroyed( |
| web_contents(), GetUrl("/prerender/page-with-embedded-plugin.html"), |
| prerender_helper()); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kPlugin, 1); |
| |
| // TODO(https://crbug.com/1215031): Remove this reload after fixing the issue. |
| // Now a document cannot trigger prerendering twice, even if the first started |
| // one is canceled. So we have to reload the initiator page to get a new |
| // document instance. |
| ReloadBlockUntilNavigationsComplete(shell(), 1); |
| LoadAndWaitForPrerenderDestroyed( |
| web_contents(), GetUrl("/prerender/page-with-object-plugin.html"), |
| prerender_helper()); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kPlugin, 2); |
| } |
| #endif // BUILDFLAG(ENABLE_PLUGINS) |
| |
| // This is a browser test and cannot be upstreamed to WPT because it diverges |
| // from the spec by cancelling prerendering in the Notification constructor, |
| // whereas the spec says to defer upon use requestPermission(). |
| #if defined(OS_ANDROID) |
| // On Android the Notification constructor throws an exception regardless of |
| // whether the page is being prerendered. |
| // Tests that we will get the exception from the prerendering if the |
| // prerendering page attempts to use notification. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NotificationConstructorAndroid) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Create the Notification and fail. |
| EXPECT_EQ(false, EvalJs(prerender_render_frame_host, R"( |
| (() => { |
| try { new Notification('My Notification'); return true; |
| } catch(e) { return false; } |
| })(); |
| )")); |
| } |
| #else |
| // On non-Android the Notification constructor is supported and can be used to |
| // show a notification, but if used during prerendering it cancels prerendering. |
| // Tests that we will cancel the prerendering if the prerendering page attempts |
| // to use notification. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NotificationConstructor) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| LoadAndWaitForPrerenderDestroyed(web_contents(), |
| GetUrl("/prerender/notification.html"), |
| prerender_helper()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kNotificationService, 1); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DownloadInMainFrame) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // TODO(crbug.com/1215073): Make a WPT for the content-disposition WPT test. |
| const GURL download_url = |
| GetUrl("/set-header?Content-Disposition: attachment"); |
| |
| LoadAndWaitForPrerenderDestroyed(web_contents(), download_url, |
| prerender_helper()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kDownload, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DownloadInSubframe) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_host = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(AddTestUtilJS(prerender_host)); |
| |
| // TODO(crbug.com/1215073): Make a WPT for the content-disposition WPT test. |
| const GURL download_url = |
| GetUrl("/set-header?Content-Disposition: attachment"); |
| ExecuteScriptAsync(prerender_host, |
| JsReplace("add_iframe_async($1)", download_url)); |
| |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kDownload, 1); |
| } |
| |
| // End: Tests for feature restrictions in prerendered pages ==================== |
| |
| // Tests that prerendering doesn't run for low-end devices. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LowEndDevice) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Set low-end device mode. |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kEnableLowEndDeviceMode); |
| |
| // Attempt to prerender. |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| AddPrerenderAsync(kPrerenderingUrl); |
| |
| // It should fail. |
| observer.WaitForTrigger(kPrerenderingUrl); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kLowEndDevice, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| IsInactiveAndDisallowActivationCancelsPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A prerender host for the URL should be registered. |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Invoke IsInactiveAndDisallowActivation for the prerendered document. |
| EXPECT_EQ(prerender_render_frame_host->lifecycle_state(), |
| RenderFrameHostImpl::LifecycleStateImpl::kPrerendering); |
| EXPECT_TRUE(prerender_render_frame_host->IsInactiveAndDisallowActivation()); |
| |
| // The prerender host for the URL should be destroyed as |
| // RenderFrameHost::IsInactiveAndDisallowActivation cancels prerendering in |
| // LifecycleStateImpl::kPrerendering state. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Cancelling the prerendering disables the activation. The navigation |
| // should issue a request again. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kDestroyed, 1); |
| } |
| |
| // Make sure input events are routed to the primary FrameTree not the prerender |
| // one. See https://crbug.com/1197136 |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, InputRoutedToPrimaryFrameTree) { |
| const GURL kInitialUrl = GetUrl("/prerender/simple_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| WaitForPrerenderLoadCompletion(kPrerenderingUrl); |
| |
| // Touch / click the link and wait for the navigation to complete. |
| TestNavigationObserver navigation_observer(web_contents()); |
| SyntheticTapGestureParams params; |
| params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput; |
| params.position = GetCenterCoordinatesOfElementWithId(web_contents(), "link"); |
| web_contents_impl()->GetRenderViewHost()->GetWidget()->QueueSyntheticGesture( |
| std::make_unique<SyntheticTapGesture>(params), base::DoNothing()); |
| navigation_observer.Wait(); |
| |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, VisibilityWhilePrerendering) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A prerender host for the URL should be registered. |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // The visibility state must be "hidden" while prerendering. |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| auto* rvh = static_cast<RenderViewHostImpl*>( |
| prerendered_render_frame_host->GetRenderViewHost()); |
| EXPECT_EQ(rvh->GetPageLifecycleStateManager() |
| ->CalculatePageLifecycleState() |
| ->visibility, |
| PageVisibilityState::kHidden); |
| } |
| |
| // Tests that prerendering doesn't affect WebContents::GetTitle(). |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TitleWhilePrerendering) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/simple_page.html"); |
| const std::u16string kInitialTitle(u"title"); |
| const std::u16string kPrerenderingTitle(u"OK"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| JsReplace("document.title = $1", kInitialTitle))); |
| EXPECT_EQ(shell()->web_contents()->GetTitle(), kInitialTitle); |
| |
| // Start a prerender to `kPrerenderUrl` that has title `kPrerenderingTitle`. |
| ASSERT_NE(AddPrerender(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Make sure that WebContents::GetTitle() returns the current title from the |
| // primary page. |
| EXPECT_EQ(shell()->web_contents()->GetTitle(), kInitialTitle); |
| |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| // The title should be updated with the activated page. |
| EXPECT_EQ(shell()->web_contents()->GetTitle(), kPrerenderingTitle); |
| } |
| |
| // Ensures WebContents::OpenURL targeting a frame in a prerendered host will |
| // successfully navigate that frame. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, OpenURLInPrerenderingFrame) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html"); |
| const GURL kNewIframeUrl = GetUrl("/simple_page.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| auto* child_frame = ChildFrameAt(prerendered_render_frame_host, 0); |
| ASSERT_TRUE(child_frame); |
| |
| // Navigate the iframe's FrameTreeNode in the prerendering frame tree. This |
| // should successfully navigate. |
| TestNavigationManager iframe_observer(shell()->web_contents(), kNewIframeUrl); |
| shell()->web_contents()->OpenURL(OpenURLParams( |
| kNewIframeUrl, Referrer(), child_frame->GetFrameTreeNodeId(), |
| WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_SUBFRAME, |
| /*is_renderer_initiated=*/false)); |
| iframe_observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(iframe_observer.was_committed()); |
| EXPECT_TRUE(iframe_observer.was_successful()); |
| EXPECT_EQ(child_frame->GetLastCommittedURL(), kNewIframeUrl); |
| } |
| |
| // Ensures WebContents::OpenURL with a cross-origin URL targeting a frame in a |
| // prerendered host will successfully navigate that frame, though it should be |
| // deferred until activation. |
| // TODO(bokan): This test exposes a race condition between the iframe |
| // navigation and the prerenderingchange event being dispatched. |
| // https://crbug.com/1213454. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DISABLED_OpenURLCrossOriginInPrerenderingFrame) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html"); |
| const GURL kNewIframeUrl = GetCrossOriginUrl("/simple_page.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| auto* child_frame = ChildFrameAt(prerendered_render_frame_host, 0); |
| ASSERT_TRUE(child_frame); |
| |
| TestNavigationManager iframe_observer(shell()->web_contents(), kNewIframeUrl); |
| |
| // Navigate the iframe's FrameTreeNode in the prerendering frame tree. This |
| // should successfully navigate but the navigation will be deferred until the |
| // prerendering page is activated. |
| { |
| shell()->web_contents()->OpenURL(OpenURLParams( |
| kNewIframeUrl, Referrer(), child_frame->GetFrameTreeNodeId(), |
| WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_SUBFRAME, |
| /*is_renderer_initiated=*/false)); |
| iframe_observer.WaitForFirstYieldAfterDidStartNavigation(); |
| NavigationRequest* request = |
| static_cast<NavigationRequest*>(iframe_observer.GetNavigationHandle()); |
| EXPECT_EQ(request->state(), NavigationRequest::WILL_START_REQUEST); |
| EXPECT_TRUE(request->IsDeferredForTesting()); |
| } |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. |
| { |
| test::PrerenderHostObserver prerender_observer(*web_contents(), |
| kPrerenderingUrl); |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| prerender_observer.WaitForActivation(); |
| } |
| |
| // Now that we're activated, the iframe navigation should be able to finish. |
| // Ensure the navigation completes in the iframe. |
| { |
| iframe_observer.WaitForNavigationFinished(); |
| content::RenderFrameHost* child_frame = |
| ChildFrameAt(web_contents()->GetMainFrame(), 0); |
| ASSERT_TRUE(child_frame); |
| EXPECT_EQ(child_frame->GetLastCommittedURL(), kNewIframeUrl); |
| } |
| } |
| |
| // Test starting a main frame navigation after the initial |
| // prerender navigation when activation has already started. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MainFrameNavigationDuringActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?1"); |
| const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_rfh = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| test::PrerenderHostObserver prerender_observer(*web_contents(), |
| prerender_host_id); |
| auto* prerender_ftn = prerendered_rfh->frame_tree_node(); |
| EXPECT_FALSE(prerender_ftn->HasNavigation()); |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. Use a CommitDeferringCondition to pause activation |
| // before it completes. |
| TestNavigationManager activation_observer(shell()->web_contents(), |
| kPrerenderingUrl); |
| MockCommitDeferringConditionWrapper condition(/*is_ready_to_commit=*/false); |
| { |
| MockCommitDeferringConditionInstaller installer(web_contents(), |
| condition.PassToDelegate()); |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| |
| ASSERT_TRUE(activation_observer.WaitForResponse()); |
| activation_observer.ResumeNavigation(); |
| |
| // The prerender host should have been reserved. |
| ASSERT_TRUE( |
| web_contents_impl()->GetPrerenderHostRegistry()->FindReservedHostById( |
| prerender_host_id)); |
| } |
| // Wait for the condition to pause the activation. |
| condition.WaitUntilInvoked(); |
| EXPECT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Make a navigation in the prerendered page. This navigation should |
| // be cancelled by PrerenderNavigationThrottle. |
| TestNavigationManager bad_nav_observer(web_contents(), kPrerenderingUrl2); |
| ASSERT_TRUE( |
| ExecJs(prerendered_rfh, JsReplace("location = $1", kPrerenderingUrl2))); |
| bad_nav_observer.WaitForNavigationFinished(); |
| EXPECT_FALSE(bad_nav_observer.was_successful()); |
| EXPECT_EQ(prerendered_rfh->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| // Finish activation. It should work as normal. |
| condition.CallResumeClosure(); |
| prerender_observer.WaitForActivation(); |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| } |
| |
| // Ensures WebContents::OpenURL to a frame in a currently activating (i.e. |
| // "reserved") prerendering host navigates the frame. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| OpenURLInReservedPrerenderingFrame) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html"); |
| const GURL kNewIframeUrl = GetUrl("/simple_page.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int prerender_host_id = RenderFrameHost::kNoFrameTreeNodeId; |
| RenderFrameHost* child_frame = nullptr; |
| { |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| prerender_host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_NE(prerender_host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| auto* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| child_frame = ChildFrameAt(prerendered_render_frame_host, 0); |
| ASSERT_TRUE(child_frame); |
| } |
| |
| // Now navigate the primary page to the prerendered URL so that we activate |
| // the prerender. |
| test::PrerenderHostObserver prerender_observer(*web_contents(), |
| kPrerenderingUrl); |
| TestNavigationManager activation_observer(shell()->web_contents(), |
| kPrerenderingUrl); |
| MockCommitDeferringConditionWrapper condition(/*is_ready_to_commit=*/false); |
| { |
| MockCommitDeferringConditionInstaller installer(web_contents(), |
| condition.PassToDelegate()); |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| |
| ASSERT_TRUE(activation_observer.WaitForResponse()); |
| activation_observer.ResumeNavigation(); |
| |
| // The prerender host should have been reserved. |
| ASSERT_TRUE( |
| web_contents_impl()->GetPrerenderHostRegistry()->FindReservedHostById( |
| prerender_host_id)); |
| } |
| |
| // Use the OpenURL API to navigate the iframe in the reserved prerendering |
| // frame tree. This navigation should succeed. |
| { |
| TestNavigationManager iframe_observer(shell()->web_contents(), |
| kNewIframeUrl); |
| shell()->web_contents()->OpenURL(OpenURLParams( |
| kNewIframeUrl, Referrer(), child_frame->GetFrameTreeNodeId(), |
| WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_SUBFRAME, |
| /*is_renderer_initiated=*/false)); |
| iframe_observer.WaitForNavigationFinished(); |
| EXPECT_EQ(child_frame->GetLastCommittedURL(), kNewIframeUrl); |
| } |
| |
| // Allow the navigation to complete to activation, the iframe navigation |
| // should be able to finish. Ensure the navigation completes in the iframe. |
| { |
| condition.CallResumeClosure(); |
| prerender_observer.WaitForActivation(); |
| } |
| } |
| |
| class ScopedDataSaverTestContentBrowserClient |
| : public TestContentBrowserClient { |
| public: |
| ScopedDataSaverTestContentBrowserClient() |
| : old_client(SetBrowserClientForTesting(this)) {} |
| ~ScopedDataSaverTestContentBrowserClient() override { |
| SetBrowserClientForTesting(old_client); |
| } |
| |
| // ContentBrowserClient overrides: |
| bool IsDataSaverEnabled(BrowserContext* context) override { return true; } |
| |
| void OverrideWebkitPrefs(WebContents* web_contents, |
| blink::web_pref::WebPreferences* prefs) override { |
| prefs->data_saver_enabled = true; |
| } |
| |
| private: |
| ContentBrowserClient* old_client; |
| }; |
| |
| // Tests that the data saver doesn't prevent image load in a prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DataSaver) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/image.html"); |
| const GURL kImageUrl = GetUrl("/blank.jpg"); |
| |
| // Enable data saver. |
| ScopedDataSaverTestContentBrowserClient scoped_content_browser_client; |
| shell()->web_contents()->OnWebPreferencesChanged(); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A request for the image in the prerendered page shouldn't be prevented by |
| // the data saver. |
| EXPECT_EQ(GetRequestCount(kImageUrl), 1); |
| } |
| |
| // Tests that loading=lazy doesn't prevent image load in a prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LazyLoading) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/image_loading_lazy.html"); |
| const GURL kImageUrl = GetUrl("/blank.jpg"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A request for the image in the prerendered page shouldn't be prevented by |
| // loading=lazy. |
| EXPECT_EQ(GetRequestCount(kImageUrl), 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| SessionStorageAfterBackNavigation_NoProcessReuse) { |
| // When BackForwardCache feature is enabled, this test doesn't work, because |
| // this test is checking the behavior of a new renderer process which is |
| // created for a back forward navigation from a prerendered page. |
| DisableBackForwardCacheForTesting(shell()->web_contents(), |
| BackForwardCache::TEST_ASSUMES_NO_CACHING); |
| |
| const GURL kInitialUrl = GetUrl("/prerender/session_storage.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/session_storage.html?prerendering="); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| std::unique_ptr<RenderProcessHostWatcher> process_host_watcher = |
| std::make_unique<RenderProcessHostWatcher>( |
| current_frame_host()->GetProcess(), |
| RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| |
| AddPrerender(kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| EXPECT_EQ("initial", EvalJs(current_frame_host(), |
| "window.sessionKeysInPrerenderingchange") |
| .ExtractString()); |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| |
| // Speculative fix for the test flakiness (crbug.com/1216038), which may be |
| // caused by the delayed async IPC of Session Storage (StorageArea.Put()). |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| "new Promise(resolve => requestIdleCallback(resolve));")); |
| |
| // Make sure that the initial renderer process is destroyed. So that the |
| // initial renderer process will not be reused after the back forward |
| // navigation below. |
| process_host_watcher->Wait(); |
| |
| // Navigate back to the initial page. |
| content::TestNavigationObserver observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| observer.Wait(); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl); |
| |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| SessionStorageAfterBackNavigation_KeepInitialProcess) { |
| const GURL kInitialUrl = GetUrl("/prerender/session_storage.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/session_storage.html?prerendering="); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| RenderProcessHostImpl* initial_process_host = |
| static_cast<RenderProcessHostImpl*>(current_frame_host()->GetProcess()); |
| // Increment the keep alive ref count of the renderer process to keep it alive |
| // so it is reused on the back navigation below. The test checks that the |
| // session storage state changed in the activated page is correctly propagated |
| // after a back navigation that uses an existing renderer process. |
| initial_process_host->IncrementKeepAliveRefCount(); |
| |
| AddPrerender(kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| EXPECT_EQ("initial", EvalJs(current_frame_host(), |
| "window.sessionKeysInPrerenderingchange") |
| .ExtractString()); |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| |
| // Speculative fix for the test flakiness (crbug.com/1216038), which may be |
| // caused by the delayed async IPC of Session Storage (StorageArea.Put()). |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| "new Promise(resolve => requestIdleCallback(resolve));")); |
| |
| // Navigate back to the initial page. |
| content::TestNavigationObserver observer(shell()->web_contents()); |
| shell()->GoBackOrForward(-1); |
| observer.Wait(); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl); |
| |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| } |
| |
| // Test if the host is abandoned when the renderer page crashes. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AbandonIfRendererProcessCrashes) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Crash the relevant renderer. |
| { |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| RenderProcessHost* process = |
| GetPrerenderedMainFrameHost(host_id)->GetProcess(); |
| ScopedAllowRendererCrashes allow_renderer_crashes(process); |
| #if defined(OS_ANDROID) && defined(ARCH_CPU_X86_FAMILY) |
| // On x86 and x86_64 Android, IMMEDIATE_CRASH() macro used in |
| // ChildProcessHostImpl::CrashHungProcess() called from ForceCrash() |
| // does not seem to work as expected. (See https://crbug.com/1211655) |
| // We have no other ForceCrash() call sites on other than Linux and CrOS. |
| // In this test, we call Shutdown(content::RESULT_CODE_HUNG) instead as |
| // HungRenderDialogView does so on other platforms than Linux and CrOS. |
| process->Shutdown(content::RESULT_CODE_HUNG); |
| #else |
| // On Android, ForceCrash results in TERMINATION_STATUS_NORMAL_TERMINATION. |
| // On other platforms, it does in TERMINATION_STATUS_PROCESS_CRASHED. |
| process->ForceCrash(); |
| #endif |
| host_observer.WaitForDestroyed(); |
| } |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| #if defined(OS_ANDROID) |
| PrerenderHost::FinalStatus::kRendererProcessKilled, 1); |
| #else |
| PrerenderHost::FinalStatus::kRendererProcessCrashed, 1); |
| #endif // defined(OS_ANDROID) |
| } |
| |
| // Test if the host is abandoned when the renderer page is killed. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AbandonIfRendererProcessIsKilled) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| AddPrerender(kPrerenderingUrl); |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Shut down the relevant renderer. |
| { |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| RenderProcessHost* process = |
| GetPrerenderedMainFrameHost(host_id)->GetProcess(); |
| ScopedAllowRendererCrashes allow_renderer_crashes(process); |
| EXPECT_TRUE(process->Shutdown(0)); |
| host_observer.WaitForDestroyed(); |
| } |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kRendererProcessKilled, 1); |
| } |
| |
| class PrerenderBackForwardCacheBrowserTest : public PrerenderBrowserTest { |
| public: |
| PrerenderBackForwardCacheBrowserTest() { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kBackForwardCache, {{"enable_same_site", "true"}}}, |
| {kBackForwardCacheNoTimeEviction, {}}}, |
| // Allow BackForwardCache for all devices regardless of their memory. |
| {features::kBackForwardCacheMemoryControls}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBackForwardCacheBrowserTest, |
| SessionStorageAfterBackNavigation) { |
| const GURL kInitialUrl = GetUrl("/prerender/session_storage.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/session_storage.html?prerendering="); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| RenderFrameHostWrapper main_frame(shell()->web_contents()->GetMainFrame()); |
| |
| AddPrerender(kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| EXPECT_EQ("initial", EvalJs(current_frame_host(), |
| "window.sessionKeysInPrerenderingchange") |
| .ExtractString()); |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| |
| // Speculative fix for the test flakiness (crbug.com/1216038), which may be |
| // caused by the delayed async IPC of Session Storage (StorageArea.Put()). |
| EXPECT_TRUE(ExecJs(shell()->web_contents(), |
| "new Promise(resolve => requestIdleCallback(resolve));")); |
| |
| // Navigate back to the initial page. |
| shell()->GoBackOrForward(-1); |
| WaitForLoadStop(shell()->web_contents()); |
| |
| // Expect the navigation to be served from the back-forward cache to verify |
| // the test is testing what is intended. |
| ASSERT_EQ(shell()->web_contents()->GetMainFrame(), main_frame.get()); |
| |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| } |
| |
| #if !defined(OS_ANDROID) |
| // StorageServiceOutOfProcess is not implemented on Android. Also as commented |
| // below, test_api->CrashNow() won't work on x86 and x86_64 Android. |
| |
| class PrerenderRestartStorageServiceBrowserTest : public PrerenderBrowserTest { |
| public: |
| PrerenderRestartStorageServiceBrowserTest() { |
| // These tests only make sense when the service is running |
| // out-of-process. |
| feature_list_.InitAndEnableFeature(features::kStorageServiceOutOfProcess); |
| } |
| |
| protected: |
| void CrashStorageServiceAndWaitForRestart() { |
| mojo::Remote<storage::mojom::StorageService>& service = |
| StoragePartitionImpl::GetStorageServiceForTesting(); |
| base::RunLoop loop; |
| service.set_disconnect_handler(base::BindLambdaForTesting([&] { |
| loop.Quit(); |
| service.reset(); |
| })); |
| mojo::Remote<storage::mojom::TestApi> test_api; |
| StoragePartitionImpl::GetStorageServiceForTesting()->BindTestApi( |
| test_api.BindNewPipeAndPassReceiver().PassPipe()); |
| // On x86 and x86_64 Android, IMMEDIATE_CRASH() macro used in CrashNow() |
| // does not seem to work as expected. (See https://crbug.com/1211655) |
| test_api->CrashNow(); |
| loop.Run(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderRestartStorageServiceBrowserTest, |
| RestartStorageServiceBeforePrerendering) { |
| const GURL kInitialUrl = GetUrl("/prerender/session_storage.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/session_storage.html?prerendering="); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| CrashStorageServiceAndWaitForRestart(); |
| |
| EXPECT_EQ( |
| "initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| |
| AddPrerender(kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| EXPECT_EQ("initial", EvalJs(current_frame_host(), |
| "window.sessionKeysInPrerenderingchange") |
| .ExtractString()); |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderRestartStorageServiceBrowserTest, |
| RestartStorageServiceWhilePrerendering) { |
| const GURL kInitialUrl = GetUrl("/prerender/session_storage.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/session_storage.html?prerendering="); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| |
| CrashStorageServiceAndWaitForRestart(); |
| |
| EXPECT_EQ( |
| "initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| EXPECT_EQ( |
| "initial, prerendering", |
| EvalJs(GetPrerenderedMainFrameHost(host_id), "getSessionStorageKeys()") |
| .ExtractString()); |
| |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| EXPECT_EQ("initial", EvalJs(current_frame_host(), |
| "window.sessionKeysInPrerenderingchange") |
| .ExtractString()); |
| EXPECT_EQ( |
| "activated, initial", |
| EvalJs(current_frame_host(), "getSessionStorageKeys()").ExtractString()); |
| } |
| #endif |
| |
| class PrerenderWithProactiveBrowsingInstanceSwap : public PrerenderBrowserTest { |
| public: |
| PrerenderWithProactiveBrowsingInstanceSwap() { |
| feature_list_.InitWithFeaturesAndParameters( |
| /*enabled_features=*/{{features::kProactivelySwapBrowsingInstance, |
| {{"level", "SameSite"}}}}, |
| /*disabled_features=*/{}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Make sure that we can deal with the speculative RFH that is created during |
| // the activation navigation. |
| // TODO(https://crbug.com/1190197): We should try to avoid creating the |
| // speculative RFH (redirects allowing). Once that is done we should either |
| // change this test (if redirects allowed) or remove it completely. |
| IN_PROC_BROWSER_TEST_F(PrerenderWithProactiveBrowsingInstanceSwap, |
| SpeculationRulesScript) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A prerender host for the URL should be registered. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Activate the prerendered page. |
| // The test passes if we don't crash while cleaning up speculative render |
| // frame host. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // The prerender host should be consumed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Activating the prerendered page should not issue a request. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| |
| class PrerenderWithBackForwardCacheBrowserTest |
| : public PrerenderBrowserTest, |
| public testing::WithParamInterface<BackForwardCacheType> { |
| public: |
| PrerenderWithBackForwardCacheBrowserTest() { |
| // Set up the common params for the BFCache. |
| base::FieldTrialParams feature_params; |
| feature_params["TimeToLiveInBackForwardCacheInSeconds"] = "3600"; |
| |
| // Allow the BFCache for all devices regardless of their memory. |
| std::vector<base::Feature> disabled_features{ |
| features::kBackForwardCacheMemoryControls}; |
| |
| switch (GetParam()) { |
| case BackForwardCacheType::kDisabled: |
| feature_list_.InitAndDisableFeature(features::kBackForwardCache); |
| break; |
| case BackForwardCacheType::kEnabled: |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kBackForwardCache, feature_params}}, disabled_features); |
| break; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| feature_params["enable_same_site"] = "true"; |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kBackForwardCache, feature_params}}, disabled_features); |
| break; |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| PrerenderWithBackForwardCacheBrowserTest, |
| testing::Values(BackForwardCacheType::kDisabled, |
| BackForwardCacheType::kEnabled, |
| BackForwardCacheType::kEnabledWithSameSite), |
| ToString); |
| |
| // Tests that history navigation works after activation. This runs with variaous |
| // BFCache configurations that may modify behavior of history navigation. |
| // This is a regression test for https://crbug.com/1201914. |
| IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest, |
| HistoryNavigationAfterActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| RenderFrameHostImpl* initial_frame_host = current_frame_host(); |
| blink::LocalFrameToken initial_frame_token = |
| initial_frame_host->GetFrameToken(); |
| |
| // When the BFCache is disabled, activation will destroy the initial frame |
| // host. This observer will be used for confirming it. |
| RenderFrameDeletedObserver delete_observer(initial_frame_host); |
| |
| // Make and activate a prerendered page. |
| AddPrerender(kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| // Check if the initial page is in the BFCache. |
| switch (GetParam()) { |
| case BackForwardCacheType::kDisabled: |
| EXPECT_NE(current_frame_host(), initial_frame_host); |
| // The initial frame host should be deleted after activation because it is |
| // not cached in the BFCache. |
| delete_observer.WaitUntilDeleted(); |
| break; |
| case BackForwardCacheType::kEnabled: |
| // Same-origin prerender activation should allow the initial page to be |
| // cached in the BFCache even if the BFCache for same-site (same-origin) |
| // is not enabled. This is because prerender activation always swaps |
| // BrowsingInstance and it makes the previous page cacheacble unlike |
| // regular same-origin navigation. |
| ASSERT_FALSE(IsSameSiteBackForwardCacheEnabled()); |
| EXPECT_TRUE(initial_frame_host->IsInBackForwardCache()); |
| break; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| // Same-origin prerender activation should allow the initial page to be |
| // cached in the BFCache. |
| ASSERT_TRUE(IsSameSiteBackForwardCacheEnabled()); |
| EXPECT_TRUE(initial_frame_host->IsInBackForwardCache()); |
| break; |
| } |
| |
| // Navigate back to the initial page. |
| content::TestNavigationObserver observer(web_contents()); |
| shell()->GoBackOrForward(-1); |
| observer.Wait(); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Check if the back navigation is served from the BFCache. |
| switch (GetParam()) { |
| case BackForwardCacheType::kDisabled: |
| // The frame host should be created again. |
| EXPECT_NE(current_frame_host()->GetFrameToken(), initial_frame_token); |
| break; |
| case BackForwardCacheType::kEnabled: |
| case BackForwardCacheType::kEnabledWithSameSite: |
| // The frame host should be restored. |
| EXPECT_EQ(current_frame_host()->GetFrameToken(), initial_frame_token); |
| EXPECT_FALSE(initial_frame_host->IsInBackForwardCache()); |
| break; |
| } |
| } |
| |
| class TestSpeculationHostDelegate final : public SpeculationHostDelegate { |
| public: |
| TestSpeculationHostDelegate() = default; |
| ~TestSpeculationHostDelegate() override = default; |
| |
| // Disallows copy and move operations. |
| TestSpeculationHostDelegate(const TestSpeculationHostDelegate&) = delete; |
| TestSpeculationHostDelegate& operator=(const TestSpeculationHostDelegate&) = |
| delete; |
| |
| // SpeculationRulesDelegate implementation. |
| void ProcessCandidates( |
| std::vector<blink::mojom::SpeculationCandidatePtr>& candidates) override { |
| EXPECT_FALSE(processed_); |
| processed_ = true; |
| if (waiting_for_processing_) { |
| // SpeculationHostImpl processes prerender candidates right after |
| // ProcessCandidates(). Run the quit closure asynchronously so that |
| // the closure can see results of processing the candidates. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, std::move(waiting_for_processing_)); |
| } |
| } |
| |
| base::WeakPtr<TestSpeculationHostDelegate> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void ResetProcessingState() { |
| processed_ = false; |
| waiting_for_processing_.Reset(); |
| } |
| |
| void WaitUntilCandidatesAreProcessed() { |
| if (processed_) |
| return; |
| base::RunLoop loop; |
| waiting_for_processing_ = loop.QuitClosure(); |
| loop.Run(); |
| } |
| |
| private: |
| base::OnceClosure waiting_for_processing_; |
| bool processed_ = false; |
| |
| base::WeakPtrFactory<TestSpeculationHostDelegate> weak_ptr_factory_{this}; |
| }; |
| |
| class ScopedSpeculationHostImplContentBrowserClient |
| : public TestContentBrowserClient { |
| public: |
| ScopedSpeculationHostImplContentBrowserClient() { |
| old_browser_client_ = SetBrowserClientForTesting(this); |
| } |
| |
| ~ScopedSpeculationHostImplContentBrowserClient() override { |
| EXPECT_EQ(this, SetBrowserClientForTesting(old_browser_client_)); |
| } |
| |
| std::unique_ptr<SpeculationHostDelegate> CreateSpeculationHostDelegate( |
| RenderFrameHost& render_frame_host) override { |
| auto delegate = std::make_unique<TestSpeculationHostDelegate>(); |
| speculation_host_delegate_ = delegate->GetWeakPtr(); |
| if (waiting_for_created_) |
| std::move(waiting_for_created_).Run(); |
| return delegate; |
| } |
| |
| void WaitForDelegateCreation() { |
| if (speculation_host_delegate_) |
| return; |
| base::RunLoop loop; |
| waiting_for_created_ = loop.QuitClosure(); |
| loop.Run(); |
| } |
| |
| base::WeakPtr<TestSpeculationHostDelegate> speculation_host_delegate() { |
| return speculation_host_delegate_; |
| } |
| |
| private: |
| ContentBrowserClient* old_browser_client_; |
| base::OnceClosure waiting_for_created_; |
| base::WeakPtr<TestSpeculationHostDelegate> speculation_host_delegate_; |
| }; |
| |
| // Tests that SpeculationHostImpl only triggers prerendering for the first |
| // prerender speculation rule it receives. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AddSpeculationRulesMultipleTimes) { |
| ScopedSpeculationHostImplContentBrowserClient test_browser_client; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kFirstPrerenderingUrl = GetUrl("/empty.html?prerender1"); |
| const GURL kSecondPrerenderingUrl = GetUrl("/empty.html?prerender2"); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| const int host_id = AddPrerender(kFirstPrerenderingUrl); |
| |
| // The first prerender rule should be applied, so the prerender host for |
| // kFirstPrerenderingUrl should be registered. |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| base::WeakPtr<TestSpeculationHostDelegate> delegate = |
| test_browser_client.speculation_host_delegate(); |
| ASSERT_TRUE(delegate); |
| delegate->ResetProcessingState(); |
| |
| // Add a new speculation rule. Since SpeculationHostImpl limits the number of |
| // prerenders to one, this rule should not be applied. |
| AddPrerenderAsync(kSecondPrerenderingUrl); |
| delegate->WaitUntilCandidatesAreProcessed(); |
| |
| // The kSecondPrerenderingUrl request should not be issued. |
| EXPECT_EQ(GetRequestCount(kSecondPrerenderingUrl), 0); |
| EXPECT_FALSE(HasHostForUrl(kSecondPrerenderingUrl)); |
| EXPECT_TRUE(HasHostForUrl(kFirstPrerenderingUrl)); |
| } |
| |
| // Tests that speculationrules cannot trigger cross-origin prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CrossOriginSpeculationRules) { |
| ScopedSpeculationHostImplContentBrowserClient test_browser_client; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kFirstPrerenderingUrlCrossOrigin = |
| GetCrossOriginUrl("/empty.html?crossorigin"); |
| const GURL kSecondPrerenderingUrlSameOrigin = |
| GetUrl("/empty.html?sameorigin"); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Add a cross-origin prerender speculation rule, and it should be ignored. |
| AddPrerenderAsync(kFirstPrerenderingUrlCrossOrigin); |
| test_browser_client.WaitForDelegateCreation(); |
| base::WeakPtr<TestSpeculationHostDelegate> delegate = |
| test_browser_client.speculation_host_delegate(); |
| ASSERT_TRUE(delegate); |
| delegate->WaitUntilCandidatesAreProcessed(); |
| |
| // Cross-origin prerender candidates should be ignored. |
| EXPECT_FALSE(HasHostForUrl(kFirstPrerenderingUrlCrossOrigin)); |
| delegate->ResetProcessingState(); |
| |
| // Since the first one was ignored, the second one should be processed by |
| // SpeculationHostImpl and trigger prerendering. |
| const int host_id = AddPrerender(kSecondPrerenderingUrlSameOrigin); |
| EXPECT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| } |
| |
| } // namespace |
| } // namespace content |