| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/barrier_closure.h" |
| #include "base/base_switches.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/optional.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 "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/frame_tree_node.h" |
| #include "content/browser/renderer_host/input/synthetic_tap_gesture.h" |
| #include "content/browser/renderer_host/render_frame_host_impl.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_document_host_user_data.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.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/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/browser_interface_broker.mojom.h" |
| #include "ui/shell_dialogs/select_file_dialog.h" |
| #include "ui/shell_dialogs/select_file_dialog_factory.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| namespace { |
| |
| enum class BackForwardCacheType { |
| kDisabled, |
| kEnabled, |
| kEnabledWithSameSite, |
| }; |
| |
| std::string ToString(const testing::TestParamInfo<BackForwardCacheType>& info) { |
| switch (info.param) { |
| case BackForwardCacheType::kDisabled: |
| return "Disabled"; |
| case BackForwardCacheType::kEnabled: |
| return "Enabled"; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| return "EnabledWithSameSite"; |
| } |
| } |
| |
| RenderFrameHost* FindRenderFrameHost(RenderFrameHost& root, const GURL& url) { |
| std::vector<RenderFrameHost*> rfhs = root.GetFramesInSubtree(); |
| for (auto* rfh : rfhs) { |
| if (rfh->GetLastCommittedURL() == url) |
| return rfh; |
| } |
| return nullptr; |
| } |
| |
| // Example class which inherits the RenderDocumentHostUserData, all the data is |
| // associated to the lifetime of the document. |
| class DocumentData : public RenderDocumentHostUserData<DocumentData> { |
| public: |
| ~DocumentData() override = default; |
| |
| base::WeakPtr<DocumentData> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| private: |
| explicit DocumentData(RenderFrameHost* render_frame_host) {} |
| |
| friend class content::RenderDocumentHostUserData<DocumentData>; |
| |
| base::WeakPtrFactory<DocumentData> weak_ptr_factory_{this}; |
| |
| RENDER_DOCUMENT_HOST_USER_DATA_KEY_DECL(); |
| }; |
| |
| RENDER_DOCUMENT_HOST_USER_DATA_KEY_IMPL(DocumentData) |
| |
| class PrerenderBrowserTest : public ContentBrowserTest { |
| public: |
| using LifecycleStateImpl = RenderFrameHostImpl::LifecycleStateImpl; |
| |
| PrerenderBrowserTest() { |
| prerender_helper_ = |
| std::make_unique<test::PrerenderTestHelper>(base::BindRepeating( |
| &PrerenderBrowserTest::web_contents, base::Unretained(this))); |
| } |
| ~PrerenderBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ssl_server_.AddDefaultHandlers(GetTestDataFilePath()); |
| ssl_server_.SetSSLConfig( |
| net::test_server::EmbeddedTestServer::CERT_TEST_NAMES); |
| prerender_helper_->SetUpOnMainThread(&ssl_server_); |
| ASSERT_TRUE(ssl_server_.Start()); |
| } |
| |
| void TearDownOnMainThread() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| EXPECT_TRUE(ssl_server_.ShutdownAndWaitUntilComplete()); |
| } |
| |
| // Waits until the request count for `url` reaches `count`. |
| void WaitForRequest(const GURL& url, int count) { |
| prerender_helper_->WaitForRequest(url, count); |
| } |
| |
| int AddPrerender(const GURL& prerendering_url) { |
| return prerender_helper_->AddPrerender(prerendering_url); |
| } |
| |
| void NavigatePrimaryPage(const GURL& url) { |
| prerender_helper_->NavigatePrimaryPage(url); |
| } |
| |
| int GetHostForUrl(const GURL& url) { |
| return prerender_helper_->GetHostForUrl(url); |
| } |
| |
| RenderFrameHostImpl* GetPrerenderedMainFrameHost(int host_id) { |
| return static_cast<RenderFrameHostImpl*>( |
| prerender_helper_->GetPrerenderedMainFrameHost(host_id)); |
| } |
| |
| void NavigatePrerenderedPage(int host_id, const GURL& url) { |
| return prerender_helper_->NavigatePrerenderedPage(host_id, url); |
| } |
| |
| bool HasHostForUrl(const GURL& url) { |
| int host_id = GetHostForUrl(url); |
| return host_id != RenderFrameHost::kNoFrameTreeNodeId; |
| } |
| |
| void WaitForPrerenderLoadCompleted(int host_id) { |
| prerender_helper_->WaitForPrerenderLoadCompletion(host_id); |
| } |
| |
| void WaitForPrerenderLoadCompletion(const GURL& url) { |
| prerender_helper_->WaitForPrerenderLoadCompletion(url); |
| } |
| |
| GURL GetUrl(const std::string& path) { |
| return ssl_server_.GetURL("a.test", path); |
| } |
| |
| GURL GetCrossOriginUrl(const std::string& path) { |
| return ssl_server_.GetURL("b.test", path); |
| } |
| |
| int GetRequestCount(const GURL& url) { |
| return prerender_helper_->GetRequestCount(url); |
| } |
| |
| WebContents* web_contents() const { return shell()->web_contents(); } |
| |
| WebContentsImpl* web_contents_impl() const { |
| return static_cast<WebContentsImpl*>(web_contents()); |
| } |
| |
| RenderFrameHostImpl* current_frame_host() { |
| return web_contents_impl()->GetMainFrame(); |
| } |
| |
| void TestHostPrerenderingState(const GURL& prerender_url) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // The initial page should not be in prerendered state. |
| RenderFrameHostImpl* initiator_render_frame_host = current_frame_host(); |
| EXPECT_EQ(initiator_render_frame_host->frame_tree()->type(), |
| FrameTree::Type::kPrimary); |
| EXPECT_EQ(initiator_render_frame_host->lifecycle_state(), |
| LifecycleStateImpl::kActive); |
| |
| // Start a prerender. |
| AddPrerender(prerender_url); |
| |
| EXPECT_TRUE(prerender_helper_->VerifyPrerenderingState(prerender_url)); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(prerender_url); |
| EXPECT_EQ(web_contents()->GetURL(), prerender_url); |
| |
| // The activated page should no longer be in the prerendering state. |
| RenderFrameHostImpl* navigated_render_frame_host = current_frame_host(); |
| // The new page shouldn't be in the prerendering state. |
| std::vector<RenderFrameHost*> frames = |
| navigated_render_frame_host->GetFramesInSubtree(); |
| for (auto* frame : frames) { |
| auto* rfhi = static_cast<RenderFrameHostImpl*>(frame); |
| // All the subframes should be transitioned to LifecycleStateImpl::kActive |
| // state after activation. |
| EXPECT_EQ(rfhi->lifecycle_state(), |
| RenderFrameHostImpl::LifecycleStateImpl::kActive); |
| EXPECT_FALSE(rfhi->frame_tree()->is_prerendering()); |
| } |
| } |
| |
| test::PrerenderTestHelper* prerender_helper() { |
| return prerender_helper_.get(); |
| } |
| |
| private: |
| void SetUpCommandLine(base::CommandLine* command_line) final { |
| // Useful for testing CSP:prefetch-src |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kEnableExperimentalWebPlatformFeatures); |
| } |
| net::test_server::EmbeddedTestServer ssl_server_{ |
| net::test_server::EmbeddedTestServer::TYPE_HTTPS}; |
| |
| std::unique_ptr<test::PrerenderTestHelper> prerender_helper_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LinkRelPrerender) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `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)); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // The prerender host should be consumed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Activating the prerendered page should not issue a request. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LinkRelPrerender_Multiple) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl1 = GetUrl("/empty.html?1"); |
| const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2"); |
| |
| // TODO(https://crbug.com/1186893): PrerenderHost is not deleted when the |
| // page enters BackForwardCache, though it should be. While this functionality |
| // is not implemented, disable BackForwardCache for testing and wait for the |
| // old RenderFrameHost to be deleted after we navigate away from it. |
| DisableBackForwardCacheForTesting( |
| web_contents(), BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `kPrerenderingUrl1` and |
| // `kPrerenderingUrl2`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl1), 0); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl2), 0); |
| AddPrerender(kPrerenderingUrl1); |
| AddPrerender(kPrerenderingUrl2); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1); |
| |
| // Prerender hosts for `kPrerenderingUrl1` and `kPrerenderingUrl2` should be |
| // registered. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl1)); |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl2)); |
| |
| RenderFrameDeletedObserver delete_observer_rfh( |
| web_contents()->GetMainFrame()); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl2); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl2); |
| |
| // Other PrerenderHost instances are deleted with the RFH. |
| delete_observer_rfh.WaitUntilDeleted(); |
| |
| // The prerender hosts should be consumed or destroyed for activation. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl1)); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl2)); |
| |
| // Activating the prerendered page should not issue a request. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LinkRelPrerender_Duplicate) { |
| const GURL kInitialUrl = GetUrl("/prerender/duplicate_prerenders.html"); |
| const GURL kPrerenderingUrl1 = GetUrl("/empty.html?1"); |
| const GURL kPrerenderingUrl2 = GetUrl("/empty.html?2"); |
| |
| // TODO(https://crbug.com/1186893): PrerenderHost is not deleted when the |
| // page enters BackForwardCache, though it should be. While this functionality |
| // is not implemented, disable BackForwardCache for testing and wait for the |
| // old RenderFrameHost to be deleted after we navigate away from it. |
| DisableBackForwardCacheForTesting( |
| web_contents(), BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING); |
| |
| // Navigate to a page that initiates prerendering for `kPrerenderingUrl1` |
| // twice. The second prerendering request should be ignored. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Wait until the completion of prerendering. |
| WaitForPrerenderLoadCompletion(kPrerenderingUrl1); |
| WaitForPrerenderLoadCompletion(kPrerenderingUrl2); |
| |
| // Requests should be issued once per prerendering URL. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1); |
| |
| // Prerender hosts for `kPrerenderingUrl1` and `kPrerenderingUrl2` should be |
| // registered. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl1)); |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl2)); |
| |
| RenderFrameDeletedObserver delete_observer_rfh( |
| web_contents()->GetMainFrame()); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl1); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl1); |
| |
| // Other PrerenderHost instances are deleted with the RFH. |
| delete_observer_rfh.WaitUntilDeleted(); |
| |
| // The prerender hosts should be consumed or destroyed for activation. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl1)); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl2)); |
| |
| // Activating the prerendered page should not issue a request. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl1), 1); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl2), 1); |
| } |
| |
| // Regression test for https://crbug.com/1194865. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CloseOnPrerendering) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `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(); |
| } |
| |
| // Tests that non-http(s) schemes are disallowed for prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, HttpToBlobUrl) { |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Generate a Blob page and obtain a URL for the Blob page. |
| const char kCreateBlobUrlScript[] = |
| "URL.createObjectURL(" |
| "new Blob([\"<h1>hello blob</h1>\"], { type: 'text/html' }));"; |
| const std::string blob_url = |
| EvalJs(web_contents(), kCreateBlobUrlScript).ExtractString(); |
| const GURL blob_gurl(blob_url); |
| |
| // Add <link rel=prerender> that will prerender the Blob page. |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| EXPECT_TRUE(ExecJs(web_contents(), JsReplace("add_prerender($1)", blob_url))); |
| observer.WaitForTrigger(blob_gurl); |
| |
| // A prerender host for the URL should not be registered. |
| EXPECT_FALSE(HasHostForUrl(blob_gurl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kInvalidSchemeNavigation, 1); |
| } |
| |
| // Tests that non-http(s) schemes are disallowed for prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, BlobUrlToBlobUrl) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| // This test can not use `about:blank` as the initial url because created |
| // blobs inside the page are populated as opaque and blob to blob prerendering |
| // are alerted as cross-origin prerendering. |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Navigate to a dynamically constructed Blob page. |
| const char kCreateBlobUrlScript[] = |
| "URL.createObjectURL(new Blob([\"<script>" |
| "function add_prerender(url) {" |
| " const link = document.createElement('link');" |
| " link.rel = 'prerender';" |
| " link.href= url;" |
| " document.head.appendChild(link);" |
| "}" |
| "</script>\"], { type: 'text/html' }));"; |
| const std::string initial_blob_url = |
| EvalJs(web_contents(), kCreateBlobUrlScript).ExtractString(); |
| ASSERT_TRUE(NavigateToURL(shell(), GURL(initial_blob_url))); |
| |
| // Create another Blob URL inside the Blob page. |
| const std::string blob_url = |
| EvalJs(web_contents(), kCreateBlobUrlScript).ExtractString(); |
| const GURL blob_gurl(blob_url); |
| |
| // Add <link rel=prerender> that will prerender the Blob page. |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| EXPECT_TRUE(ExecJs(web_contents(), JsReplace("add_prerender($1)", blob_url))); |
| observer.WaitForTrigger(blob_gurl); |
| |
| // A prerender host for the URL should not be registered. |
| EXPECT_FALSE(HasHostForUrl(blob_gurl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kInvalidSchemeNavigation, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SameOriginRedirection) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start prerendering a URL that causes same-origin redirection. |
| const GURL kRedirectedUrl = GetUrl("/empty.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/server-redirect?" + kRedirectedUrl.spec()); |
| AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ(GetRequestCount(kRedirectedUrl), 1); |
| |
| // The prerender host should be registered for the initial request URL, not |
| // the redirected URL. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_FALSE(HasHostForUrl(kRedirectedUrl)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CrossOriginRedirection) { |
| base::HistogramTester histogram_tester; |
| |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.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"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/server-redirect?" + kRedirectedUrl.spec()); |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents_impl()); |
| EXPECT_TRUE( |
| ExecJs(web_contents(), JsReplace("add_prerender($1)", kPrerenderingUrl))); |
| registry_observer.WaitForTrigger(kPrerenderingUrl); |
| int host_id = GetHostForUrl(kPrerenderingUrl); |
| test::PrerenderHostObserver host_observer(*web_contents_impl(), host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ(GetRequestCount(kRedirectedUrl), 0); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_FALSE(HasHostForUrl(kRedirectedUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kCrossOriginRedirect, 1); |
| } |
| |
| // Makes sure that activation on navigation for an iframes doesn't happen. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_iFrame) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Attempt to activate the prerendered page for an iframe. This should fail |
| // and fallback to network request. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ("LOADED", EvalJs(web_contents(), |
| JsReplace("add_iframe($1)", kPrerenderingUrl))); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| |
| // Activation shouldn't happen, so the prerender host should not be consumed. |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id); |
| } |
| |
| // Makes sure that cross-origin subframe navigations are deferred during |
| // prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| DeferCrossOriginSubframeNavigation) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/add_prerender.html?prerender"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| const GURL kSameOriginSubframeUrl = |
| GetUrl("/prerender/add_prerender.html?same_origin_iframe"); |
| const GURL kCrossOriginSubframeUrl = |
| GetCrossOriginUrl("/prerender/add_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); |
| // Use ExecuteScriptAsync instead of EvalJs as inserted cross-origin iframe |
| // navigation would be deferred and script execution does not finish until |
| // the activation. |
| ExecuteScriptAsync(prerender_frame_host, JsReplace("add_iframe_async($1)", |
| kCrossOriginSubframeUrl)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Add a same-origin iframe to the prerendering page. |
| ASSERT_EQ("LOADED", |
| EvalJs(prerender_frame_host, |
| JsReplace("add_iframe($1)", kSameOriginSubframeUrl))); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 1); |
| ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| ASSERT_EQ("LOADED", |
| EvalJs(prerender_frame_host, JsReplace("wait_iframe_async($1)", |
| kCrossOriginSubframeUrl))); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| ASSERT_EQ(GetRequestCount(kSameOriginSubframeUrl), 1); |
| EXPECT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 1); |
| |
| const char kInitialDocumentPrerenderingScript[] = |
| "initial_document_prerendering"; |
| const char kCurrentDocumentPrerenderingScript[] = "document.prerendering"; |
| const char kOnprerenderingchangeObservedScript[] = |
| "onprerenderingchange_observed"; |
| 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* same_origin_render_frame_host = |
| FindRenderFrameHost(*prerender_frame_host, kSameOriginSubframeUrl); |
| DCHECK(same_origin_render_frame_host); |
| EXPECT_EQ(true, EvalJs(same_origin_render_frame_host, |
| kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(same_origin_render_frame_host, |
| kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(true, EvalJs(same_origin_render_frame_host, |
| kOnprerenderingchangeObservedScript)); |
| |
| RenderFrameHost* cross_origin_render_frame_host = |
| FindRenderFrameHost(*prerender_frame_host, kCrossOriginSubframeUrl); |
| DCHECK(cross_origin_render_frame_host); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kOnprerenderingchangeObservedScript)); |
| } |
| |
| // 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/add_prerender.html?initial"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/add_prerender.html?prerender"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| const GURL kCrossOriginSubframeUrl = |
| GetCrossOriginUrl("/prerender/add_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); |
| // Use ExecuteScriptAsync instead of EvalJs as inserted iframe redirect |
| // navigation would be deferred and script execution does not finish until |
| // the activation. |
| ExecuteScriptAsync( |
| prerender_frame_host, |
| JsReplace("add_iframe_async($1)", kServerRedirectSubframeUrl)); |
| WaitForRequest(kServerRedirectSubframeUrl, 1); |
| ASSERT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 1); |
| ASSERT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 0); |
| |
| // Activate. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| ASSERT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| ASSERT_EQ("LOADED", EvalJs(prerender_frame_host, |
| JsReplace("wait_iframe_async($1)", |
| kServerRedirectSubframeUrl))); |
| EXPECT_EQ(GetRequestCount(kServerRedirectSubframeUrl), 1); |
| EXPECT_EQ(GetRequestCount(kCrossOriginSubframeUrl), 1); |
| |
| const char kInitialDocumentPrerenderingScript[] = |
| "initial_document_prerendering"; |
| const char kCurrentDocumentPrerenderingScript[] = "document.prerendering"; |
| const char kOnprerenderingchangeObservedScript[] = |
| "onprerenderingchange_observed"; |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, |
| EvalJs(prerender_frame_host, kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(true, |
| EvalJs(prerender_frame_host, kOnprerenderingchangeObservedScript)); |
| |
| RenderFrameHost* cross_origin_render_frame_host = |
| FindRenderFrameHost(*prerender_frame_host, kCrossOriginSubframeUrl); |
| DCHECK(cross_origin_render_frame_host); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kInitialDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kCurrentDocumentPrerenderingScript)); |
| EXPECT_EQ(false, EvalJs(cross_origin_render_frame_host, |
| kOnprerenderingchangeObservedScript)); |
| } |
| |
| // Test main frame navigation in prerendering page cancels the prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MainFrameNavigationCancelsPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| const GURL kHungUrl = GetUrl("/hung"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| |
| // Start a navigation in the prerender frame tree that will cancel the |
| // initiator's prerendering. |
| test::PrerenderHostObserver observer(*web_contents_impl(), host_id); |
| |
| NavigatePrerenderedPage(host_id, kHungUrl); |
| |
| observer.WaitForDestroyed(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMainFrameNavigation, 1); |
| } |
| |
| // Regression test for https://crbug.com/1198051 |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MainFrameSamePageNavigation) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/navigation_controller/hash_anchor_with_iframe.html"); |
| const GURL kAnchorUrl = |
| GetUrl("/navigation_controller/hash_anchor_with_iframe.html#Test"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| WaitForPrerenderLoadCompleted(host_id); |
| |
| // Do a same document navigation |
| NavigatePrerenderedPage(host_id, kAnchorUrl); |
| WaitForPrerenderLoadCompleted(host_id); |
| |
| // Activate |
| NavigatePrimaryPage(kPrerenderingUrl); |
| // 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("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Attempt to activate the prerendered page for a pop-up window. This should |
| // fail and fallback to network request. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ("LOADED", EvalJs(web_contents(), |
| JsReplace("open_window($1)", kPrerenderingUrl))); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| |
| // Activation shouldn't happen, so the prerender host should not be consumed. |
| EXPECT_EQ(GetHostForUrl(kPrerenderingUrl), host_id); |
| } |
| |
| // Makes sure that activation on navigation for a page that has a pop-up window |
| // doesn't happen. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, Activation_PageWithPopUpWindow) { |
| // Navigate to an initial page. |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = GetUrl("/empty.html?next"); |
| AddPrerender(kPrerenderingUrl); |
| ASSERT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Open a pop-up window. |
| const GURL kWindowUrl = GetUrl("/empty.html?window"); |
| EXPECT_EQ("LOADED", |
| EvalJs(web_contents(), JsReplace("open_window($1)", kWindowUrl))); |
| |
| // Attempt to activate the prerendered page for the top-level frame. This |
| // should fail and fallback to network request because the pop-up window |
| // exists. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| |
| // Activation shouldn't happen, so the prerender host should not be consumed. |
| // However, we don't check the existence of the prerender host here unlike |
| // other activation tests because navigating the frame that triggered |
| // prerendering abandons the prerendered page regardless of activation. |
| } |
| |
| // Tests that all RenderFrameHostImpls in the prerendering page know the |
| // prerendering state. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderIframe) { |
| TestHostPrerenderingState(GetUrl("/page_with_iframe.html")); |
| } |
| |
| // Blank <iframe> is a special case. Tests that the blank iframe knows the |
| // prerendering state as well. |
| // TODO(https://crbug.com/1185965): This test is disabled for flakiness. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DISABLED_PrerenderBlankIframe) { |
| TestHostPrerenderingState(GetUrl("/page_with_blank_iframe.html")); |
| } |
| |
| // Tests that an inner WebContents can be attached in a prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivatePageWithInnerContents) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_blank_iframe.html"); |
| const GURL kInnerContentsUrl = GetUrl("/title1.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| WebContentsImpl* inner_contents = |
| static_cast<WebContentsImpl*>(CreateAndAttachInnerContents( |
| prerendered_render_frame_host->child_at(0)->current_frame_host())); |
| ASSERT_TRUE(NavigateToURLFromRenderer(inner_contents, kInnerContentsUrl)); |
| |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| EXPECT_EQ(GetRequestCount(kInnerContentsUrl), 1); |
| } |
| |
| // Tests that RenderFrameHost::ForEachRenderFrameHost behaves correctly when |
| // prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ForEachRenderFrameHost) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.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)); |
| |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHostImpl* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| RenderFrameHostImpl* rfh_sub_1 = |
| prerendered_render_frame_host->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_sub_1_1 = |
| rfh_sub_1->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_sub_2 = |
| prerendered_render_frame_host->child_at(1)->current_frame_host(); |
| |
| EXPECT_THAT(CollectAllRenderFrameHosts(prerendered_render_frame_host), |
| testing::ElementsAre(prerendered_render_frame_host, rfh_sub_1, |
| rfh_sub_2, rfh_sub_1_1)); |
| } |
| |
| class MojoCapabilityControlTestContentBrowserClient |
| : public TestContentBrowserClient, |
| mojom::TestInterfaceForDefer, |
| mojom::TestInterfaceForGrant, |
| mojom::TestInterfaceForCancel, |
| mojom::TestInterfaceForUnexpected { |
| public: |
| void RegisterBrowserInterfaceBindersForFrame( |
| RenderFrameHost* render_frame_host, |
| mojo::BinderMapWithContext<RenderFrameHost*>* map) override { |
| map->Add<mojom::TestInterfaceForDefer>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindDeferInterface, |
| base::Unretained(this))); |
| map->Add<mojom::TestInterfaceForGrant>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindGrantInterface, |
| base::Unretained(this))); |
| map->Add<mojom::TestInterfaceForCancel>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindCancelInterface, |
| base::Unretained(this))); |
| map->Add<mojom::TestInterfaceForUnexpected>(base::BindRepeating( |
| &MojoCapabilityControlTestContentBrowserClient::BindUnexpectedInterface, |
| base::Unretained(this))); |
| } |
| |
| void RegisterMojoBinderPoliciesForSameOriginPrerendering( |
| MojoBinderPolicyMap& policy_map) override { |
| policy_map.SetPolicy<mojom::TestInterfaceForGrant>( |
| MojoBinderPolicy::kGrant); |
| policy_map.SetPolicy<mojom::TestInterfaceForCancel>( |
| MojoBinderPolicy::kCancel); |
| policy_map.SetPolicy<mojom::TestInterfaceForUnexpected>( |
| MojoBinderPolicy::kUnexpected); |
| } |
| |
| void BindDeferInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<content::mojom::TestInterfaceForDefer> receiver) { |
| defer_receiver_set_.Add(this, std::move(receiver)); |
| } |
| |
| void BindGrantInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::TestInterfaceForGrant> receiver) { |
| grant_receiver_set_.Add(this, std::move(receiver)); |
| } |
| |
| void BindCancelInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::TestInterfaceForCancel> receiver) { |
| cancel_receiver_.Bind(std::move(receiver)); |
| } |
| |
| void BindUnexpectedInterface( |
| RenderFrameHost* render_frame_host, |
| mojo::PendingReceiver<mojom::TestInterfaceForUnexpected> receiver) { |
| unexpected_receiver_.Bind(std::move(receiver)); |
| } |
| |
| // mojom::TestInterfaceForDefer implementation. |
| void Ping(PingCallback callback) override { std::move(callback).Run(); } |
| |
| size_t GetDeferReceiverSetSize() { return defer_receiver_set_.size(); } |
| |
| size_t GetGrantReceiverSetSize() { return grant_receiver_set_.size(); } |
| |
| private: |
| mojo::ReceiverSet<mojom::TestInterfaceForDefer> defer_receiver_set_; |
| mojo::ReceiverSet<mojom::TestInterfaceForGrant> grant_receiver_set_; |
| mojo::Receiver<mojom::TestInterfaceForCancel> cancel_receiver_{this}; |
| mojo::Receiver<mojom::TestInterfaceForUnexpected> unexpected_receiver_{this}; |
| }; |
| |
| // Tests that binding requests are handled according to MojoBinderPolicyMap |
| // during prerendering. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, MojoCapabilityControl) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| RenderFrameHost* prerendered_render_frame_host = |
| GetPrerenderedMainFrameHost(host_id); |
| std::vector<RenderFrameHost*> frames = |
| prerendered_render_frame_host->GetFramesInSubtree(); |
| |
| // A barrier closure to wait until a deferred interface is granted on all |
| // frames. |
| base::RunLoop run_loop; |
| auto barrier_closure = |
| base::BarrierClosure(frames.size(), run_loop.QuitClosure()); |
| |
| mojo::RemoteSet<mojom::TestInterfaceForDefer> defer_remote_set; |
| mojo::RemoteSet<mojom::TestInterfaceForGrant> grant_remote_set; |
| for (auto* frame : frames) { |
| auto* rfhi = static_cast<RenderFrameHostImpl*>(frame); |
| EXPECT_TRUE(rfhi->frame_tree()->is_prerendering()); |
| EXPECT_EQ(rfhi->lifecycle_state(), LifecycleStateImpl::kPrerendering); |
| EXPECT_EQ(rfhi->GetLifecycleState(), |
| RenderFrameHost::LifecycleState::kPrerendering); |
| |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| rfhi->browser_interface_broker_receiver_for_testing(); |
| blink::mojom::BrowserInterfaceBroker* prerender_broker = |
| bib.internal_state()->impl(); |
| |
| // Try to bind a kDefer interface. |
| mojo::Remote<mojom::TestInterfaceForDefer> prerender_defer_remote; |
| prerender_broker->GetInterface( |
| prerender_defer_remote.BindNewPipeAndPassReceiver()); |
| // The barrier closure will be called after the deferred interface is |
| // granted. |
| prerender_defer_remote->Ping(barrier_closure); |
| defer_remote_set.Add(std::move(prerender_defer_remote)); |
| |
| // Try to bind a kGrant interface. |
| mojo::Remote<mojom::TestInterfaceForGrant> prerender_grant_remote; |
| prerender_broker->GetInterface( |
| prerender_grant_remote.BindNewPipeAndPassReceiver()); |
| grant_remote_set.Add(std::move(prerender_grant_remote)); |
| } |
| // Verify that BrowserInterfaceBrokerImpl defers running binders whose |
| // policies are kDefer until the prerendered page is activated. |
| EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), 0U); |
| // Verify that BrowserInterfaceBrokerImpl executes kGrant binders immediately. |
| EXPECT_EQ(test_browser_client.GetGrantReceiverSetSize(), frames.size()); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // Wait until the deferred interface is granted on all frames. |
| run_loop.Run(); |
| EXPECT_EQ(test_browser_client.GetDeferReceiverSetSize(), frames.size()); |
| |
| SetBrowserClientForTesting(old_browser_client); |
| } |
| |
| // Tests that mojo capability control will cancel prerendering if the main frame |
| // receives a request for a kCancel interface. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MojoCapabilityControl_CancelMainFrame) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| prerendered_render_frame_host |
| ->browser_interface_broker_receiver_for_testing(); |
| blink::mojom::BrowserInterfaceBroker* prerender_broker = |
| bib.internal_state()->impl(); |
| |
| // Send a kCancel request to cancel prerendering. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| mojo::Remote<mojom::TestInterfaceForCancel> remote; |
| prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver()); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| // `TestInterfaceForCancel` doesn't have a enum value because it is not used |
| // in production, so histogram_tester should log |
| // PrerenderCancelledInterface::kUnkown here. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kUnknown, 1); |
| SetBrowserClientForTesting(old_browser_client); |
| } |
| |
| // Tests that mojo capability control will cancel prerendering if child frames |
| // receive a request for a kCancel interface. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MojoCapabilityControl_CancelIframe) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| base::HistogramTester histogram_tester; |
| |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/page_with_iframe.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| ASSERT_GE(main_render_frame_host->child_count(), 1U); |
| RenderFrameHostImpl* child_render_frame_host = |
| main_render_frame_host->child_at(0U)->current_frame_host(); |
| EXPECT_NE(main_render_frame_host->GetLastCommittedURL(), |
| child_render_frame_host->GetLastCommittedURL()); |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| child_render_frame_host->browser_interface_broker_receiver_for_testing(); |
| blink::mojom::BrowserInterfaceBroker* prerender_broker = |
| bib.internal_state()->impl(); |
| |
| // Send a kCancel request to cancel prerendering. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| |
| mojo::Remote<mojom::TestInterfaceForCancel> remote; |
| prerender_broker->GetInterface(remote.BindNewPipeAndPassReceiver()); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| SetBrowserClientForTesting(old_browser_client); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| // `TestInterfaceForCancel` doesn't have a enum value because it is not used |
| // in production, so histogram_tester should log |
| // PrerenderCancelledInterface::kUnkown here. |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kUnknown, 1); |
| } |
| |
| // Tests that mojo capability control will crash the prerender if the browser |
| // process receives a kUnexpected interface. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| MojoCapabilityControl_HandleUnexpected) { |
| MojoCapabilityControlTestContentBrowserClient test_browser_client; |
| auto* old_browser_client = SetBrowserClientForTesting(&test_browser_client); |
| |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Set up the error handler for bad mojo messages. |
| std::string bad_message_error; |
| mojo::SetDefaultProcessErrorHandler( |
| base::BindLambdaForTesting([&](const std::string& error) { |
| EXPECT_FALSE(error.empty()); |
| EXPECT_TRUE(bad_message_error.empty()); |
| bad_message_error = error; |
| })); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| auto* main_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Rebind a receiver for testing. |
| // mojo::ReportBadMessage must be called within the stack frame derived from |
| // mojo IPC calls, so this browser test should call the |
| // remote<blink::mojom::BrowserInterfaceBroker>::GetInterface() to test |
| // unexpected interfaces. But its remote end is in renderer processes and |
| // inaccessible, so the test code has to create another BrowserInterfaceBroker |
| // pipe and rebind the receiver end so as to send the request from the remote. |
| mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& bib = |
| main_render_frame_host->browser_interface_broker_receiver_for_testing(); |
| auto broker_receiver_of_previous_document = bib.Unbind(); |
| ASSERT_TRUE(broker_receiver_of_previous_document); |
| mojo::Remote<blink::mojom::BrowserInterfaceBroker> remote_broker; |
| mojo::PendingReceiver<blink::mojom::BrowserInterfaceBroker> fake_receiver = |
| remote_broker.BindNewPipeAndPassReceiver(); |
| main_render_frame_host->BindBrowserInterfaceBrokerReceiver( |
| std::move(fake_receiver)); |
| |
| // Send a kUnexpected request. |
| EXPECT_TRUE(HasHostForUrl(kPrerenderingUrl)); |
| mojo::Remote<mojom::TestInterfaceForUnexpected> remote; |
| remote_broker->GetInterface(remote.BindNewPipeAndPassReceiver()); |
| remote_broker.FlushForTesting(); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| EXPECT_EQ(bad_message_error, |
| "MBPA_BAD_INTERFACE: content.mojom.TestInterfaceForUnexpected"); |
| |
| SetBrowserClientForTesting(old_browser_client); |
| } |
| |
| // TODO(https://crbug.com/1132746): Test canceling prerendering when its |
| // initiator is no longer interested in prerending this page. |
| |
| // TODO(https://crbug.com/1132746): Test prerendering for 404 page, auth error, |
| // cross origin, 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("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Start a prerender. |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/add_prerender.html?prerendering"); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_frame = GetPrerenderedMainFrameHost(host_id); |
| |
| // Attempt to open a window in the prerendered page. This should fail. |
| const GURL kWindowOpenUrl = GetUrl("/empty.html"); |
| 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) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/add_prerender.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_EQ(current_frame_host()->lifecycle_state(), |
| LifecycleStateImpl::kActive); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // Open an iframe in the prerendered page. |
| RenderFrameHostImpl* rfh_a = GetPrerenderedMainFrameHost(host_id); |
| EXPECT_EQ("LOADED", |
| EvalJs(rfh_a, JsReplace("add_iframe($1)", GetUrl("/empty.html")))); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| |
| // Both rfh_a and rfh_b lifecycle state's should be kPrerendering. |
| EXPECT_EQ(LifecycleStateImpl::kPrerendering, rfh_a->lifecycle_state()); |
| EXPECT_EQ(LifecycleStateImpl::kPrerendering, rfh_b->lifecycle_state()); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // Both rfh_a and rfh_b lifecycle state's should be kActive after activation. |
| EXPECT_EQ(LifecycleStateImpl::kActive, rfh_a->lifecycle_state()); |
| EXPECT_EQ(LifecycleStateImpl::kActive, rfh_b->lifecycle_state()); |
| } |
| |
| // Tests that prerendering is gated behind CSP:prefetch-src |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, CSPPrefetchSrc) { |
| base::HistogramTester histogram_tester; |
| |
| GURL initial_url = GetUrl("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| // Add CSP:prefetch-src */empty.html |
| EXPECT_TRUE(ExecJs(current_frame_host(), R"( |
| const meta = document.createElement('meta'); |
| meta.httpEquiv = "Content-Security-Policy"; |
| meta.content = "prefetch-src https://a.test:*/empty.html"; |
| document.getElementsByTagName('head')[0].appendChild(meta); |
| )")); |
| |
| 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:*/empty.html\"*"; |
| |
| // Check what happens when a prerendering is blocked: |
| { |
| GURL disallowed_url = GetUrl("/title1.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()); |
| EXPECT_TRUE( |
| ExecJs(web_contents(), JsReplace("add_prerender($1)", disallowed_url))); |
| observer.WaitForTrigger(disallowed_url); |
| EXPECT_FALSE(HasHostForUrl(disallowed_url)); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(disallowed_url), 0); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kNavigationRequestBlockedByCsp, 1); |
| } |
| |
| // Check what happens when prerendering isn't blocked. |
| { |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| GURL kAllowedUrl = GetUrl("/empty.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("/prerender/add_prerender.html"); |
| ASSERT_TRUE(NavigateToURL(shell(), initial_url)); |
| |
| // Add CSP:prefetch-src */empty.html |
| EXPECT_TRUE(ExecJs(current_frame_host(), R"( |
| const meta = document.createElement('meta'); |
| meta.httpEquiv = "Content-Security-Policy"; |
| meta.content = |
| "default-src https://a.test:*/empty.html; script-src 'unsafe-eval'"; |
| document.getElementsByTagName('head')[0].appendChild(meta); |
| )")); |
| |
| 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:*/empty.html\"*"; |
| |
| // Check what happens when a prerendering is blocked: |
| { |
| GURL disallowed_url = GetUrl("/title1.html"); |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| EXPECT_TRUE( |
| ExecJs(web_contents(), JsReplace("add_prerender($1)", disallowed_url))); |
| observer.WaitForTrigger(disallowed_url); |
| EXPECT_FALSE(HasHostForUrl(disallowed_url)); |
| console_observer.Wait(); |
| EXPECT_EQ(1u, console_observer.messages().size()); |
| EXPECT_EQ(GetRequestCount(disallowed_url), 0); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kNavigationRequestBlockedByCsp, 1); |
| } |
| |
| // Check what happens when prerendering isn't blocked. |
| { |
| WebContentsConsoleObserver console_observer(web_contents_impl()); |
| console_observer.SetPattern(kConsolePattern); |
| GURL kAllowedUrl = GetUrl("/empty.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("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = |
| GetUrl("/prerender/restriction_file_system.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| EXPECT_EQ( |
| true, |
| ExecJs(prerender_render_frame_host, "accessOriginPrivateFileSystem();", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE | |
| EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| // Run a event loop so the page can fail the test. |
| EXPECT_TRUE(ExecJs(prerender_render_frame_host, "runLoop();")); |
| |
| // Activate the page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| |
| // Wait for the completion of `accessOriginPrivateFileSystem`. |
| EXPECT_EQ(true, EvalJs(prerender_render_frame_host, "result;")); |
| // Check the event sequence seen in the prerendered page. |
| EvalJsResult results = EvalJs(prerender_render_frame_host, "eventsSeen"); |
| std::vector<std::string> eventsSeen; |
| const base::Value resultsList = results.ExtractList(); |
| for (auto& result : resultsList.GetList()) |
| eventsSeen.push_back(result.GetString()); |
| EXPECT_THAT(eventsSeen, |
| testing::ElementsAreArray( |
| {"accessOriginPrivateFileSystem (prerendering: true)", |
| "prerenderingchange (prerendering: false)", |
| "getDirectory (prerendering: false)"})); |
| } |
| |
| // Tests that RenderDocumentHostUserData object is not cleared on activating a |
| // prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, RenderDocumentHostUserData) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Start a prerender. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Get the DocumentData associated with prerender RenderFrameHost. |
| DocumentData::CreateForCurrentDocument(prerender_render_frame_host); |
| base::WeakPtr<DocumentData> data = |
| DocumentData::GetForCurrentDocument(prerender_render_frame_host) |
| ->GetWeakPtr(); |
| EXPECT_TRUE(data); |
| |
| // Activate the prerendered page. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // The prerender host should be consumed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // DocumentData associated with document shouldn't have been cleared on |
| // activating prerendered page. |
| base::WeakPtr<DocumentData> data_after_activation = |
| DocumentData::GetForCurrentDocument(current_frame_host())->GetWeakPtr(); |
| EXPECT_TRUE(data_after_activation); |
| |
| // Both the instances of DocumentData before and after activation should point |
| // to the same object and make sure they aren't null. |
| EXPECT_EQ(data_after_activation.get(), data.get()); |
| } |
| |
| // Tests that executing the GamepadMonitor API on a prerendering before |
| // navigating to the prerendered page causes cancel prerendering. |
| // This test cannot be a web test because web tests handles the GamepadMonitor |
| // interface on the renderer side. See GamepadController::Install(). |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, GamepadMonitorCancelPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Executing `navigator.getGamepads()` to start binding the GamepadMonitor |
| // interface. |
| ignore_result(EvalJs(prerender_render_frame_host, "navigator.getGamepads()", |
| EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE)); |
| // Verify Mojo capability control cancels prerendering. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kGamepadMonitor, 1); |
| } |
| |
| // TODO(https://crbug.com/1201980) LaCrOS binds the HidManager interface, which |
| // might be required by Gamepad Service, in a different way. Disable this test |
| // before figuring out how to set the test context correctly. |
| #if !BUILDFLAG(IS_CHROMEOS_LACROS) |
| // Tests that requesting to bind the GamepadMonitor interface after the |
| // prerenderingchange event dispatched does not cancel prerendering. |
| // This test cannot be a web test because web tests handles the GamepadMonitor |
| // interface on the renderer side. See GamepadController::Install(). |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, GamepadMonitorAfterNavigation) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/restriction-gamepad.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| AddPrerender(kPrerenderingUrl); |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // Activate the prerendered page to dispatch the prerenderingchange event and |
| // run the Gamepad API in the event. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| // Wait for the completion of the prerenderingchange event to make sure the |
| // API is called. |
| EXPECT_EQ(true, EvalJs(shell()->web_contents(), "prerenderingChanged")); |
| // The API call shouldn't discard the prerendered page and shouldn't restart |
| // navigation. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS_LACROS) |
| |
| // Tests that accessing the clipboard via the execCommand API fails because the |
| // page does not has any user activation. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ClipboardByExecCommandFail) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // 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)); |
| } |
| |
| #if !defined(OS_ANDROID) || BUILDFLAG(ENABLE_PLUGINS) |
| void LoadAndWaitForPrerenderDestroyed(WebContents* const web_contents, |
| const GURL prerendering_url, |
| test::PrerenderTestHelper* helper) { |
| test::PrerenderHostRegistryObserver registry_observer(*web_contents); |
| EXPECT_TRUE( |
| ExecJs(web_contents, JsReplace("add_prerender($1)", prerendering_url))); |
| registry_observer.WaitForTrigger(prerendering_url); |
| int host_id = helper->GetHostForUrl(prerendering_url); |
| test::PrerenderHostObserver host_observer(*web_contents, host_id); |
| host_observer.WaitForDestroyed(); |
| EXPECT_EQ(helper->GetHostForUrl(prerendering_url), |
| RenderFrameHost::kNoFrameTreeNodeId); |
| } |
| #endif // !defined(OS_ANDROID) || BUILDFLAG(ENABLE_PLUGINS) |
| |
| #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("/prerender/add_prerender.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| LoadAndWaitForPrerenderDestroyed( |
| web_contents(), GetUrl("/prerender/page-with-embedded-plugin.html"), |
| prerender_helper()); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kPlugin, 1); |
| LoadAndWaitForPrerenderDestroyed( |
| web_contents(), GetUrl("/prerender/page-with-object-plugin.html"), |
| prerender_helper()); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kPlugin, 2); |
| } |
| #endif // BUILDFLAG(ENABLE_PLUGINS) |
| |
| // This is a browser test and cannot be upstreamed to WPT because it diverges |
| // from the spec by cancelling prerendering in the Notification constructor, |
| // whereas the spec says to defer upon use requestPermission(). |
| #if defined(OS_ANDROID) |
| // On Android the Notification constructor throws an exception regardless of |
| // whether the page is being prerendered. |
| // Tests that we will get the exception from the prerendering if the |
| // prerendering page attempts to use notification. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, NotificationConstructorAndroid) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| // Make a prerendered page. |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // 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("/prerender/add_prerender.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| |
| LoadAndWaitForPrerenderDestroyed(web_contents(), |
| GetUrl("/prerender/notification.html"), |
| prerender_helper()); |
| |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kMojoBinderPolicy, 1); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderCancelledInterface", |
| PrerenderCancelledInterface::kNotificationService, 1); |
| } |
| #endif // defined(OS_ANDROID) |
| |
| // End: Tests for feature restrictions in prerendered pages ==================== |
| |
| // Tests that prerendering doesn't run for low-end devices. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, LowEndDevice) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Set low-end device mode. |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kEnableLowEndDeviceMode); |
| |
| // Attempt to prerender. |
| test::PrerenderHostRegistryObserver observer(*web_contents_impl()); |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| EXPECT_TRUE( |
| ExecJs(web_contents(), JsReplace("add_prerender($1)", kPrerenderingUrl))); |
| |
| // It should fail. |
| observer.WaitForTrigger(kPrerenderingUrl); |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kLowEndDevice, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, |
| IsInactiveAndDisallowActivationCancelsPrerendering) { |
| base::HistogramTester histogram_tester; |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A prerender host for the URL should be registered. |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| auto* prerender_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| |
| // Invoke IsInactiveAndDisallowActivation for the prerendered document. |
| EXPECT_EQ(prerender_render_frame_host->lifecycle_state(), |
| RenderFrameHostImpl::LifecycleStateImpl::kPrerendering); |
| EXPECT_TRUE(prerender_render_frame_host->IsInactiveAndDisallowActivation()); |
| |
| // The prerender host for the URL should be destroyed as |
| // RenderFrameHost::IsInactiveAndDisallowActivation cancels prerendering in |
| // LifecycleStateImpl::kPrerendering state. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Cancelling the prerendering disables the activation. The navigation |
| // should issue a request again. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 2); |
| histogram_tester.ExpectUniqueSample( |
| "Prerender.Experimental.PrerenderHostFinalStatus", |
| PrerenderHost::FinalStatus::kDestroyed, 1); |
| } |
| |
| // Make sure input events are routed to the primary FrameTree not the prerender |
| // one. See https://crbug.com/1197136 |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, InputRoutedToPrimaryFrameTree) { |
| const GURL kInitialUrl = GetUrl("/prerender/simple_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| WaitForPrerenderLoadCompletion(kPrerenderingUrl); |
| |
| // Touch / click the link and wait for the navigation to complete. |
| TestNavigationObserver navigation_observer(web_contents()); |
| SyntheticTapGestureParams params; |
| params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput; |
| params.position = GetCenterCoordinatesOfElementWithId(web_contents(), "link"); |
| web_contents_impl()->GetRenderViewHost()->GetWidget()->QueueSyntheticGesture( |
| std::make_unique<SyntheticTapGesture>(params), base::DoNothing()); |
| navigation_observer.Wait(); |
| |
| EXPECT_EQ(shell()->web_contents()->GetURL(), kPrerenderingUrl); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, VisibilityWhilePrerendering) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `kPrerenderingUrl`. |
| ASSERT_EQ(GetRequestCount(kPrerenderingUrl), 0); |
| const int host_id = AddPrerender(kPrerenderingUrl); |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| |
| // A prerender host for the URL should be registered. |
| ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId); |
| |
| // The visibility state must be "hidden" while prerendering. |
| auto* prerendered_render_frame_host = GetPrerenderedMainFrameHost(host_id); |
| auto* rvh = static_cast<RenderViewHostImpl*>( |
| prerendered_render_frame_host->GetRenderViewHost()); |
| EXPECT_EQ(rvh->GetPageLifecycleStateManager() |
| ->CalculatePageLifecycleState() |
| ->visibility, |
| PageVisibilityState::kHidden); |
| } |
| |
| class ScopedDataSaverTestContentBrowserClient |
| : public TestContentBrowserClient { |
| public: |
| ScopedDataSaverTestContentBrowserClient() |
| : old_client(SetBrowserClientForTesting(this)) {} |
| ~ScopedDataSaverTestContentBrowserClient() override { |
| SetBrowserClientForTesting(old_client); |
| } |
| |
| // ContentBrowserClient overrides: |
| bool IsDataSaverEnabled(BrowserContext* context) override { return true; } |
| |
| void OverrideWebkitPrefs(WebContents* web_contents, |
| blink::web_pref::WebPreferences* prefs) override { |
| prefs->data_saver_enabled = true; |
| } |
| |
| private: |
| ContentBrowserClient* old_client; |
| }; |
| |
| // Tests that the data saver doesn't prevent image load in a prerendered page. |
| IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, DataSaver) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/image.html"); |
| const GURL kImageUrl = GetUrl("/blank.jpg"); |
| |
| // Enable data saver. |
| ScopedDataSaverTestContentBrowserClient scoped_content_browser_client; |
| shell()->web_contents()->OnWebPreferencesChanged(); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `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("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/prerender/image_loading_lazy.html"); |
| const GURL kImageUrl = GetUrl("/blank.jpg"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(shell()->web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `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); |
| } |
| |
| 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, |
| LinkRelPrerender) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // Navigate to an initial page. |
| ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl)); |
| ASSERT_EQ(web_contents()->GetURL(), kInitialUrl); |
| |
| // Add <link rel=prerender> that will prerender `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)); |
| |
| // Activate the prerendered page. |
| // The test passes if we don't crash while cleaning up speculative render |
| // frame host. |
| NavigatePrimaryPage(kPrerenderingUrl); |
| EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl); |
| |
| // The prerender host should be consumed. |
| EXPECT_FALSE(HasHostForUrl(kPrerenderingUrl)); |
| |
| // Activating the prerendered page should not issue a request. |
| EXPECT_EQ(GetRequestCount(kPrerenderingUrl), 1); |
| } |
| |
| class PrerenderWithBackForwardCacheBrowserTest |
| : public PrerenderBrowserTest, |
| public testing::WithParamInterface<BackForwardCacheType> { |
| public: |
| PrerenderWithBackForwardCacheBrowserTest() { |
| // Set up the common params for the BFCache. |
| base::FieldTrialParams feature_params; |
| feature_params["TimeToLiveInBackForwardCacheInSeconds"] = "3600"; |
| |
| // Allow the BFCache for all devices regardless of their memory. |
| std::vector<base::Feature> disabled_features{ |
| features::kBackForwardCacheMemoryControls}; |
| |
| switch (GetParam()) { |
| case BackForwardCacheType::kDisabled: |
| feature_list_.InitAndDisableFeature(features::kBackForwardCache); |
| break; |
| case BackForwardCacheType::kEnabled: |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kBackForwardCache, feature_params}}, disabled_features); |
| break; |
| case BackForwardCacheType::kEnabledWithSameSite: |
| feature_params["enable_same_site"] = "true"; |
| feature_list_.InitWithFeaturesAndParameters( |
| {{features::kBackForwardCache, feature_params}}, disabled_features); |
| break; |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| PrerenderWithBackForwardCacheBrowserTest, |
| testing::Values(BackForwardCacheType::kDisabled, |
| BackForwardCacheType::kEnabled, |
| BackForwardCacheType::kEnabledWithSameSite), |
| ToString); |
| |
| // Tests that history navigation works after activation. This runs with variaous |
| // BFCache configurations that may modify behavior of history navigation. |
| // This is a regression test for https://crbug.com/1201914. |
| IN_PROC_BROWSER_TEST_P(PrerenderWithBackForwardCacheBrowserTest, |
| HistoryNavigationAfterActivation) { |
| const GURL kInitialUrl = GetUrl("/prerender/add_prerender.html"); |
| const GURL kPrerenderingUrl = GetUrl("/empty.html"); |
| |
| // 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; |
| } |
| } |
| |
| } // namespace |
| } // namespace content |