| // 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 <cstdint> |
| |
| #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/ignore_result.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/metrics_hashes.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 "build/chromeos_buildflags.h" |
| #include "components/services/storage/public/mojom/storage_service.mojom.h" |
| #include "components/services/storage/public/mojom/test_api.test-mojom.h" |
| #include "components/ukm/test_ukm_recorder.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/disallow_activation_reason.h" |
| #include "content/public/browser/document_user_data.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/navigation_type.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/render_process_host.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/background_color_change_waiter.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/mock_web_contents_observer.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/public/test/theme_change_waiter.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/render_document_feature.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/default_handlers.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 "services/metrics/public/cpp/ukm_builders.h" |
| #include "services/network/public/cpp/web_sandbox_flags.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/common/loader/loader_constants.h" |
| #include "third_party/blink/public/mojom/browser_interface_broker.mojom.h" |
| #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h" |
| #include "third_party/blink/public/mojom/page/display_cutout.mojom.h" |
| #include "ui/shell_dialogs/select_file_dialog.h" |
| #include "ui/shell_dialogs/select_file_dialog_factory.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| #include "content/common/pepper_plugin.mojom.h" |
| #endif // BUILDFLAG(ENABLE_PLUGINS) |
| |
| using ::testing::Exactly; |
| |
| 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"; |
| } |
| } |
| |
| int32_t InterfaceNameHasher(const std::string& interface_name) { |
| return static_cast<int32_t>(base::HashMetricNameAs32Bits(interface_name)); |
| } |
| |
| RenderFrameHost* FindRenderFrameHost(Page& page, const GURL& url) { |
| return content::FrameMatchingPredicate( |
| page, base::BindRepeating(&content::FrameHasSourceUrl, url)); |
| } |
| |
| // Example class which inherits the DocumentUserData, all the data is |
| // associated to the lifetime of the document. |
| class DocumentData : public DocumentUserData<DocumentData> { |
| public: |
| ~DocumentData() override = default; |
| |
| base::WeakPtr<DocumentData> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| explicit DocumentData(RenderFrameHost* render_frame_host) |
| : DocumentUserData<DocumentData>(render_frame_host) {} |
| |
| friend class content::DocumentUserData<DocumentData>; |
| |
| base::WeakPtrFactory<DocumentData> weak_ptr_factory_{this}; |
| |
| DOCUMENT_USER_DATA_KEY_DECL(); |
| }; |
| |
| DOCUMENT_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 SetUp() override { |
| prerender_helper_->SetUp(&ssl_server_); |
| ContentBrowserTest::SetUp(); |
| } |
| |
| 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); |
| 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); |
| } |
| |
| void ResetSSLConfig( |
| net::test_server::EmbeddedTestServer::ServerCertificate cert, |
| const net::SSLServerConfig& ssl_config) { |
| ASSERT_TRUE(ssl_server_.ResetSSLConfig(cert, ssl_config)); |
| } |
| |
| int GetRequestCount(const GURL& url) { |
| return prerender_helper_->GetRequestCount(url); |
| } |
| |
| net::test_server::HttpRequest::HeaderMap GetRequestHeaders(const GURL& url) { |
| return prerender_helper_->GetRequestHeaders(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()->GetLastCommittedURL(), 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. |
| navigated_render_frame_host->ForEachRenderFrameHost( |
| base::BindRepeating([](content::RenderFrameHostImpl* rfhi) { |
| // 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()); |
| |
| // Check that each document can use a deferred Mojo interface. Choose |
| // WebLocks API as the feature is enabled by default and does not |
| // require permission. |
| const std::string kMojoScript = R"( |
| navigator.locks.request('hi', {mode:'shared'}, () => {}); |
| )"; |
| EXPECT_TRUE(ExecJs(rfhi, kMojoScript)); |
| })); |
| } |
| |
| 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); |
| // The viewport meta tag is only enabled on Android. |
| #if defined(OS_ANDROID) |
| command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, |
| "DisplayCutoutAPI"); |
| #endif |
| } |
| |
| void TestNavigationHistory(const GURL& expected_current_url, |
| int expected_history_index, |
| int expected_history_length) { |
| ASSERT_EQ(expected_current_url, web_contents()->GetLastCommittedURL()); |
| EXPECT_EQ(expected_history_index, |
| web_contents()->GetController().GetCurrentEntryIndex()); |
| EXPECT_EQ(expected_history_length, |
| web_contents()->GetController().GetEntryCount()); |
| EXPECT_EQ(expected_history_length, |
| EvalJs(web_contents(), "history.length")); |
| } |
| |
| void AssertPrerenderHistoryLength(int host_id, |
| RenderFrameHost* prerender_frame_host) { |
| EXPECT_EQ(1, FrameTreeNode::GloballyFindByID(host_id) |
| ->frame_tree() |
| ->controller() |
| .GetEntryCount()); |
| ASSERT_EQ(1, EvalJs(prerender_frame_host, "history.length")); |
| } |
| |
| void GoBack() { |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| } |
| |
| void GoForward() { |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| } |
| |
| protected: |
| net::test_server::EmbeddedTestServer& ssl_server() { return ssl_server_; } |
| |
| private: |
| net::test_server::EmbeddedTestServer ssl_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| |
| std::unique_ptr<test::PrerenderTestHelper> prerender_helper_; |
| }; |
| } // namespace |
| |
| // 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| 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"); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // 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)); |
| } |
| |
| // Tests that clicking a link can activate a prerender. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateOnLinkClick) { |
| const GURL kInitialUrl = GetUrl("/simple_links.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title2.html"); |
| |
| // Navigate to an initial page which has a link to `kPrerenderingUrl`. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver prerender_observer(*web_contents(), |
| prerender_host_id); |
| |
| // Click the link. It should activate the prerendered page. |
| TestNavigationObserver nav_observer(web_contents()); |
| const std::string kLinkClickScript = R"( |
| const link = document.querySelector('#same_site_link'); |
| link.click(); |
| )"; |
| EXPECT_TRUE(ExecJs(web_contents(), kLinkClickScript)); |
| nav_observer.WaitForNavigationFinished(); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| EXPECT_TRUE(prerender_observer.was_activated()); |
| } |
| |
| 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl` and check if `X-Foo` header is |
| // observed. |
| NavigationHandleObserver observer1(web_contents(), kPrerenderingUrl); |
| AddPrerender(kPrerenderingUrl); |
| 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 an empty response with 404 status. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelledOnEmptyBody404) { |
| 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.SpeculationRule", |
| PrerenderHost::FinalStatus::kNavigationBadHttpStatus, 1); |
| } |
| |
| // Tests that prerendering is cancelled if a network request for the |
| // navigation results in an non-empty response with 404 status. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| PrerenderCancelledOnNonEmptyBody404) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page404.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Add prerendering to the 404 error page, then check that it got cancelled. |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), |
| kPrerenderingUrl); |
| AddPrerenderAsync(kPrerenderingUrl); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kNavigationBadHttpStatus, 1); |
| } |
| |
| // Tests that prerendering is cancelled if a network request for the |
| // navigation results in an non-empty response with 500 status. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderCancelledOn500Page) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page500.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Add prerendering to the 500 error page, then check that it got cancelled. |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), |
| kPrerenderingUrl); |
| AddPrerenderAsync(kPrerenderingUrl); |
| host_observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kNavigationBadHttpStatus, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequested) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| const GURL kPrerenderingUrl = GetUrl("/auth-basic"); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), |
| kPrerenderingUrl); |
| AddPrerenderAsync(kPrerenderingUrl); |
| |
| // The prerender should be destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Cancellation must have occurred due to authentication request. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kLoginAuthRequested, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequestedSubframe) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| WaitForPrerenderLoadCompletion(kPrerenderingUrl); |
| |
| // Fetch a subframe that requires authentication. |
| const GURL kAuthIFrameUrl = GetUrl("/auth-basic"); |
| RenderFrameHost* prerender_rfh = GetPrerenderedMainFrameHost(host_id); |
| ignore_result(ExecJs(prerender_rfh, |
| "var i = document.createElement('iframe'); i.src = '" + |
| kAuthIFrameUrl.spec() + |
| "'; document.body.appendChild(i);")); |
| |
| // The prerender should be destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Cancellation must have occurred due to authentication request. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kLoginAuthRequested, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnAuthRequestedSubResource) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| WaitForPrerenderLoadCompletion(kPrerenderingUrl); |
| |
| ASSERT_NE(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Fetch a subresrouce. |
| std::string fetch_subresource_script = R"( |
| const imgElement = document.createElement('img'); |
| imgElement.src = '/auth-basic/favicon.gif'; |
| document.body.appendChild(imgElement); |
| )"; |
| ignore_result( |
| ExecJs(GetPrerenderedMainFrameHost(host_id), fetch_subresource_script)); |
| |
| // The prerender should be destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Cancellation must have occurred due to authentication request. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kLoginAuthRequested, 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)); |
| 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), 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()); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), |
| kPrerenderingUrl); |
| AddPrerenderAsync(kPrerenderingUrl); |
| 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.SpeculationRule", |
| 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"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // 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); |
| } |
| |
| // Make sure that the prerendering browsing context has an isolated trivial |
| // session history. history.length should be limited to 1 in the prerendering |
| // browsing context. |
| // |
| // Explainer: |
| // https://github.com/jeremyroman/alternate-loading-modes/blob/main/browsing-context.md#session-history |
| IN_PROC_BROWSER_TEST_F( |
| PrerenderBrowserTest, |
| SessionHistoryShouldHaveSingleNavigationEntryInPrerender) { |
| // Navigate the primary main frame to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| TestNavigationHistory(kInitialUrl, /*expected_history_index=*/0, |
| /*expected_history_length=*/1); |
| |
| // Navigate the primary main frame to another page so that the initiator |
| // page's `history.length` becomes 2. That helps us to distinguish the initial |
| // page's session history and the prerendering page's session history. This is |
| // not a robust way, but probably good enough in this test. |
| const GURL k2ndUrl = GetUrl("/empty.html?2nd"); |
| ASSERT_TRUE(NavigateToURL(shell(), k2ndUrl)); |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| |
| // From here, we perform several operations which usually append a new entry |
| // to the session history, however, all navigations within the prerendering |
| // browsing context should be done with replacement in the isolated session |
| // history. |
| // TODO: Factor out this test into several tests. This test is getting large. |
| |
| // Perform history.replaceState() in the prerendered page. Note |
| // history.replaceState() doesn't append a new entry anyway. The purpose of |
| // testing history.replaceState() here is just for the comparison; pushState() |
| // vs replaceState(). Both should have the same behavior in a prerendering |
| // browsing context. |
| { |
| FrameNavigateParamsCapturer capturer( |
| FrameTreeNode::From(prerender_frame_host)); |
| |
| ASSERT_EQ(nullptr, EvalJs(prerender_frame_host, |
| "history.replaceState('state1', null, null)")); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ("state1", EvalJs(prerender_frame_host, "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY, |
| capturer.navigation_type()); |
| EXPECT_TRUE(capturer.is_same_document()); |
| EXPECT_TRUE(capturer.did_replace_entry()); |
| } |
| |
| // Perform history.pushState() in the prerendered page. |
| { |
| FrameNavigateParamsCapturer capturer( |
| FrameTreeNode::From(prerender_frame_host)); |
| |
| ASSERT_EQ(nullptr, EvalJs(prerender_frame_host, |
| "history.pushState('state2', null, null)")); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ("state2", EvalJs(prerender_frame_host, "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY, |
| capturer.navigation_type()); |
| EXPECT_TRUE(capturer.is_same_document()); |
| EXPECT_TRUE(capturer.did_replace_entry()); |
| } |
| |
| // Do a fragment navigation in the prerendered main frame. |
| { |
| FrameNavigateParamsCapturer capturer( |
| FrameTreeNode::From(prerender_frame_host)); |
| |
| const GURL kPrerenderingAnchorUrl = GetUrl("/empty.html?prerender#anchor"); |
| NavigatePrerenderedPage(host_id, kPrerenderingAnchorUrl); |
| WaitForPrerenderLoadCompleted(host_id); |
| ASSERT_EQ(GetRequestCount(kPrerenderingAnchorUrl), 1); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| // history.state should be replaced with a fragment navigation. |
| EXPECT_EQ(nullptr, EvalJs(prerender_frame_host, "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY, |
| capturer.navigation_type()); |
| EXPECT_TRUE(capturer.is_same_document()); |
| EXPECT_TRUE(capturer.did_replace_entry()); |
| } |
| |
| // Add a same-origin iframe to the prerendered page and let it navigate to the |
| // different same-origin URL. |
| { |
| // Add an iframe. |
| const GURL kSameOriginSubframeUrl1 = |
| GetUrl("/empty.html?same_origin_iframe1"); |
| EXPECT_TRUE(AddTestUtilJS(prerender_frame_host)); |
| ASSERT_EQ("LOADED", |
| EvalJs(prerender_frame_host, |
| JsReplace("add_iframe($1)", kSameOriginSubframeUrl1))); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl1), 1); |
| |
| auto* child_frame = ChildFrameAt(prerender_frame_host, 0); |
| ASSERT_NE(nullptr, child_frame); |
| EXPECT_EQ(kSameOriginSubframeUrl1, child_frame->GetLastCommittedURL()); |
| |
| // Let the added iframe navigate to the different URL. |
| { |
| FrameNavigateParamsCapturer capturer(FrameTreeNode::From(child_frame)); |
| const GURL kSameOriginSubframeUrl2 = |
| GetUrl("/empty.html?same_origin_iframe2"); |
| ASSERT_EQ(kSameOriginSubframeUrl2, |
| EvalJs(child_frame, |
| JsReplace("location = $1", kSameOriginSubframeUrl2))); |
| capturer.Wait(); |
| |
| EXPECT_EQ(kSameOriginSubframeUrl2, child_frame->GetLastCommittedURL()); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl2), 1); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ(nullptr, EvalJs(prerender_frame_host, "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type()); |
| EXPECT_FALSE(capturer.is_same_document()); |
| EXPECT_TRUE(capturer.did_replace_entry()); |
| EXPECT_TRUE(capturer.is_renderer_initiated()); |
| } |
| |
| // Use WebContents::OpenURL() to let the added iframe navigate. |
| { |
| FrameNavigateParamsCapturer capturer(FrameTreeNode::From(child_frame)); |
| const GURL kSameOriginSubframeUrl3 = |
| GetUrl("/empty.html?same_origin_iframe3"); |
| shell()->web_contents()->OpenURL(OpenURLParams( |
| kSameOriginSubframeUrl3, Referrer(), |
| child_frame->GetFrameTreeNodeId(), WindowOpenDisposition::CURRENT_TAB, |
| ui::PAGE_TRANSITION_AUTO_SUBFRAME, |
| /*is_renderer_initiated=*/false)); |
| capturer.Wait(); |
| |
| EXPECT_EQ(kSameOriginSubframeUrl3, child_frame->GetLastCommittedURL()); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl3), 1); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ(nullptr, EvalJs(prerender_frame_host, "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_AUTO_SUBFRAME, capturer.navigation_type()); |
| EXPECT_FALSE(capturer.is_same_document()); |
| EXPECT_TRUE(capturer.did_replace_entry()); |
| EXPECT_FALSE(capturer.is_renderer_initiated()); |
| } |
| } |
| |
| // Perform history.back() in the prerendered page, which should be no-op. |
| { |
| int current_request_count = GetRequestCount(k2ndUrl); |
| ASSERT_EQ(nullptr, EvalJs(prerender_frame_host, "history.back()")); |
| // Make sure that loading is not happening. |
| EXPECT_FALSE( |
| FrameTreeNode::GloballyFindByID(host_id)->frame_tree()->IsLoading()); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ(nullptr, EvalJs(prerender_frame_host, "history.state")); |
| EXPECT_EQ(current_request_count, GetRequestCount(k2ndUrl)); |
| } |
| |
| // Perform history.forward() in the prerendered page, which should be no-op. |
| { |
| int current_request_count = GetRequestCount(k2ndUrl); |
| ASSERT_EQ(nullptr, EvalJs(prerender_frame_host, "history.forward()")); |
| // Make sure that loading is not happening. |
| EXPECT_FALSE( |
| FrameTreeNode::GloballyFindByID(host_id)->frame_tree()->IsLoading()); |
| |
| TestNavigationHistory(k2ndUrl, /*expected_history_index=*/1, |
| /*expected_history_length=*/2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ(nullptr, EvalJs(prerender_frame_host, "history.state")); |
| EXPECT_EQ(current_request_count, GetRequestCount(k2ndUrl)); |
| } |
| } |
| |
| // Make sure that activation appends the prerendering page's single navigation |
| // entry to the initiator page's joint session history. We can go back or |
| // forward after activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SessionHistoryAfterActivation) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| TestNavigationHistory(kInitialUrl, 0, 1); |
| |
| // Navigate to another page. |
| const GURL k2ndUrl = GetUrl("/empty.html?2nd"); |
| ASSERT_TRUE(NavigateToURL(shell(), k2ndUrl)); |
| ASSERT_EQ(GetRequestCount(k2ndUrl), 1); |
| TestNavigationHistory(k2ndUrl, 1, 2); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| TestNavigationHistory(k2ndUrl, 1, 2); |
| |
| // Call history.pushState(...) in prerendering. |
| ASSERT_EQ(nullptr, EvalJs(prerender_frame_host, |
| "history.pushState('teststate', null, null)")); |
| TestNavigationHistory(k2ndUrl, 1, 2); |
| AssertPrerenderHistoryLength(host_id, prerender_frame_host); |
| EXPECT_EQ("teststate", EvalJs(prerender_frame_host, "history.state")); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| // The joint session history becomes [initial, 2nd, <prerender>]. |
| TestNavigationHistory(kPrerenderingUrl, 2, 3); |
| EXPECT_EQ("teststate", EvalJs(web_contents(), "history.state")); |
| |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| // Go Back. |
| { |
| FrameNavigateParamsCapturer capturer(root); |
| GoBack(); |
| // The joint session history becomes [initial, <2nd>, prerender]. |
| TestNavigationHistory(k2ndUrl, 1, 3); |
| EXPECT_EQ(nullptr, EvalJs(web_contents(), "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY, |
| capturer.navigation_type()); |
| EXPECT_FALSE(capturer.is_same_document()); |
| } |
| |
| // Go Forward. |
| { |
| FrameNavigateParamsCapturer capturer(root); |
| GoForward(); |
| // The joint session history becomes [initial, 2nd, <prerender>]. |
| TestNavigationHistory(kPrerenderingUrl, 2, 3); |
| EXPECT_EQ("teststate", EvalJs(web_contents(), "history.state")); |
| |
| EXPECT_EQ(NAVIGATION_TYPE_MAIN_FRAME_EXISTING_ENTRY, |
| capturer.navigation_type()); |
| EXPECT_FALSE(capturer.is_same_document()); |
| } |
| } |
| |
| // 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"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| 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()->GetLastCommittedURL(), 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->GetPage(), 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->GetPage(), 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"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| 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()->GetLastCommittedURL(), 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->GetPage(), 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. |
| 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.SpeculationRule", |
| PrerenderHost::FinalStatus::kMainFrameNavigation, 1); |
| } |
| |
| // Regression test for https://crbug.com/1198051 |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MainFrameFragmentNavigation) { |
| 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| WaitForPrerenderLoadCompleted(host_id); |
| |
| // Do a fragment 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 fragment |
| // 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"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // 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()->GetLastCommittedURL(), 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. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, 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)); |
| |
| 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), kInitialUrl); |
| AddPrerender(kPrerenderingUrl); |
| } |
| |
| 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()->GetLastCommittedURL(), 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(); |
| |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| 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(); |
| |
| 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)); |
| |
| EXPECT_EQ(nullptr, initiator_render_frame_host->GetParentOrOuterDocument()); |
| EXPECT_EQ(nullptr, prerendered_render_frame_host->GetParentOrOuterDocument()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| rfh_sub_1->GetParentOrOuterDocument()); |
| EXPECT_EQ(rfh_sub_1, rfh_sub_1_1->GetParentOrOuterDocument()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| rfh_sub_2->GetParentOrOuterDocument()); |
| EXPECT_EQ(initiator_render_frame_host, |
| initiator_render_frame_host->GetOutermostMainFrame()); |
| EXPECT_EQ(initiator_render_frame_host, |
| initiator_render_frame_host->GetOutermostMainFrameOrEmbedder()); |
| // The outermost document of a prerendered page is the prerendered main |
| // RenderFrameHost, not the primary main RenderFrameHost. |
| EXPECT_EQ(prerendered_render_frame_host, |
| prerendered_render_frame_host->GetOutermostMainFrame()); |
| EXPECT_EQ(prerendered_render_frame_host, rfh_sub_1->GetOutermostMainFrame()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| rfh_sub_1_1->GetOutermostMainFrame()); |
| EXPECT_EQ(prerendered_render_frame_host, rfh_sub_2->GetOutermostMainFrame()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| prerendered_render_frame_host->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| rfh_sub_1->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| rfh_sub_1_1->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(prerendered_render_frame_host, |
| rfh_sub_2->GetOutermostMainFrameOrEmbedder()); |
| |
| // WebContentsImpl::ForEachFrameTree should include prerenders. |
| bool visited_prerender_frame_tree = false; |
| web_contents_impl()->ForEachFrameTree( |
| base::BindLambdaForTesting([&](FrameTree* frame_tree) { |
| if (frame_tree == prerendered_render_frame_host->frame_tree()) { |
| visited_prerender_frame_tree = true; |
| } |
| })); |
| EXPECT_TRUE(visited_prerender_frame_tree); |
| } |
| |
| // Tests that a prerendering page cannot change the visible URL of the |
| // corresponding WebContentsImpl instance before activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TabVisibleURL) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| ASSERT_EQ(shell()->web_contents()->GetVisibleURL(), kInitialUrl); |
| AddPrerender(kPrerenderingUrl); |
| |
| // The visible URL should not be modified by the prerendering page. |
| EXPECT_EQ(shell()->web_contents()->GetVisibleURL(), kInitialUrl); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // The visible URL should be updated after activation. |
| EXPECT_EQ(shell()->web_contents()->GetVisibleURL(), kPrerenderingUrl); |
| } |
| |
| // Tests that prerendering will be cancelled if a prerendering page wants to set |
| // a WebContents-level preferred size. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CancelOnPreferredSizeChanged) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| int host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| |
| // Enable PreferredSize mode in the prerendering page. Usually this mode is |
| // enabled by extentsions; here we enable it manually. Enabling this mode |
| // makes renderers ask the browser to update WebContents-level preferred size, |
| // which leads to the cancellation of prerendering. |
| RenderFrameHostImpl* prerender_main_frame = |
| GetPrerenderedMainFrameHost(host_id); |
| prerender_main_frame->GetRenderViewHost()->EnablePreferredSizeMode(); |
| |
| host_observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kDestroyed, 1); |
| } |
| |
| // Tests that prerendering cannot request the browser to create a popup widget. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NoPopupWidget) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostWrapper prerender_main_frame( |
| GetPrerenderedMainFrameHost(host_id)); |
| |
| std::string create_element_script = R"( |
| const widgetElement = document.createElement('input'); |
| widgetElement.type = 'color'; |
| widgetElement.id = 'chooser'; |
| widgetElement.value = '#000000'; |
| document.body.appendChild(widgetElement); |
| )"; |
| |
| EXPECT_TRUE(ExecJs(prerender_main_frame.get(), create_element_script, |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| |
| std::string click_element_script = R"( |
| const element = document.getElementById('chooser'); |
| element.click(); |
| )"; |
| |
| // It should be ignored because prerendering page do not have user gestures. |
| EXPECT_TRUE(ExecJs(prerender_main_frame.get(), click_element_script)); |
| |
| // Give the test a chance to fail if the click() is not ignored. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| } |
| |
| 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.SetNonAssociatedPolicy<mojom::TestInterfaceForGrant>( |
| MojoBinderNonAssociatedPolicy::kGrant); |
| policy_map.SetNonAssociatedPolicy<mojom::TestInterfaceForCancel>( |
| MojoBinderNonAssociatedPolicy::kCancel); |
| policy_map.SetNonAssociatedPolicy<mojom::TestInterfaceForUnexpected>( |
| MojoBinderNonAssociatedPolicy::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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start a prerender. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| std::vector<RenderFrameHost*> frames = |
| CollectAllRenderFrameHosts(prerendered_render_frame_host); |
| |
| // 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start a prerender. |
| 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.SpeculationRule", |
| 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); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledUnknownInterface", |
| InterfaceNameHasher(mojom::TestInterfaceForCancel::Name_), 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start a prerender. |
| 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.SpeculationRule", |
| 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); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledUnknownInterface", |
| InterfaceNameHasher(mojom::TestInterfaceForCancel::Name_), 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()->GetLastCommittedURL(), 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. |
| 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); |
| } |
| |
| enum class SSLPrerenderTestErrorBlockType { kClientCertRequested, kCertError }; |
| |
| std::string SSLPrerenderTestErrorBlockTypeToString( |
| const testing::TestParamInfo<SSLPrerenderTestErrorBlockType>& info) { |
| switch (info.param) { |
| case SSLPrerenderTestErrorBlockType::kClientCertRequested: |
| return "ClientCertRequested"; |
| case SSLPrerenderTestErrorBlockType::kCertError: |
| return "CertError"; |
| } |
| } |
| |
| class SSLPrerenderBrowserTest |
| : public testing::WithParamInterface<SSLPrerenderTestErrorBlockType>, |
| public PrerenderBrowserTest { |
| protected: |
| void RequireClientCertsOrSendExpiredCerts() { |
| net::SSLServerConfig ssl_config; |
| switch (GetParam()) { |
| case SSLPrerenderTestErrorBlockType::kClientCertRequested: |
| ssl_config.client_cert_type = |
| net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT; |
| ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_TEST_NAMES, |
| ssl_config); |
| break; |
| case SSLPrerenderTestErrorBlockType::kCertError: |
| ResetSSLConfig(net::test_server::EmbeddedTestServer::CERT_EXPIRED, |
| ssl_config); |
| break; |
| } |
| } |
| PrerenderHost::FinalStatus GetExpectedFinalStatus() { |
| switch (GetParam()) { |
| case SSLPrerenderTestErrorBlockType::kClientCertRequested: |
| return PrerenderHost::FinalStatus::kClientCertRequested; |
| case SSLPrerenderTestErrorBlockType::kCertError: |
| return PrerenderHost::FinalStatus::kSslCertificateError; |
| } |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SSLPrerenderBrowserTest, |
| testing::Values(SSLPrerenderTestErrorBlockType::kClientCertRequested, |
| SSLPrerenderTestErrorBlockType::kCertError), |
| SSLPrerenderTestErrorBlockTypeToString); |
| |
| // For a prerendering navigation request, if the server requires a client |
| // certificate or responds to the request with an invalid certificate, the |
| // prernedering should be canceled. |
| IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest, |
| CertificateValidation_Navigation) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Reset the server's config. |
| RequireClientCertsOrSendExpiredCerts(); |
| |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl); |
| prerender_helper()->AddPrerenderAsync(kPrerenderingUrl); |
| |
| // The prerender should be destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| GetExpectedFinalStatus(), 1); |
| } |
| |
| // For a prerendering subresource request, if the server requires a client |
| // certificate or responds to the request with an invalid certificate, the |
| // prernedering should be canceled. |
| IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest, |
| CertificateValidation_Subresource) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents()); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| int host_id = prerender_helper()->AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| |
| // Reset the server's config. |
| RequireClientCertsOrSendExpiredCerts(); |
| |
| ASSERT_NE(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| content::RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Fetch a subresrouce. |
| std::string fetch_subresource_script = R"( |
| const imgElement = document.createElement('img'); |
| imgElement.src = '/load_image/image.png'; |
| document.body.appendChild(imgElement); |
| )"; |
| ignore_result(ExecJs(prerender_helper()->GetPrerenderedMainFrameHost(host_id), |
| fetch_subresource_script)); |
| |
| // The prerender should be destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| GetExpectedFinalStatus(), 1); |
| } |
| |
| // Tests that prerendering will be cancelled if the server asks for client |
| // certificates or responds with an expired certificate, even if the main |
| // resource request is intercepted and sent by a service worker. |
| IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest, |
| CertificateValidation_SWMainResource) { |
| base::HistogramTester histogram_tester; |
| |
| // Register a service worker that intercepts resource requests. |
| const GURL kInitialUrl = GetUrl("/workers/service_worker_setup.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_EQ("ok", EvalJs(web_contents(), "setup();")); |
| |
| // Reset the server's config. |
| RequireClientCertsOrSendExpiredCerts(); |
| |
| const GURL kPrerenderingUrl = GetUrl("/workers/simple.html?intercept"); |
| test::PrerenderHostObserver host_observer(*web_contents(), kPrerenderingUrl); |
| prerender_helper()->AddPrerenderAsync(kPrerenderingUrl); |
| |
| // The prerender should be destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // For the kCertError case, StoragePartitionImpl cannot locate any |
| // WebContents. So, the certificate error does not cause any UI changes; it |
| // just cancels the url request, and leads to the cancellation of |
| // prerendering with kNavigationRequestNetworkError. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| GetParam() == SSLPrerenderTestErrorBlockType::kClientCertRequested |
| ? PrerenderHost::FinalStatus::kClientCertRequested |
| : PrerenderHost::FinalStatus::kNavigationRequestNetworkError, |
| 1); |
| } |
| |
| // Tests that prerendering will be cancelled if the server asks for client |
| // certificates or responds with an expired certificate, even if the subresource |
| // request is intercepted by a service worker. |
| IN_PROC_BROWSER_TEST_P(SSLPrerenderBrowserTest, |
| CertificateValidation_SWSubResource) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Prerender a page, then register a service worker that intercepts resource |
| // requests. |
| const GURL kPrerenderingUrl = GetUrl("/workers/service_worker_setup.html"); |
| int host_id = prerender_helper()->AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ("ok", |
| EvalJs(prerender_helper()->GetPrerenderedMainFrameHost(host_id), |
| "setup();")); |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| RequireClientCertsOrSendExpiredCerts(); |
| |
| // Try to fetch a sub resource through the registered service worker. The |
| // server should ask for a client certificate or respond with an expired |
| // certificate, which leads to the cancellation of prerendering. |
| std::string resource_url = GetUrl("/workers/empty.js?intercept").spec(); |
| ignore_result(ExecJs(prerender_helper()->GetPrerenderedMainFrameHost(host_id), |
| JsReplace("fetch($1);", resource_url))); |
| |
| // Check the prerender was destroyed. |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| GetExpectedFinalStatus(), 1); |
| } |
| |
| // 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"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // 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()); |
| EXPECT_FALSE(rfh_a->IsInPrimaryMainFrame()); |
| EXPECT_FALSE(rfh_b->IsInPrimaryMainFrame()); |
| |
| // 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()); |
| EXPECT_TRUE(rfh_a->IsInPrimaryMainFrame()); |
| EXPECT_FALSE(rfh_b->IsInPrimaryMainFrame()); |
| |
| // "Navigation.TimeToActivatePrerender.SpeculationRule" histogram should be |
| // recorded on every prerender activation. |
| histogram_tester.ExpectTotalCount( |
| "Navigation.TimeToActivatePrerender.SpeculationRule", 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))); |
| |
| NavigationRequest* request = |
| web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request(); |
| |
| // Wait until the navigation is deferred by CommitDeferringCondition. |
| // TODO(nhiroki): Avoid using base::RunUntilIdle() and instead use some |
| // explicit signal of the running condition. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| |
| // The navigation should not have proceeded past NOT_STARTED because the |
| // PrerenderCommitDeferringCondition is deferring it. |
| EXPECT_EQ(request->state(), NavigationRequest::NOT_STARTED); |
| |
| // 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), 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); |
| int host_id = GetHostForUrl(disallowed_url); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(disallowed_url), 0); |
| host_observer.WaitForDestroyed(); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| 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()); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), |
| disallowed_url); |
| AddPrerenderAsync(disallowed_url); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(disallowed_url), 0); |
| host_observer.WaitForDestroyed(); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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; |
| 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 DocumentUserData object is not cleared on activating a |
| // prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DocumentUserData) { |
| 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start a prerender. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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()->GetLastCommittedURL(), 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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.SpeculationRule", |
| 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()->GetLastCommittedURL(), 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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::PrerenderHostObserver host_observer(*web_contents, prerendering_url); |
| helper->AddPrerenderAsync(prerendering_url); |
| 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.SpeculationRule", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kUnknown, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledUnknownInterface", |
| InterfaceNameHasher(mojom::PepperHost::Name_), 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.SpeculationRule", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 2); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kUnknown, 2); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledUnknownInterface", |
| InterfaceNameHasher(mojom::PepperHost::Name_), 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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.SpeculationRule", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kNotificationService, 1); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| // TODO(crbug.com/1215073): Make a WPT when we have a stable way to wait |
| // cancellation runs. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DownloadByScript) { |
| 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerender_host = GetPrerenderedMainFrameHost(host_id); |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| |
| const std::string js_string = R"( |
| document.body.innerHTML = |
| "<a id='target' download='download-link' href='cache.txt'>here</a>"; |
| document.getElementById('target').click(); |
| )"; |
| ExecuteScriptAsync(prerender_host, js_string); |
| |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kDownload, 1); |
| } |
| |
| 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 kDownloadUrl = |
| GetUrl("/set-header?Content-Disposition: attachment"); |
| |
| LoadAndWaitForPrerenderDestroyed(web_contents(), kDownloadUrl, |
| prerender_helper()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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 kDownloadUrl = |
| GetUrl("/set-header?Content-Disposition: attachment"); |
| ExecuteScriptAsync(prerender_host, |
| JsReplace("add_iframe_async($1)", kDownloadUrl)); |
| |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kDownload, 1); |
| } |
| |
| // Tests that requesting audio output devices from prerendering documents result |
| // in cancellation of prerendering. Prerender2 decides to cancel prerendering |
| // here, because browser cannot defer this request as the renderer's main thread |
| // blocks while it waits for the response. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RequestAudioOutputDevice) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id); |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| |
| // Create a output audio context which request audio output devices. |
| // Prerendering should be cancelled in this case. |
| // Whether using the EXECUTE_SCRIPT_NO_USER_GESTURE flag or not does not |
| // affect the test result. The purpose of using it is to simulate real |
| // scenarios since prerendering pages cannot have user gestures. |
| ignore_result(ExecJs(prerender_rfh, "const context = new AudioContext();", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| host_observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kAudioOutputDeviceRequested, 1); |
| } |
| |
| // Tests that an activated page is allowed to request output devices. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| RequestAudioOutputDeviceAfterActivation) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| content::test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| |
| // After being activated, the document can play audio and it should work as a |
| // normal document. |
| prerender_helper()->NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_TRUE(host_observer.was_activated()); |
| std::string audio_script = R"( |
| const context = new AudioContext(); |
| const osc = new OscillatorNode(context); |
| osc.connect(context.destination); |
| osc.start(); |
| )"; |
| EXPECT_TRUE(ExecJs(web_contents()->GetMainFrame(), audio_script)); |
| } |
| |
| // The viewport meta tag is only enabled on Android. |
| #if defined(OS_ANDROID) |
| namespace { |
| |
| // Used to observe the viewport change in the WebContents. |
| class TestViewportWebContentsObserver : public WebContentsObserver { |
| public: |
| TestViewportWebContentsObserver(WebContents* web_contents, |
| blink::mojom::ViewportFit wanted_value) |
| : WebContentsObserver(web_contents), wanted_value_(wanted_value) {} |
| |
| TestViewportWebContentsObserver(const TestViewportWebContentsObserver&) = |
| delete; |
| TestViewportWebContentsObserver& operator=( |
| const TestViewportWebContentsObserver&) = delete; |
| |
| // WebContentsObserver implementation. |
| void ViewportFitChanged(blink::mojom::ViewportFit value) override { |
| value_ = value; |
| if (waiting_for_wanted_value_ && value == wanted_value_) { |
| std::move(waiting_for_wanted_value_).Run(); |
| } |
| } |
| |
| void WaitForWantedValue() { |
| if (value_.has_value() && value_.value() == wanted_value_) { |
| return; |
| } |
| base::RunLoop loop; |
| waiting_for_wanted_value_ = loop.QuitClosure(); |
| loop.Run(); |
| } |
| |
| private: |
| base::OnceClosure waiting_for_wanted_value_; |
| absl::optional<blink::mojom::ViewportFit> value_; |
| const blink::mojom::ViewportFit wanted_value_; |
| }; |
| |
| } // namespace |
| |
| // Tests that the viewport-fit property works well on prerendering page: |
| // * The property in prerendering page shouldn't affect the primary page. |
| // * After activating the prerendered page, WebContents's viewport property can |
| // be updated. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ViewportFit) { |
| const GURL kInitialUrl = GetUrl("/prerender/viewport.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/viewport.html?prerendering"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id); |
| RenderFrameHostImpl* primary_rfh = web_contents_impl()->GetMainFrame(); |
| |
| { |
| // Set viewport-fit property in the primary page and the prerendering page. |
| // Prerendering shouldn't be cancelled, nor should its property affect the |
| // corresponding WebContents's property. |
| TestViewportWebContentsObserver observer(web_contents_impl(), |
| blink::mojom::ViewportFit::kCover); |
| EXPECT_TRUE(ExecJs(prerender_rfh, "setViewportFit('contain')")); |
| EXPECT_TRUE(ExecJs(primary_rfh, "setViewportFit('cover')")); |
| web_contents_impl()->FullscreenStateChanged( |
| primary_rfh, true, blink::mojom::FullscreenOptions::New()); |
| observer.WaitForWantedValue(); |
| } |
| { |
| // After the prerendering page is activated, the WebContents's property |
| // should be updated. |
| TestViewportWebContentsObserver observer( |
| web_contents_impl(), blink::mojom::ViewportFit::kContain); |
| prerender_helper()->NavigatePrimaryPage(kPrerenderingUrl); |
| web_contents_impl()->FullscreenStateChanged( |
| prerender_rfh, true, blink::mojom::FullscreenOptions::New()); |
| observer.WaitForWantedValue(); |
| } |
| EXPECT_TRUE(host_observer.was_activated()); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| // End: Tests for feature restrictions in prerendered pages ==================== |
| |
| // Tests prerendering for low-end devices. |
| class PrerenderLowMemoryBrowserTest : public PrerenderBrowserTest { |
| public: |
| PrerenderLowMemoryBrowserTest() { |
| // Set the value of memory threshold more than the physical memory. The |
| // test will expect that prerendering does not occur. |
| std::string memory_threshold = |
| base::NumberToString(base::SysInfo::AmountOfPhysicalMemoryMB() + 1); |
| feature_list_.InitWithFeaturesAndParameters( |
| {{blink::features::kPrerender2, {}}, |
| {blink::features::kPrerender2MemoryControls, |
| {{blink::features::kPrerender2MemoryThresholdParamName, |
| memory_threshold}}}}, |
| {}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Tests that prerendering doesn't run for low-end devices. |
| IN_PROC_BROWSER_TEST_F(PrerenderLowMemoryBrowserTest, NoPrerender) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Attempt to prerender. |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| AddPrerenderAsync(kPrerenderingUrl); |
| observer.WaitForTrigger(kPrerenderingUrl); |
| |
| // It should fail. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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( |
| DisallowActivationReasonId::kForTesting)); |
| |
| // 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.SpeculationRule", |
| 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // The visibility state must be "hidden" while prerendering. |
| auto* rvh = static_cast<RenderViewHostImpl*>( |
| prerendered_render_frame_host->GetRenderViewHost()); |
| EXPECT_EQ(rvh->GetPageLifecycleStateManager() |
| ->CalculatePageLifecycleState() |
| ->visibility, |
| PageVisibilityState::kHidden); |
| EXPECT_EQ(prerendered_render_frame_host->GetVisibilityState(), |
| PageVisibilityState::kHidden); |
| |
| // Activate prerendering page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // The visibility state should be "visible" after activation. |
| EXPECT_EQ(rvh->GetPageLifecycleStateManager() |
| ->CalculatePageLifecycleState() |
| ->visibility, |
| PageVisibilityState::kVisible); |
| EXPECT_EQ(prerendered_render_frame_host->GetVisibilityState(), |
| PageVisibilityState::kVisible); |
| } |
| |
| // 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()->GetLastCommittedURL(), kPrerenderingUrl); |
| // The title should be updated with the activated page. |
| EXPECT_EQ(shell()->web_contents()->GetTitle(), kPrerenderingTitle); |
| } |
| |
| // Tests that WebContentsObserver::TitleWasSet is not dispatched when title is |
| // set during prerendering, but is later dispatched after activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TitleWasSetWithPrerendering) { |
| const GURL kInitialUrl = GetUrl("/title2.html"); |
| const GURL kPrerenderingUrlWithTitle = GetUrl("/simple_page.html"); |
| const GURL kPrerenderingUrlWithoutTitle = GetUrl("/title1.html"); |
| const std::u16string kInitialTitle(u"Title Of Awesomeness"); |
| const std::u16string kPrerenderingTitle(u"OK"); |
| |
| // Navigate to an initial page; TitleWasSet should be called when page sets |
| // its title. |
| { |
| testing::NiceMock<MockWebContentsObserver> mock_observer( |
| shell()->web_contents()); |
| EXPECT_CALL(mock_observer, TitleWasSet(testing::_)); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_EQ(shell()->web_contents()->GetTitle(), kInitialTitle); |
| } |
| |
| // Prerender a page; TitleWasSet should not be called despite the page setting |
| // a title. |
| { |
| testing::NiceMock<MockWebContentsObserver> mock_observer( |
| shell()->web_contents()); |
| EXPECT_CALL(mock_observer, TitleWasSet(testing::_)).Times(0); |
| ASSERT_NE(AddPrerender(kPrerenderingUrlWithTitle), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| } |
| |
| // Activate prerendered page; TitleWasSet should now be called. |
| { |
| testing::NiceMock<MockWebContentsObserver> mock_observer( |
| shell()->web_contents()); |
| EXPECT_CALL(mock_observer, TitleWasSet(testing::_)) |
| .WillOnce(testing::Invoke([kPrerenderingTitle](NavigationEntry* entry) { |
| EXPECT_EQ(entry->GetTitleForDisplay(), kPrerenderingTitle); |
| })); |
| NavigatePrimaryPage(kPrerenderingUrlWithTitle); |
| } |
| |
| // Prerender a page without a title and then activate it; TitleWasSet should |
| // not be called. |
| { |
| testing::NiceMock<MockWebContentsObserver> mock_observer( |
| shell()->web_contents()); |
| EXPECT_CALL(mock_observer, TitleWasSet(testing::_)).Times(0); |
| ASSERT_NE(AddPrerender(kPrerenderingUrlWithoutTitle), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| NavigatePrimaryPage(kPrerenderingUrlWithoutTitle); |
| } |
| } |
| |
| // 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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); |
| } |
| |
| // Ensure that WebContentsObserver::DidFailLoad is not invoked and cancels |
| // prerendering when invoked inside prerender frame tree. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DidFailLoadCancelsPrerendering) { |
| 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)); |
| |
| // Initialize a MockWebContentsObserver and ensure that DidFailLoad is not |
| // invoked inside prerender frame tree. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| EXPECT_CALL(observer, DidFailLoad(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| |
| // Trigger DidFailLoad, this should cancel prerendering. |
| prerender_frame_host->DidFailLoadWithError(kPrerenderingUrl, net::ERR_FAILED); |
| |
| // The prerender host for the URL should be deleted as DidFailLoad cancels |
| // prerendering. |
| TestNavigationManager activation_observer(shell()->web_contents(), |
| kPrerenderingUrl); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Now navigate the primary page to the prerendered URL. Cancelling the |
| // prerender disables the activation due to DidFailLoad. |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| activation_observer.WaitForNavigationFinished(); |
| EXPECT_FALSE(activation_observer.was_prerendered_page_activation()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kDidFailLoad, 1); |
| } |
| |
| // 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. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| 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(); |
| 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) { |
| base::HistogramTester histogram_tester; |
| 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()); |
| |
| // Start an activation navigation for 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(condition.PassToDelegate()); |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| |
| // Wait for the condition to pause the activation. |
| condition.WaitUntilInvoked(); |
| NavigationRequest* request = |
| web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request(); |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl); |
| } |
| |
| // Make a navigation in the prerendered page. This navigation should |
| // be cancelled by PrerenderNavigationThrottle. |
| TestNavigationManager bad_nav_observer(web_contents(), kPrerenderingUrl2); |
| NavigatePrerenderedPage(prerender_host_id, kPrerenderingUrl2); |
| bad_nav_observer.WaitForNavigationFinished(); |
| EXPECT_FALSE(bad_nav_observer.was_successful()); |
| |
| // PrerenderNavigationThrottle also cancels the activation and then starts |
| // regular navigation. |
| condition.CallResumeClosure(); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl); |
| |
| // The prerender host should have been abandoned. |
| EXPECT_FALSE( |
| web_contents_impl()->GetPrerenderHostRegistry()->FindNonReservedHostById( |
| prerender_host_id)); |
| EXPECT_FALSE( |
| web_contents_impl()->GetPrerenderHostRegistry()->FindReservedHostById( |
| prerender_host_id)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kMainFrameNavigation, 1); |
| |
| // Wait for completion of the navigation. This shouldn't be the prerendered |
| // page activation. |
| activation_observer.WaitForNavigationFinished(); |
| EXPECT_FALSE(activation_observer.was_prerendered_page_activation()); |
| EXPECT_EQ(shell()->web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test that WebContentsObserver::DidFinishLoad is not invoked when the page |
| // gets loaded while prerendering but it is deferred and invoked on prerender |
| // activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DidFinishLoadInvokedAfterActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/simple_page.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver and ensure that DidFinishLoad is not |
| // invoked while prerendering. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| EXPECT_CALL(observer, DidFinishLoad(testing::_, testing::_)).Times(0); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| EXPECT_EQ(0u, prerender_frame_host->child_count()); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::InSequence s; |
| |
| // Activate the prerendered page. This should result in invoking DidFinishLoad |
| // once for root RenderFrameHost `prerender_frame_host`. |
| { |
| // Verify that DidFinishNavigation is invoked before DidFinishLoad on |
| // activation. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)); |
| |
| EXPECT_CALL(observer, |
| DidFinishLoad(prerender_frame_host, kPrerenderingUrl)); |
| } |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test that WebContentsObserver::DidFinishLoad is not invoked when the page |
| // gets loaded while prerendering but it is deferred and invoked on prerender |
| // activation for both main and sub-frames. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DidFinishLoadInvokedAfterActivationWithSubframes) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver and ensure that DidFinishLoad is not |
| // invoked while prerendering. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| testing::InSequence s; |
| EXPECT_CALL(observer, DidFinishLoad(testing::_, testing::_)).Times(0); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_main_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| RenderFrameHost* child_frame = ChildFrameAt(prerender_main_frame_host, 0); |
| EXPECT_EQ(1u, prerender_main_frame_host->child_count()); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| // Activate the prerendered page. This should result in invoking DidFinishLoad |
| // twice once for root and once for child RenderFrameHosts. |
| { |
| // Verify that DidFinishNavigation is invoked before DidFinishLoad. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)); |
| |
| EXPECT_CALL(observer, |
| DidFinishLoad(prerender_main_frame_host, kPrerenderingUrl)); |
| |
| EXPECT_CALL(observer, |
| DidFinishLoad(child_frame, child_frame->GetLastCommittedURL())); |
| } |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test that WebContentsObserver::DOMContentLoaded is not invoked while |
| // prerendering but it is deferred and invoked on prerender activation for both |
| // main and sub-frames. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DOMContentLoadedInvokedAfterActivationWithSubframes) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver and ensure that DOMContentLoaded is |
| // not invoked while prerendering. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| EXPECT_CALL(observer, DOMContentLoaded(testing::_)).Times(0); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_main_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| RenderFrameHost* child_frame = ChildFrameAt(prerender_main_frame_host, 0); |
| EXPECT_EQ(prerender_main_frame_host->child_count(), 1u); |
| ASSERT_NE(prerender_host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::InSequence s; |
| |
| // Activate the prerendered page. This should result in invoking |
| // DOMContentLoaded twice once for root and once for child RenderFrameHost. |
| { |
| // Verify that DidFinishNavigation is invoked before DOMContentLoaded on |
| // activation. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)); |
| |
| EXPECT_CALL(observer, DOMContentLoaded(prerender_main_frame_host)).Times(1); |
| |
| EXPECT_CALL(observer, DOMContentLoaded(child_frame)).Times(1); |
| } |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test that WebContentsObserver::DocumentOnLoadCompletedInPrimaryMainFrame is |
| // not invoked when the page gets loaded while prerendering but it is deferred |
| // and invoked on prerender activation. |
| IN_PROC_BROWSER_TEST_F( |
| PrerenderBrowserTest, |
| DocumentOnLoadCompletedInPrimaryMainFrameInvokedAfterActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver and ensure that |
| // DocumentOnLoadCompletedInPrimaryMainFrame is not invoked while |
| // prerendering. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| EXPECT_CALL(observer, DocumentOnLoadCompletedInPrimaryMainFrame()).Times(0); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| EXPECT_EQ(prerender_frame_host->child_count(), 1u); |
| ASSERT_NE(prerender_host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::InSequence s; |
| |
| // Activate the prerendered page. This should result in invoking |
| // DocumentOnLoadCompletedInPrimaryMainFrame only for main RenderFrameHost. |
| { |
| // Verify that DidFinishNavigation is invoked before |
| // DocumentOnLoadCompletedInPrimaryMainFrame on activation. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)); |
| |
| EXPECT_CALL(observer, DocumentOnLoadCompletedInPrimaryMainFrame()).Times(1); |
| } |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test that WebContentsObserver::DocumentAvailableInMainFrame is not |
| // invoked when the page gets loaded while prerendering but it is deferred and |
| // invoked on prerender activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DocumentAvailableInMainFrameInvokedAfterActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver and ensure that |
| // DocumentAvailableInMainFrame is not invoked while prerendering. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| EXPECT_CALL(observer, DocumentAvailableInMainFrame(testing::_)).Times(0); |
| |
| // AddPrerender() below waits until WebContentsObserver::DidStopLoading() is |
| // called and RenderFrameHostImpl::DocumentAvailableInMainFrame() call is |
| // expected before it returns. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| EXPECT_EQ(prerender_frame_host->child_count(), 1u); |
| ASSERT_NE(prerender_host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::InSequence s; |
| |
| // Activate the prerendered page. This should result in invoking |
| // DocumentOnLoadCompletedInPrimaryMainFrame only for main RenderFrameHost. |
| // Verify that DidFinishNavigation is invoked before |
| // DocumentAvailableInMainFrame on activation. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)); |
| |
| EXPECT_CALL(observer, DocumentAvailableInMainFrame(prerender_frame_host)) |
| .Times(1); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test that WebContentsObserver::LoadProgressChanged is not invoked when the |
| // page gets loaded while prerendering but is invoked on prerender activation. |
| // Check that LoadProgressChanged is only called once for |
| // blink::kFinalLoadProgress if the prerender page completes loading on |
| // activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| LoadProgressChangedInvokedOnActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/simple_page.html"); |
| |
| web_contents_impl()->set_minimum_delay_between_loading_updates_for_testing( |
| base::Milliseconds(0)); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver and ensure that LoadProgressChanged is |
| // not invoked while prerendering. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| testing::InSequence s; |
| EXPECT_CALL(observer, LoadProgressChanged(testing::_)).Times(0); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(prerender_host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| RenderFrameHostImpl* prerender_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| |
| // Activate the prerendered page. This should result in invoking |
| // LoadProgressChanged for the following cases: |
| { |
| // 1) During DidStartLoading LoadProgressChanged is invoked with |
| // kInitialLoadProgress value. |
| EXPECT_CALL(observer, LoadProgressChanged(blink::kInitialLoadProgress)); |
| |
| // Verify that DidFinishNavigation is invoked before final load progress |
| // notification. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)); |
| |
| // 2) During DidStopLoading LoadProgressChanged is invoked with |
| // kFinalLoadProgress. |
| EXPECT_CALL(observer, LoadProgressChanged(blink::kFinalLoadProgress)) |
| .Times(1); |
| } |
| |
| // Set the prerender load progress value to blink::kFinalLoadProgress, this |
| // should result in invoking LoadProgressChanged(blink::kFinalLoadProgress) |
| // only once on activation during call to DidStopLoading. |
| prerender_frame_host->GetPage().set_load_progress(blink::kFinalLoadProgress); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Test the dispatch order of various load events on prerender activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, OrderingOfDifferentLoadEvents) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/simple_page.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Initialize a MockWebContentsObserver to check order of different load |
| // events. |
| testing::NiceMock<MockWebContentsObserver> observer(shell()->web_contents()); |
| |
| // Start a prerender. |
| int prerender_host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(prerender_host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Verify and clear all expectations on the mock observer before setting new |
| // ones. |
| testing::Mock::VerifyAndClearExpectations(&observer); |
| testing::InSequence s; |
| |
| // Activate the prerendered page. This should result in invoking various |
| // WebContentsObserver events in the following order. |
| { |
| EXPECT_CALL(observer, DidStartLoading()).Times(1); |
| |
| // Verify that DidFinishNavigation is invoked before any finish load events |
| // are dispatched. |
| EXPECT_CALL(observer, DidFinishNavigation(testing::_)).Times(1); |
| |
| EXPECT_CALL(observer, DOMContentLoaded(testing::_)).Times(1); |
| |
| EXPECT_CALL(observer, DocumentOnLoadCompletedInPrimaryMainFrame()).Times(1); |
| |
| EXPECT_CALL(observer, DidFinishLoad(testing::_, testing::_)).Times(1); |
| |
| EXPECT_CALL(observer, DidStopLoading()).Times(1); |
| } |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| } |
| |
| // Tests that cross-origin subframe navigations in a prerendered page are |
| // deferred even if they start after the a navigation starts that will |
| // attempt to activate the prerendered page. |
| // |
| // Regression test for https://crbug.com/1190262. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| CrossOriginSubframeNavigationDuringActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html"); |
| const GURL kCrossOriginUrl = GetCrossOriginUrl("/simple_page.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int prerender_host_id = RenderFrameHost::kNoFrameTreeNodeId; |
| RenderFrameHost* prerender_main_frame = nullptr; |
| { |
| prerender_host_id = AddPrerender(kPrerenderingUrl); |
| prerender_main_frame = GetPrerenderedMainFrameHost(prerender_host_id); |
| RenderFrameHost* child_frame = ChildFrameAt(prerender_main_frame, 0); |
| ASSERT_TRUE(child_frame); |
| } |
| |
| // Start an activation navigation for the prerender. Use a |
| // CommitDeferringCondition to pause activation before it completes. |
| test::PrerenderHostObserver prerender_observer(*web_contents(), |
| kPrerenderingUrl); |
| TestNavigationManager activation_observer(shell()->web_contents(), |
| kPrerenderingUrl); |
| MockCommitDeferringConditionWrapper condition(/*is_ready_to_commit=*/false); |
| { |
| MockCommitDeferringConditionInstaller installer(condition.PassToDelegate()); |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| |
| // Wait for the condition to pause the activation. |
| condition.WaitUntilInvoked(); |
| NavigationRequest* request = |
| web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request(); |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl); |
| } |
| |
| // Start a cross-origin subframe navigation in the prerendered page. It |
| // should be deferred. |
| std::string kNavigateScript = R"( |
| document.querySelector('iframe').src = $1; |
| )"; |
| TestNavigationManager iframe_nav_observer(shell()->web_contents(), |
| kCrossOriginUrl); |
| ASSERT_TRUE(ExecJs(prerender_main_frame, |
| JsReplace(kNavigateScript, kCrossOriginUrl))); |
| |
| iframe_nav_observer.WaitForFirstYieldAfterDidStartNavigation(); |
| |
| // The PrerenderSubframeNavigationThrottle should defer it until activation. |
| auto* child_ftn = |
| FrameTreeNode::GloballyFindByID(prerender_host_id)->child_at(0); |
| auto* child_navigation = child_ftn->navigation_request(); |
| ASSERT_NE(child_navigation, nullptr); |
| EXPECT_TRUE(child_navigation->IsDeferredForTesting()); |
| |
| // Allow the activation navigation to complete. |
| condition.CallResumeClosure(); |
| activation_observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_observer.was_prerendered_page_activation()); |
| |
| // The iframe navigation should finish. |
| iframe_nav_observer.WaitForNavigationFinished(); |
| EXPECT_EQ(ChildFrameAt(prerender_main_frame, 0)->GetLastCommittedURL(), |
| kCrossOriginUrl); |
| } |
| |
| // Tests WebContents::OpenURL to a frame in a prerendered page when a |
| // navigation that will attempt to activate the page has already started. The |
| // subframe navigation should succeed. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| OpenURLInSubframeDuringActivation) { |
| 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Start prerendering `kPrerenderingUrl`. |
| int prerender_host_id = RenderFrameHost::kNoFrameTreeNodeId; |
| RenderFrameHost* child_frame = nullptr; |
| { |
| prerender_host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(prerender_host_id); |
| child_frame = ChildFrameAt(prerendered_render_frame_host, 0); |
| ASSERT_TRUE(child_frame); |
| } |
| |
| // Start an activation navigation for the prerender. Use a |
| // CommitDeferringCondition to pause activation before it completes. |
| test::PrerenderHostObserver prerender_observer(*web_contents(), |
| kPrerenderingUrl); |
| TestNavigationManager activation_observer(shell()->web_contents(), |
| kPrerenderingUrl); |
| MockCommitDeferringConditionWrapper condition(/*is_ready_to_commit=*/false); |
| { |
| MockCommitDeferringConditionInstaller installer(condition.PassToDelegate()); |
| ASSERT_TRUE(ExecJs(web_contents()->GetMainFrame(), |
| JsReplace("location = $1", kPrerenderingUrl))); |
| |
| // Wait for the condition to pause the activation. |
| condition.WaitUntilInvoked(); |
| NavigationRequest* request = |
| web_contents_impl()->GetPrimaryFrameTree().root()->navigation_request(); |
| EXPECT_TRUE(request->IsCommitDeferringConditionDeferredForTesting()); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl); |
| } |
| |
| // Use the OpenURL API to navigate the iframe in the 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 activation navigation to complete. |
| condition.CallResumeClosure(); |
| activation_observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_observer.was_prerendered_page_activation()); |
| } |
| |
| 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: |
| raw_ptr<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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), 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(0); |
| |
| 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); |
| |
| // 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.SpeculationRule", |
| #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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // 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.SpeculationRule", |
| 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)); |
| |
| 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()->GetLastCommittedURL(), 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()->GetLastCommittedURL(), 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, and ensure |
| // bot flags won't enable BFCache (as we want to control BFCache |
| // enabling/disabling ourselves). |
| std::vector<base::Feature> disabled_features{ |
| features::kBackForwardCacheMemoryControls, |
| features::kBackForwardCacheSameSiteForBots}; |
| |
| 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; |
| } |
| } |
| |
| // Tests that a trigger page destroys a prerendered page when it navigates |
| // forward and goes into the BFCache. |
| IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest, |
| CancelOnAfterTriggerIsStoredInBackForwardCache_Forward) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kNextUrl = GetUrl("/empty.html?next"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| RenderFrameHostImpl* initial_frame_host = current_frame_host(); |
| |
| // Make a prerendered page from the initial page. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id); |
| |
| // Navigate the initial page to a non-prerendered page. |
| ASSERT_TRUE(NavigateToURL(shell(), kNextUrl)); |
| |
| // Check if the initial page is in the BFCache. |
| switch (GetParam()) { |
| case BackForwardCacheType::kDisabled: |
| // The BFCache is disabled, so the initial page is not in the BFCache. |
| ASSERT_FALSE(initial_frame_host->IsInBackForwardCache()); |
| break; |
| case BackForwardCacheType::kEnabled: |
| // The BFCache is enabled but the same-site BFCache is disabled. The |
| // navigation was same-origin, so the initial page is not in the BFCache. |
| ASSERT_FALSE(IsSameSiteBackForwardCacheEnabled()); |
| ASSERT_FALSE(initial_frame_host->IsInBackForwardCache()); |
| break; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| // The same-site BFCache is enabled, so the initial page is in the BFCache |
| // after the same-origin navigation. |
| ASSERT_TRUE(IsSameSiteBackForwardCacheEnabled()); |
| ASSERT_TRUE(initial_frame_host->IsInBackForwardCache()); |
| break; |
| } |
| |
| // The navigation should destroy the prerendered page regardless of if the |
| // initial page was in the BFCache. |
| prerender_observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kTriggerDestroyed, 1); |
| } |
| |
| // Tests that a trigger page destroys a prerendered page when it navigates back |
| // and goes into the BFCache. |
| IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest, |
| CancelOnAfterTriggerIsStoredInBackForwardCache_Back) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kNextUrl = GetUrl("/empty.html?next"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Navigate to a next page. |
| ASSERT_TRUE(NavigateToURL(shell(), kNextUrl)); |
| RenderFrameHostImpl* next_frame_host = current_frame_host(); |
| |
| // Make a prerendered page from the next page. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| test::PrerenderHostObserver prerender_observer(*web_contents_impl(), host_id); |
| |
| // Navigate back to the initial page. |
| content::TestNavigationObserver navigation_observer(web_contents()); |
| shell()->GoBackOrForward(-1); |
| navigation_observer.Wait(); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kInitialUrl); |
| |
| // Check if the next page is in the BFCache. |
| switch (GetParam()) { |
| case BackForwardCacheType::kDisabled: |
| // The BFCache is disabled, so the next page is not in the BFCache. |
| ASSERT_FALSE(next_frame_host->IsInBackForwardCache()); |
| break; |
| case BackForwardCacheType::kEnabled: |
| // The BFCache is enabled but the same-site BFCache is disabled. The back |
| // navigation was same-origin, so the next page is not in the BFCache. |
| ASSERT_FALSE(IsSameSiteBackForwardCacheEnabled()); |
| ASSERT_FALSE(next_frame_host->IsInBackForwardCache()); |
| break; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| // The same-site BFCache is enabled, so the next page is in the BFCache |
| // after the same-origin back navigation. |
| ASSERT_TRUE(IsSameSiteBackForwardCacheEnabled()); |
| ASSERT_TRUE(next_frame_host->IsInBackForwardCache()); |
| break; |
| } |
| |
| // The navigation should destroy the prerendered page regardless of if the |
| // next page was in the BFCache. |
| prerender_observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kTriggerDestroyed, 1); |
| } |
| |
| // Tests that PrerenderHostRegistry only starts prerendering for the first |
| // prerender speculation rule it receives. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, AddSpeculationRulesMultipleTimes) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kFirstPrerenderingUrl = GetUrl("/empty.html?prerender1"); |
| const GURL kSecondPrerenderingUrl = GetUrl("/empty.html?prerender2"); |
| |
| // Add the first prerender speculation rule; it should trigger prerendering |
| // successfully. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| AddPrerender(kFirstPrerenderingUrl); |
| histogram_tester.ExpectBucketCount( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kMaxNumOfRunningPrerendersExceeded, 0); |
| |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| |
| // Add a new prerender speculation rule. Since PrerenderHostRegistry limits |
| // the number of running prerenders to one, this rule should not be applied. |
| AddPrerenderAsync(kSecondPrerenderingUrl); |
| registry_observer.WaitForTrigger(kSecondPrerenderingUrl); |
| EXPECT_FALSE(HasHostForUrl(kSecondPrerenderingUrl)); |
| histogram_tester.ExpectBucketCount( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kMaxNumOfRunningPrerendersExceeded, 1); |
| } |
| |
| // Tests that cross-origin urls cannot be prerendered. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SkipCrossOriginPrerender) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetCrossOriginUrl("/empty.html?crossorigin"); |
| |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| |
| // Add a cross-origin prerender. |
| AddPrerenderAsync(kPrerenderingUrl); |
| |
| // Wait for PrerenderHostRegistry to receive the cross-origin prerender |
| // request, and it should be ignored. |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kCrossOriginNavigation, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| EmbedderTrigger_SameOriginRedirection) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| const GURL kRedirectedUrl = GetUrl("/empty.html?prerender"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/server-redirect?" + kRedirectedUrl.spec()); |
| |
| RedirectChainObserver redirect_chain_observer(*shell()->web_contents(), |
| kRedirectedUrl); |
| |
| // Start prerendering by embedder triggered prerendering. |
| std::unique_ptr<PrerenderHandle> prerender_handle = |
| web_contents_impl()->StartPrerendering(kPrerenderingUrl, |
| PrerenderTriggerType::kEmbedder, |
| "EmbedderSuffixForTest"); |
| EXPECT_TRUE(prerender_handle); |
| test::PrerenderTestHelper::WaitForPrerenderLoadCompletion( |
| *shell()->web_contents(), kPrerenderingUrl); |
| ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" |
| "EmbedderSuffixForTest", |
| PrerenderHost::FinalStatus::kEmbedderTriggeredAndSameOriginRedirected, 1); |
| } |
| |
| void PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| WebContentsImpl& web_contents, |
| const GURL& prerendering_url, |
| const GURL& cross_origin_url) { |
| RedirectChainObserver redirect_chain_observer{web_contents, cross_origin_url}; |
| |
| // Start prerendering by embedder triggered prerendering. |
| std::unique_ptr<PrerenderHandle> prerender_handle = |
| web_contents.StartPrerendering(prerendering_url, |
| PrerenderTriggerType::kEmbedder, |
| "EmbedderSuffixForTest"); |
| EXPECT_TRUE(prerender_handle); |
| test::PrerenderTestHelper::WaitForPrerenderLoadCompletion(web_contents, |
| prerendering_url); |
| |
| ASSERT_EQ(2u, redirect_chain_observer.redirect_chain().size()); |
| } |
| |
| void CheckExpectedCrossOriginMetrics( |
| const base::HistogramTester& histogram_tester, |
| PrerenderCrossOriginRedirectionMismatch mismatch_type, |
| absl::optional<PrerenderCrossOriginRedirectionProtocolChange> |
| protocol_change, |
| absl::optional<PrerenderCrossOriginRedirectionDomain> domain_change) { |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.Embedder_" |
| "EmbedderSuffixForTest", |
| PrerenderHost::FinalStatus::kEmbedderTriggeredAndCrossOriginRedirected, |
| 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCrossOriginRedirectionMismatch.Embedder_" |
| "EmbedderSuffixForTest", |
| mismatch_type, 1); |
| if (protocol_change.has_value()) { |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.CrossOriginRedirectionProtocolChange.Embedder_" |
| "EmbedderSuffixForTest", |
| protocol_change.value(), 1); |
| } |
| if (domain_change.has_value()) { |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.CrossOriginRedirectionDomain.Embedder_" |
| "EmbedderSuffixForTest", |
| domain_change.value(), 1); |
| } |
| } |
| |
| // Tests PrerenderCrossOriginRedirectionMismatch.kSchemeHostPortMismatch was |
| // recorded when a prerendering navigaton was redireted to another origin with |
| // different scheme, host and port. |
| IN_PROC_BROWSER_TEST_F( |
| PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_SchemeHostPortMismatch) { |
| base::HistogramTester histogram_tester; |
| embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| // The redirected_url's origin completely differs from the prerendering one. |
| GURL redirected_url = embedded_test_server()->GetURL("b.test", "/empty.html"); |
| GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); |
| ASSERT_NE(prerendering_url.scheme(), redirected_url.scheme()); |
| ASSERT_NE(prerendering_url.host(), redirected_url.host()); |
| ASSERT_NE(prerendering_url.port(), redirected_url.port()); |
| |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), prerendering_url, redirected_url); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, |
| PrerenderCrossOriginRedirectionMismatch::kSchemeHostPortMismatch, |
| /*protocol_change=*/absl::nullopt, /*domain_change=*/absl::nullopt); |
| } |
| |
| // Tests a prerendering navigaton goes with HTTP protocol, and being redirected |
| // to upgrade its protocol to HTTPS. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_ProtocolUpgrade) { |
| base::HistogramTester histogram_tester; |
| embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL initial_url = embedded_test_server()->GetURL("a.test", "/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| // Redirect to another url with protocol upgraded. |
| GURL redirected_url = ssl_server().GetURL("a.test", "/empty.html"); |
| GURL prerendering_url = embedded_test_server()->GetURL( |
| "a.test", "/server-redirect?" + redirected_url.spec()); |
| ASSERT_NE(prerendering_url.scheme(), redirected_url.scheme()); |
| ASSERT_NE(prerendering_url.port(), redirected_url.port()); |
| ASSERT_EQ(prerendering_url.scheme(), url::kHttpScheme); |
| ASSERT_EQ(redirected_url.scheme(), url::kHttpsScheme); |
| |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), prerendering_url, redirected_url); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, |
| PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch, |
| PrerenderCrossOriginRedirectionProtocolChange::kHttpProtocolUpgrade, |
| /*domain_change=*/absl::nullopt); |
| } |
| |
| // Similar to |
| // CancelEmbedderTriggeredPrerenderingCrossOriginRedirection_ProtocolUpgrade, |
| // tests a prerendering navigaton goes with HTTPS protocol, and being redirected |
| // to upgrade its protocol to HTTPS. |
| IN_PROC_BROWSER_TEST_F( |
| PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_ProtocolDowngrade) { |
| base::HistogramTester histogram_tester; |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| GURL::Replacements downgrade_protocol; |
| downgrade_protocol.SetSchemeStr(url::kHttpScheme); |
| std::string port_str(base::NumberToString(ssl_server().port() + 1)); |
| downgrade_protocol.SetPortStr(port_str); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| // Redirect to another url with protocol upgraded. |
| GURL redirected_url = |
| GetUrl("/empty.html").ReplaceComponents(downgrade_protocol); |
| GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); |
| ASSERT_NE(prerendering_url.scheme(), redirected_url.scheme()); |
| ASSERT_NE(prerendering_url.port(), redirected_url.port()); |
| ASSERT_EQ(prerendering_url.scheme(), url::kHttpsScheme); |
| ASSERT_EQ(redirected_url.scheme(), "http"); |
| |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), prerendering_url, redirected_url); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, |
| PrerenderCrossOriginRedirectionMismatch::kSchemePortMismatch, |
| PrerenderCrossOriginRedirectionProtocolChange::kHttpProtocolDowngrade, |
| /*domain_change=*/absl::nullopt); |
| } |
| |
| // Tests PrerenderCrossOriginRedirectionMismatch.kHostMismatch and |
| // PrerenderCrossOriginRedirectionDomain.kRedirectToSubDomain are recorded |
| // when the prerendering navigation is redirected to a subdomain URL. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_ToSubdomain) { |
| base::HistogramTester histogram_tester; |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| GURL::Replacements set_host; |
| set_host.SetHostStr("www.a.test"); |
| |
| GURL redirected_url = GetUrl("/empty.html").ReplaceComponents(set_host); |
| GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); |
| |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), prerendering_url, redirected_url); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch, |
| /*protocol_change=*/absl::nullopt, |
| PrerenderCrossOriginRedirectionDomain::kRedirectToSubDomain); |
| } |
| |
| // Tests PrerenderCrossOriginRedirectionMismatch.kHostMismatch and |
| // PrerenderCrossOriginRedirectionDomain.kRedirectFromSubDomain are recorded |
| // when the prerendering navigation is redirected to a subdomain URL. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_FromSubdomain) { |
| base::HistogramTester histogram_tester; |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| GURL::Replacements set_host; |
| set_host.SetHostStr("www.a.test"); |
| |
| GURL redirected_url = GetUrl("/empty.html"); |
| GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()) |
| .ReplaceComponents(set_host); |
| |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), prerendering_url, redirected_url); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch, |
| /*protocol_change=*/absl::nullopt, |
| PrerenderCrossOriginRedirectionDomain::kRedirectFromSubDomain); |
| } |
| |
| // Tests PrerenderCrossOriginRedirectionMismatch.kHostMismatch and |
| // PrerenderCrossOriginRedirectionDomain.kCrossDomain are recorded |
| // when the prerendering navigation is redirected to a different domain. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_DifferentDomain) { |
| base::HistogramTester histogram_tester; |
| GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| GURL kRedirectedUrl = GetCrossOriginUrl("/empty.html?prerender"); |
| GURL kPrerenderingUrl = GetUrl("/server-redirect?" + kRedirectedUrl.spec()); |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), kPrerenderingUrl, kRedirectedUrl); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, PrerenderCrossOriginRedirectionMismatch::kHostMismatch, |
| /*protocol_change=*/absl::nullopt, |
| PrerenderCrossOriginRedirectionDomain::kCrossDomain); |
| } |
| |
| // Tests that a prerendering navigation is redirected to another origin whose |
| // port differs from the prerendering one. The prerender should be cancelled and |
| // `PrerenderCrossOriginRedirectionCase::kPortMismatch` should be recorded. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| EmbedderTrigger_CrossOriginRedirection_PortChanged) { |
| base::HistogramTester histogram_tester; |
| GURL initial_url = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| std::string port_str(base::NumberToString(ssl_server().port() + 1)); |
| GURL::Replacements set_port; |
| set_port.SetPortStr(port_str); |
| GURL redirected_url = initial_url.ReplaceComponents(set_port); |
| GURL prerendering_url = GetUrl("/server-redirect?" + redirected_url.spec()); |
| PrerenderEmbedderTriggeredCrossOriginRedirectionPage( |
| *web_contents_impl(), prerendering_url, redirected_url); |
| CheckExpectedCrossOriginMetrics( |
| histogram_tester, PrerenderCrossOriginRedirectionMismatch::kPortMismatch, |
| /*protocol_change=*/absl::nullopt, |
| /*domain_change=*/absl::nullopt); |
| } |
| |
| namespace { |
| |
| class FrameDisplayStateChangedObserver : public WebContentsObserver { |
| public: |
| explicit FrameDisplayStateChangedObserver(RenderFrameHost& host) |
| : WebContentsObserver(WebContents::FromRenderFrameHost(&host)), |
| target_host_(&host) {} |
| |
| void WaitForFrameDisplayStateChanged() { |
| if (changed_count_ > 0) { |
| changed_count_--; |
| } else { |
| base::RunLoop loop; |
| callback_ = loop.QuitClosure(); |
| loop.Run(); |
| } |
| } |
| |
| void FrameDisplayStateChanged(RenderFrameHost* host, |
| bool is_display_none) override { |
| if (host == target_host_) { |
| if (callback_) |
| std::move(callback_).Run(); |
| else |
| changed_count_++; |
| } |
| } |
| |
| int changed_count_ = 0; |
| const raw_ptr<RenderFrameHost> target_host_; |
| base::OnceClosure callback_; |
| }; |
| |
| } // namespace |
| |
| // Tests that FrameOwnerProperties are in sync after activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, FrameOwnerPropertiesDisplayNone) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/doc-with-display-none-iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE(AddTestUtilJS(current_frame_host())); |
| |
| // Start prerendering a document with a display:none iframe. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_TRUE(ExecJs(prerender_frame_host, "loaded;")); |
| |
| // The iframe is at "/empty.html". It should be display none. |
| RenderFrameHost* iframe_host = FindRenderFrameHost( |
| prerender_frame_host->GetPage(), GetUrl("/empty.html")); |
| EXPECT_FALSE(prerender_frame_host->IsFrameDisplayNone()); |
| EXPECT_TRUE(iframe_host->IsFrameDisplayNone()); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| // The frames should still have the same display properties. |
| EXPECT_FALSE(prerender_frame_host->IsFrameDisplayNone()); |
| EXPECT_TRUE(iframe_host->IsFrameDisplayNone()); |
| |
| // Change the display properties. |
| FrameDisplayStateChangedObserver obs(*iframe_host); |
| EXPECT_TRUE( |
| ExecJs(prerender_frame_host, |
| "document.querySelector('iframe').style = 'display: block;'")); |
| obs.WaitForFrameDisplayStateChanged(); |
| |
| EXPECT_FALSE(prerender_frame_host->IsFrameDisplayNone()); |
| EXPECT_FALSE(iframe_host->IsFrameDisplayNone()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, TriggeredPrerenderUkm) { |
| ukm::TestAutoSetUkmRecorder ukm_recorder; |
| |
| 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()->GetLastCommittedURL(), kInitialUrl); |
| |
| // PrerenderPageLoad metric should not be recorded yet. |
| EXPECT_EQ(0u, |
| ukm_recorder |
| .GetEntriesByName(ukm::builders::PrerenderPageLoad::kEntryName) |
| .size()); |
| |
| // Start a prerender. |
| ASSERT_NE(AddPrerender(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // PrerenderPageLoad:TriggeredPrerender is recorded for the initiator page |
| // load. |
| const std::vector<const ukm::mojom::UkmEntry*> entries = |
| ukm_recorder.GetEntriesByName( |
| ukm::builders::PrerenderPageLoad::kEntryName); |
| ASSERT_EQ(1u, entries.size()); |
| EXPECT_EQ(web_contents()->GetMainFrame()->GetPageUkmSourceId(), |
| entries.front()->source_id); |
| ukm_recorder.ExpectEntryMetric( |
| entries.front(), |
| ukm::builders::PrerenderPageLoad::kTriggeredPrerenderName, 1); |
| } |
| |
| // Tests that background color in a prerendered page does not affect |
| // the primary page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ColorSchemeDarkInNonPrimaryPage) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/color-scheme-dark.html"); |
| |
| // Expect initial page background color to be white. |
| content::BackgroundColorChangeWaiter empty_page_background_waiter( |
| web_contents()); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| // Wait for the page background to change to white. |
| empty_page_background_waiter.Wait(); |
| |
| { |
| // Now set up a mock observer for BackgroundColorChanged, to test if the |
| // mocked observer executes BackgroundColorChanged for the prerendered page. |
| testing::NiceMock<MockWebContentsObserver> background_color_observer( |
| web_contents()); |
| EXPECT_CALL(background_color_observer, OnBackgroundColorChanged()) |
| .Times(Exactly(0)); |
| |
| AddPrerender(kPrerenderingUrl); |
| } |
| |
| content::BackgroundColorChangeWaiter prerendered_page_background_waiter( |
| web_contents()); |
| // Now set up a mock observer for BackgroundColorChanged, to test if the |
| // mocked observer executes BackgroundColorChanged when activating the |
| // prerendered page. |
| testing::NiceMock<MockWebContentsObserver> background_color_observer( |
| web_contents()); |
| EXPECT_CALL(background_color_observer, OnBackgroundColorChanged()) |
| .Times(Exactly(1)); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| // Wait for the page background to change. |
| prerendered_page_background_waiter.Wait(); |
| } |
| |
| // Tests that theme color in a prerendered page does not affect |
| // the primary page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| ThemeColorSchemeChangeInNonPrimaryPage) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/theme_color.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| { |
| // Now set up a mock observer for DidChangeThemeColor, to test if the |
| // mocked observer executes DidChangeThemeColor for the prerendered page. |
| testing::NiceMock<MockWebContentsObserver> theme_color_observer( |
| web_contents()); |
| EXPECT_CALL(theme_color_observer, DidChangeThemeColor()).Times(Exactly(0)); |
| |
| AddPrerender(kPrerenderingUrl); |
| } |
| |
| content::ThemeChangeWaiter theme_change_waiter(web_contents()); |
| testing::NiceMock<MockWebContentsObserver> theme_color_observer( |
| web_contents()); |
| EXPECT_CALL(theme_color_observer, DidChangeThemeColor()).Times(Exactly(1)); |
| |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| theme_change_waiter.Wait(); |
| } |
| |
| // Tests that text autosizer works per page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| TextAutosizerInfoChangeInNonPrimaryPage) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| RenderFrameHostImpl* primary_frame_host = current_frame_host(); |
| blink::mojom::TextAutosizerPageInfo primary_page_info = |
| primary_frame_host->GetPage().text_autosizer_page_info(); |
| |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerender_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| |
| // Update the autosizer page info in the prerendering page. |
| blink::mojom::TextAutosizerPageInfo prerender_page_info( |
| /*main_frame_width=*/320, |
| /*main_frame_layout_width=*/480, |
| /*device_scale_adjustment=*/1.f); |
| prerender_frame_host->TextAutosizerPageInfoChanged( |
| prerender_page_info.Clone()); |
| |
| // Only the prerendering page's autosizer info should be updated. |
| EXPECT_TRUE(prerender_page_info.Equals( |
| prerender_frame_host->GetPage().text_autosizer_page_info())); |
| EXPECT_FALSE(prerender_page_info.Equals( |
| primary_frame_host->GetPage().text_autosizer_page_info())); |
| |
| // After being activated, the prerendered page becomes the primary page, so |
| // the page info of the primary page should equal `prerender_page_info`. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_TRUE(prerender_page_info.Equals( |
| current_frame_host()->GetPage().text_autosizer_page_info())); |
| } |
| |
| // Check that the prerendered page window.name is maintained after activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| VerifyFrameNameMaintainedAfterActivation) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| // 1. Load initiator page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // 2. Load prerender. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| |
| // 3. Set window.name. |
| ASSERT_TRUE( |
| ExecJs(prerendered_render_frame_host, "window.name = 'prerender_page'")); |
| |
| EXPECT_EQ(prerendered_render_frame_host->GetFrameName(), "prerender_page"); |
| EXPECT_EQ(current_frame_host()->GetFrameName(), ""); |
| |
| // 4. Activate prerender. |
| TestNavigationManager activation_manager(web_contents(), kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_prerendered_page_activation()); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| // 5. Ensure that the window.name is preserved. |
| EXPECT_EQ(current_frame_host()->GetFrameName(), "prerender_page"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivateWhileReloadingSubframe) { |
| const char kSubframePath[] = "/title1.html"; |
| net::test_server::ControllableHttpResponse first_response( |
| embedded_test_server(), kSubframePath); |
| net::test_server::ControllableHttpResponse second_response( |
| embedded_test_server(), kSubframePath); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const GURL kInitialUrl = embedded_test_server()->GetURL("/empty.html"); |
| const GURL kPrerenderingUrl = |
| embedded_test_server()->GetURL("/page_with_iframe.html"); |
| const GURL kSubframeUrl = embedded_test_server()->GetURL(kSubframePath); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| AddPrerenderAsync(kPrerenderingUrl); |
| |
| // Handle a response for the subframe main resource. |
| first_response.WaitForRequest(); |
| first_response.Send(net::HTTP_OK, ""); |
| first_response.Done(); |
| |
| // Now we can wait for the prerendering navigation finishes. |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| WaitForPrerenderLoadCompleted(host_id); |
| |
| RenderFrameHostImpl* prerender_rfh = GetPrerenderedMainFrameHost(host_id); |
| RenderFrameHostImpl* child_rfh = |
| prerender_rfh->child_at(0)->current_frame_host(); |
| EXPECT_EQ(child_rfh->GetLastCommittedURL(), kSubframeUrl); |
| |
| // Reload the iframe. |
| EXPECT_TRUE(ExecJs(child_rfh, "window.location.reload();")); |
| second_response.WaitForRequest(); |
| // Do not finish the second response to execute activation during the reload. |
| |
| // Ensure that activation works even while the iframe is under the reload. |
| TestNavigationObserver nav_observer(web_contents()); |
| EXPECT_TRUE( |
| ExecJs(web_contents(), JsReplace("location = $1", kPrerenderingUrl))); |
| second_response.Send(net::HTTP_OK, ""); |
| second_response.Done(); |
| nav_observer.WaitForNavigationFinished(); |
| } |
| |
| // Check that the inactive RFH shouldn't update UserActivation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DoNotUpdateUserActivationState) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| |
| // 1. Load initiator page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // 2. Load prerender. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_rfh = GetPrerenderedMainFrameHost(host_id); |
| |
| EXPECT_FALSE( |
| current_frame_host()->frame_tree_node()->HasStickyUserActivation()); |
| EXPECT_FALSE(prerendered_rfh->frame_tree_node()->HasStickyUserActivation()); |
| |
| // 3. Try to set the user activation bits to the prerendered RFH. |
| prerendered_rfh->UpdateUserActivationState( |
| blink::mojom::UserActivationUpdateType::kNotifyActivation, |
| blink::mojom::UserActivationNotificationType::kTest); |
| EXPECT_FALSE(prerendered_rfh->frame_tree_node()->HasStickyUserActivation()); |
| EXPECT_FALSE(prerendered_rfh->HasTransientUserActivation()); |
| |
| EXPECT_FALSE( |
| current_frame_host()->frame_tree_node()->HasStickyUserActivation()); |
| EXPECT_FALSE( |
| current_frame_host()->frame_tree_node()->HasTransientUserActivation()); |
| |
| // 4. Set the user activation bits to the primary RFH. |
| current_frame_host()->UpdateUserActivationState( |
| blink::mojom::UserActivationUpdateType::kNotifyActivation, |
| blink::mojom::UserActivationNotificationType::kTest); |
| EXPECT_TRUE( |
| current_frame_host()->frame_tree_node()->HasStickyUserActivation()); |
| |
| EXPECT_FALSE(prerendered_rfh->frame_tree_node()->HasStickyUserActivation()); |
| } |
| |
| // Tests that prerendering is cancelled when a mixed content subframe is |
| // detected. |
| // TODO(crbug.com/1282218): Flaky on Linux and Windows |
| #if defined(OS_LINUX) || defined(OS_WIN) |
| #define MAYBE_MixedContent DISABLED_MixedContent |
| #else |
| #define MAYBE_MixedContent MixedContent |
| #endif |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MAYBE_MixedContent) { |
| 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. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerendered_rfh = GetPrerenderedMainFrameHost(host_id); |
| DCHECK(prerendered_rfh); |
| EXPECT_TRUE(AddTestUtilJS(prerendered_rfh)); |
| |
| test::PrerenderHostObserver host_observer(*web_contents(), host_id); |
| |
| // Make a mixed content iframe. |
| EXPECT_TRUE( |
| ExecJs(prerendered_rfh, |
| "add_iframe_async('http://a.test/empty.html?prerendering')", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(prerender_helper()->GetHostForUrl(kPrerenderingUrl), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus.SpeculationRule", |
| PrerenderHost::FinalStatus::kMixedContent, 1); |
| } |
| |
| // Check that the Content-Security-Policy set via HTTP header applies after the |
| // activation. This test verifies that that the web sandbox flags value is none. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| ActivatePageWithCspHeaderFrameSrc) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/set-header?Content-Security-Policy: frame-src 'none'"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| |
| // Check that CSP was set on the prerendered page prior to activation. |
| { |
| const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_pre = |
| prerendered_render_frame_host->policy_container_host() |
| ->policies() |
| .content_security_policies; |
| EXPECT_EQ(1u, root_csp_pre.size()); |
| EXPECT_EQ("frame-src 'none'", root_csp_pre[0]->header->header_value); |
| EXPECT_EQ(prerendered_render_frame_host->active_sandbox_flags(), |
| network::mojom::WebSandboxFlags::kNone); |
| } |
| |
| TestNavigationManager activation_manager(web_contents(), kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_prerendered_page_activation()); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| // Check that CSP was set on the prerendered page after activation. |
| { |
| const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_post = |
| current_frame_host() |
| ->policy_container_host() |
| ->policies() |
| .content_security_policies; |
| EXPECT_EQ(1u, root_csp_post.size()); |
| EXPECT_EQ("frame-src 'none'", root_csp_post[0]->header->header_value); |
| EXPECT_EQ(current_frame_host()->active_sandbox_flags(), |
| network::mojom::WebSandboxFlags::kNone); |
| EXPECT_EQ(static_cast<WebContentsImpl*>(web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->active_sandbox_flags(), |
| network::mojom::WebSandboxFlags::kNone); |
| } |
| } |
| |
| // Check that the Content-Security-Policy set via HTTP header applies after the |
| // activation. This test verifies that that the web sandbox flags value is set |
| // to allow scripts. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| ActivatePageWithCspHeaderSandboxFlags) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/set-header?Content-Security-Policy: sandbox allow-scripts"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| |
| // Check that CSP was set on the prerendered page prior to activation. |
| { |
| const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_pre = |
| prerendered_render_frame_host->policy_container_host() |
| ->policies() |
| .content_security_policies; |
| EXPECT_EQ(1u, root_csp_pre.size()); |
| EXPECT_EQ("sandbox allow-scripts", root_csp_pre[0]->header->header_value); |
| EXPECT_EQ(prerendered_render_frame_host->active_sandbox_flags(), |
| network::mojom::WebSandboxFlags::kAll & |
| ~network::mojom::WebSandboxFlags::kScripts & |
| ~network::mojom::WebSandboxFlags::kAutomaticFeatures); |
| } |
| |
| TestNavigationManager activation_manager(web_contents(), kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_prerendered_page_activation()); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), kPrerenderingUrl); |
| |
| // Check that CSP was set on the prerendered page after activation. |
| { |
| const std::vector<network::mojom::ContentSecurityPolicyPtr>& root_csp_post = |
| current_frame_host() |
| ->policy_container_host() |
| ->policies() |
| .content_security_policies; |
| EXPECT_EQ(1u, root_csp_post.size()); |
| EXPECT_EQ("sandbox allow-scripts", root_csp_post[0]->header->header_value); |
| EXPECT_EQ(current_frame_host()->active_sandbox_flags(), |
| network::mojom::WebSandboxFlags::kAll & |
| ~network::mojom::WebSandboxFlags::kScripts & |
| ~network::mojom::WebSandboxFlags::kAutomaticFeatures); |
| EXPECT_EQ(static_cast<WebContentsImpl*>(web_contents()) |
| ->GetPrimaryFrameTree() |
| .root() |
| ->active_sandbox_flags(), |
| network::mojom::WebSandboxFlags::kAll & |
| ~network::mojom::WebSandboxFlags::kScripts & |
| ~network::mojom::WebSandboxFlags::kAutomaticFeatures); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, VerifyPrerenderProcessVisibility) { |
| // Navigate the primary main frame to an initial page. |
| const GURL kInitialUrl = GetUrl("/empty.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerender_frame_host = GetPrerenderedMainFrameHost(host_id); |
| RenderProcessHost* prerender_process_host = |
| prerender_frame_host->GetProcess(); |
| ASSERT_NE(prerender_frame_host, nullptr); |
| // Ensure that a prerender process is invisible in |
| // ChildProcessLauncherPriority. This will put prerender processes in lower |
| // priority compared to other active processes. (See |
| // https://crbug.com/1211665) |
| EXPECT_TRUE(prerender_process_host->IsProcessBackgrounded()); |
| |
| // Activate the prerendered page. |
| TestNavigationManager activation_manager(web_contents(), kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_prerendered_page_activation()); |
| // Expect the change in the ChildProcessLauncherPriority to become visible. |
| EXPECT_FALSE(prerender_process_host->IsProcessBackgrounded()); |
| } |
| |
| class PrerenderPurposePrefetchBrowserTest : public PrerenderBrowserTest { |
| public: |
| PrerenderPurposePrefetchBrowserTest() = default; |
| ~PrerenderPurposePrefetchBrowserTest() override = default; |
| |
| void SetUp() override { |
| ssl_server().RegisterRequestHandler( |
| base::BindRepeating(&HandleCorsRequest)); |
| PrerenderBrowserTest::SetUp(); |
| } |
| |
| static std::unique_ptr<net::test_server::HttpResponse> HandleCorsRequest( |
| const net::test_server::HttpRequest& request) { |
| // The "Purpose: prefetch" header shouldn't cause CORS preflights. |
| EXPECT_NE(request.method_string, "OPTIONS"); |
| |
| // Ignore if the request is not cross origin. |
| // |
| // Note: Checking the origin of `request.GetURL()` doesn't work here because |
| // the host part of the URL is translated (e.g., "a.test" to "127.0.0.1") |
| // based on the host resolver rule before this point. |
| if (request.relative_url.find("cors") == std::string::npos) |
| return nullptr; |
| |
| // Serves a fake response with the ACAO header. |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("Access-Control-Allow-Origin", "*"); |
| response->set_code(net::HTTP_OK); |
| response->set_content(""); |
| response->set_content_type("text/plain"); |
| return response; |
| } |
| |
| bool TestPurposePrefetchHeader(const GURL& url) { |
| net::test_server::HttpRequest::HeaderMap headers = GetRequestHeaders(url); |
| auto it = headers.find("Purpose"); |
| if (it == headers.end()) |
| return false; |
| EXPECT_EQ("prefetch", it->second); |
| return true; |
| } |
| }; |
| |
| // Tests that a request for the initial prerender navigation has the |
| // "Purpose: prefetch" header. |
| // TODO(nhiroki): Move this test to WPT. |
| IN_PROC_BROWSER_TEST_F(PrerenderPurposePrefetchBrowserTest, InitialNavigation) { |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html"))); |
| |
| // Start prerendering. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| AddPrerender(kPrerenderingUrl); |
| |
| // The prerender request should have the header. |
| EXPECT_TRUE(TestPurposePrefetchHeader(kPrerenderingUrl)); |
| } |
| |
| // Tests that a redirected request for the initial prerender navigation has the |
| // "Purpose: prefetch" header. |
| // TODO(nhiroki): Move this test to WPT. |
| IN_PROC_BROWSER_TEST_F(PrerenderPurposePrefetchBrowserTest, |
| RedirectionOnInitialNavigation) { |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html"))); |
| |
| // 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); |
| 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]); |
| |
| // Both the initial request and the redirected request should have the |
| // "Purpose: prefetch" header. |
| EXPECT_TRUE(TestPurposePrefetchHeader(kPrerenderingUrl)); |
| EXPECT_TRUE(TestPurposePrefetchHeader(kRedirectedUrl)); |
| } |
| |
| // Tests that requests from a prerendered page have the "Purpose: prefetch" |
| // header. |
| // TODO(nhiroki): Move this test to WPT. |
| IN_PROC_BROWSER_TEST_F(PrerenderPurposePrefetchBrowserTest, ResourceRequests) { |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html"))); |
| |
| // Start prerendering. |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/purpose_prefetch_header.html"); |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostWrapper prerender_main_frame( |
| GetPrerenderedMainFrameHost(host_id)); |
| |
| // The prerender request should have the "Purpose: prefetch" header. |
| TestPurposePrefetchHeader(kPrerenderingUrl); |
| |
| // Issue iframe and subresource requests in the prerendered page. |
| EXPECT_TRUE(ExecJs(prerender_main_frame.get(), "run('before');", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| |
| // Requests from the prerenderered page should have the header. |
| EXPECT_TRUE(TestPurposePrefetchHeader( |
| GetUrl("/prerender/purpose_prefetch_header_iframe.html?before"))); |
| EXPECT_TRUE( |
| TestPurposePrefetchHeader(GetUrl("/prerender/missing.jpg?before"))); |
| EXPECT_TRUE( |
| TestPurposePrefetchHeader(GetUrl("/prerender/missing.txt?before"))); |
| EXPECT_TRUE(TestPurposePrefetchHeader(GetUrl("/empty.html?before"))); |
| EXPECT_TRUE(TestPurposePrefetchHeader( |
| GetUrl("/prerender/iframe-missing.jpg?before"))); |
| EXPECT_TRUE(TestPurposePrefetchHeader( |
| GetUrl("/prerender/iframe-missing.txt?before"))); |
| |
| // Issue a cross-origin subresource request in the prerendered page. The |
| // request should have the header. |
| GURL cross_origin_url1 = |
| GetCrossOriginUrl("/prerender/cors-missing.txt?before"); |
| EXPECT_TRUE(ExecJs(prerender_main_frame.get(), |
| "request('" + cross_origin_url1.spec() + "');", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| EXPECT_TRUE(TestPurposePrefetchHeader(cross_origin_url1)); |
| |
| // Activate the prerendered page. |
| TestNavigationManager activation_manager(web_contents(), kPrerenderingUrl); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| activation_manager.WaitForNavigationFinished(); |
| EXPECT_TRUE(activation_manager.was_prerendered_page_activation()); |
| |
| // Issue iframe and subresource requests in the activated page. |
| EXPECT_TRUE(ExecJs(prerender_main_frame.get(), "run('after');", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| |
| // Requests from the activated page should not have the header. |
| EXPECT_FALSE(TestPurposePrefetchHeader( |
| GetUrl("/prerender/purpose_prefetch_header_iframe.html?after"))); |
| EXPECT_FALSE( |
| TestPurposePrefetchHeader(GetUrl("/prerender/missing.jpg?after"))); |
| EXPECT_FALSE( |
| TestPurposePrefetchHeader(GetUrl("/prerender/missing.txt?after"))); |
| EXPECT_FALSE(TestPurposePrefetchHeader(GetUrl("/empty.html?after"))); |
| EXPECT_FALSE( |
| TestPurposePrefetchHeader(GetUrl("/prerender/iframe-missing.jpg?after"))); |
| EXPECT_FALSE( |
| TestPurposePrefetchHeader(GetUrl("/prerender/iframe-missing.txt?after"))); |
| |
| // Issue a cross-origin subresource request in the activated page. The request |
| // should not have the header. |
| GURL cross_origin_url2 = |
| GetCrossOriginUrl("/prerender/cors-missing.txt?after"); |
| EXPECT_TRUE(ExecJs(prerender_main_frame.get(), |
| "request('" + cross_origin_url2.spec() + "');", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| EXPECT_FALSE(TestPurposePrefetchHeader(cross_origin_url2)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, EnterFullscreen) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerendering"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerendered_rfh = GetPrerenderedMainFrameHost(host_id); |
| |
| // We should disallow to enter Fullscreen by the inactive RFH. |
| prerendered_rfh->EnterFullscreen( |
| blink::mojom::FullscreenOptions::New(), |
| base::BindOnce([](bool value) { EXPECT_FALSE(value); })); |
| EXPECT_FALSE(web_contents_impl()->IsFullscreen()); |
| } |
| |
| namespace { |
| class TestJavaScriptDialogManager : public JavaScriptDialogManager, |
| public WebContentsDelegate { |
| public: |
| TestJavaScriptDialogManager() = default; |
| ~TestJavaScriptDialogManager() override = default; |
| |
| // WebContentsDelegate overrides |
| JavaScriptDialogManager* GetJavaScriptDialogManager( |
| WebContents* source) override { |
| return this; |
| } |
| |
| // JavaScriptDialogManager overrides |
| void RunJavaScriptDialog(WebContents* web_contents, |
| RenderFrameHost* render_frame_host, |
| JavaScriptDialogType dialog_type, |
| const std::u16string& message_text, |
| const std::u16string& default_prompt_text, |
| DialogClosedCallback callback, |
| bool* did_suppress_message) override {} |
| void RunBeforeUnloadDialog(WebContents* web_contents, |
| RenderFrameHost* render_frame_host, |
| bool is_reload, |
| DialogClosedCallback callback) override {} |
| void CancelDialogs(WebContents* web_contents, bool reset_state) override { |
| cancel_dialogs_called_ = true; |
| } |
| |
| bool cancel_dialogs_called() { return cancel_dialogs_called_; } |
| |
| private: |
| bool cancel_dialogs_called_ = false; |
| }; |
| |
| class PrerenderWithRenderDocumentBrowserTest : public PrerenderBrowserTest { |
| public: |
| PrerenderWithRenderDocumentBrowserTest() { |
| InitAndEnableRenderDocumentFeature( |
| &feature_list_, |
| GetRenderDocumentLevelName(RenderDocumentLevel::kSubframe)); |
| } |
| ~PrerenderWithRenderDocumentBrowserTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F( |
| PrerenderWithRenderDocumentBrowserTest, |
| ModalDialogShouldNotBeDismissedAfterPrerenderSubframeNavigation) { |
| const GURL kPrerenderingUrl = GetUrl("/title1.html"); |
| const GURL kSubframeUrl1 = GetUrl("/empty.html"); |
| const GURL kSubframeUrl2 = GetUrl("/title2.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), GetUrl("/empty.html"))); |
| |
| // Start prerendering. |
| int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerender_rfh = GetPrerenderedMainFrameHost(host_id); |
| DCHECK(prerender_rfh); |
| AddTestUtilJS(prerender_rfh); |
| |
| // Add subframe in prerendering page. |
| ASSERT_TRUE( |
| ExecJs(prerender_rfh, JsReplace("add_iframe($1)", kSubframeUrl1))); |
| |
| // Setup test dialog manager and create dialog. |
| TestJavaScriptDialogManager dialog_manager; |
| web_contents_impl()->SetDelegate(&dialog_manager); |
| web_contents_impl()->RunJavaScriptDialog( |
| web_contents_impl()->GetMainFrame(), u"", u"", |
| JAVASCRIPT_DIALOG_TYPE_ALERT, false, base::NullCallback()); |
| |
| // Navigate subframe (with render document enabled, this should cause a RFH |
| // swap). |
| TestNavigationManager subframe_nav_manager(web_contents(), kSubframeUrl2); |
| ASSERT_TRUE(ExecJs( |
| prerender_rfh, |
| JsReplace("document.querySelector('iframe').src = $1", kSubframeUrl2))); |
| subframe_nav_manager.WaitForNavigationFinished(); |
| |
| // We should not dismiss dialogs when the prerender's subframe navigates and |
| // swaps its RFH. |
| EXPECT_FALSE(dialog_manager.cancel_dialogs_called()); |
| |
| // Clean up test dialog manager. |
| web_contents_impl()->SetDelegate(nullptr); |
| web_contents_impl()->SetJavaScriptDialogManagerForTesting(nullptr); |
| } |
| |
| // Tests that NavigationHandle::GetNavigatingFrameType() returns the correct |
| // type in prerendering and after activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NavigationHandleFrameType) { |
| { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_TRUE(navigation_handle->IsInPrimaryMainFrame()); |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kPrimaryMainFrame); |
| })); |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| } |
| |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| { |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_TRUE(navigation_handle->IsInPrerenderedMainFrame()); |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kPrerenderMainFrame); |
| })); |
| // Start prerendering. |
| AddPrerender(kPrerenderingUrl); |
| } |
| |
| { |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_TRUE(navigation_handle->IsInPrimaryMainFrame()); |
| EXPECT_TRUE(navigation_handle->IsPrerenderedPageActivation()); |
| DCHECK_EQ(navigation_handle->GetNavigatingFrameType(), |
| FrameType::kPrimaryMainFrame); |
| })); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| } |
| } |
| |
| // Tests that NavigationHandle::IsRendererInitiated() returns RendererInitiated |
| // = true correctly. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| NavigationHandleIsRendererInitiatedTrue) { |
| const GURL kInitialUrl = GetUrl("/empty.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender"); |
| { |
| DidFinishNavigationObserver observer( |
| web_contents(), |
| base::BindLambdaForTesting([](NavigationHandle* navigation_handle) { |
| EXPECT_TRUE(navigation_handle->IsInPrerenderedMainFrame()); |
| EXPECT_TRUE(navigation_handle->IsRendererInitiated()); |
| })); |
| // Start prerendering. |
| AddPrerender(kPrerenderingUrl); |
| } |
| NavigatePrimaryPage(kPrerenderingUrl); |
| } |
| |
| } // namespace content |