| // Copyright 2021 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/memory/raw_ptr.h" |
| #include "content/browser/back_forward_cache_browsertest.h" |
| |
| #include "base/metrics/metrics_hashes.h" |
| #include "base/test/bind.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "build/build_config.h" |
| #include "content/browser/renderer_host/back_forward_cache_disable.h" |
| #include "content/browser/renderer_host/navigation_request.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/content_navigation_policy.h" |
| #include "content/public/browser/browser_accessibility_state.h" |
| #include "content/public/browser/disallow_activation_reason.h" |
| #include "content/public/browser/navigation_handle.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_utils.h" |
| #include "content/public/test/mock_web_contents_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/shell/browser/shell_content_browser_client.h" |
| #include "content/test/did_commit_navigation_interceptor.h" |
| #include "content/test/echo.test-mojom.h" |
| #include "media/base/media_switches.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| |
| // This file contains back/forward-cache tests that test or use internal |
| // features, e.g. cache-flushing, crashes, verifying proxies and other |
| // navigation internals. If you could write the test in JS or using only public |
| // functions it probably doesn't belong in this file. It was forked from |
| // https://source.chromium.org/chromium/chromium/src/+/main:content/browser/back_forward_cache_browsertest.cc;drc=db47c3a2e741f8ea55024e64ec932044024cbddc |
| // |
| // When adding tests consider also adding WPTs, although for internal tests, |
| // this is often not an option. See |
| // third_party/blink/web_tests/external/wpt/html/browsers/browsing-the-web/back-forward-cache/README.md |
| |
| using testing::_; |
| using testing::Each; |
| using ::testing::ElementsAre; |
| using ::testing::Not; |
| using ::testing::UnorderedElementsAreArray; |
| |
| namespace content { |
| |
| // Ensure flushing the BackForwardCache works properly. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BackForwardCacheFlush) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| |
| // 3) Flush A. |
| web_contents()->GetController().GetBackForwardCache().Flush(); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // 4) Go back to a new A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // 5) Flush B. |
| web_contents()->GetController().GetBackForwardCache().Flush(); |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| } |
| |
| // Tests that |RenderFrameHost::ForEachRenderFrameHost| and |
| // |WebContents::ForEachRenderFrameHost| behave correctly with bfcached |
| // RenderFrameHosts. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, ForEachRenderFrameHost) { |
| // There are sometimes unexpected messages from a renderer to the browser, |
| // which caused test flakiness on macOS. |
| // TODO(crbug.com/1263536): Fix the test flakiness. |
| DoNotFailForUnexpectedMessagesWhileCached(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b(c),d)")); |
| GURL url_e(embedded_test_server()->GetURL("e.com", "/title1.html")); |
| |
| std::vector<RenderFrameDeletedObserver*> rfh_observers; |
| |
| // 1) Navigate to a(b(c),d). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_c = rfh_b->child_at(0)->current_frame_host(); |
| RenderFrameHostImpl* rfh_d = rfh_a->child_at(1)->current_frame_host(); |
| RenderFrameDeletedObserver a_observer(rfh_a), b_observer(rfh_b), |
| c_observer(rfh_c), d_observer(rfh_d); |
| rfh_observers.insert(rfh_observers.end(), |
| {&a_observer, &b_observer, &c_observer, &d_observer}); |
| |
| // Ensure the visited frames are what we would expect for the page before |
| // entering bfcache. |
| EXPECT_THAT(CollectAllRenderFrameHosts(rfh_a), |
| ::testing::ElementsAre(rfh_a, rfh_b, rfh_d, rfh_c)); |
| EXPECT_THAT(CollectAllRenderFrameHosts(web_contents()), |
| ::testing::ElementsAre(rfh_a, rfh_b, rfh_d, rfh_c)); |
| |
| // 2) Navigate to e. |
| EXPECT_TRUE(NavigateToURL(shell(), url_e)); |
| RenderFrameHostImpl* rfh_e = current_frame_host(); |
| RenderFrameDeletedObserver e_observer(rfh_e); |
| rfh_observers.push_back(&e_observer); |
| ASSERT_THAT(rfh_observers, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({rfh_a, rfh_b, rfh_c, rfh_d}), |
| Each(InBackForwardCache())); |
| EXPECT_THAT(rfh_e, Not(InBackForwardCache())); |
| |
| // When starting iteration from the primary frame, we shouldn't see any of the |
| // frames in bfcache. |
| EXPECT_THAT(CollectAllRenderFrameHosts(rfh_e), ::testing::ElementsAre(rfh_e)); |
| |
| // When starting iteration from a bfcached RFH, we should see the frame itself |
| // and its descendants in breadth first order. |
| EXPECT_THAT(CollectAllRenderFrameHosts(rfh_a), |
| ::testing::ElementsAre(rfh_a, rfh_b, rfh_d, rfh_c)); |
| |
| // Ensure that starting iteration from a subframe of a bfcached frame also |
| // works. |
| EXPECT_THAT(CollectAllRenderFrameHosts(rfh_b), |
| ::testing::ElementsAre(rfh_b, rfh_c)); |
| |
| // When iterating over all RenderFrameHosts in a WebContents, we should see |
| // the RFHs of both the primary page and the bfcached page. |
| EXPECT_THAT( |
| CollectAllRenderFrameHosts(web_contents()), |
| ::testing::UnorderedElementsAre(rfh_a, rfh_b, rfh_c, rfh_d, rfh_e)); |
| |
| { |
| // If we stop iteration in |WebContents::ForEachRenderFrameHost|, we stop |
| // the entire iteration, not just iteration in the page being iterated at |
| // that point. In this case, if we stop iteration in the primary page, we do |
| // not continue to iterate in the bfcached page. |
| bool stopped = false; |
| web_contents()->ForEachRenderFrameHost( |
| base::BindLambdaForTesting([&](RenderFrameHostImpl* rfh) { |
| EXPECT_FALSE(stopped); |
| stopped = true; |
| return RenderFrameHost::FrameIterationAction::kStop; |
| })); |
| } |
| |
| EXPECT_EQ(nullptr, rfh_a->GetParentOrOuterDocument()); |
| EXPECT_EQ(rfh_a, rfh_b->GetParentOrOuterDocument()); |
| EXPECT_EQ(rfh_b, rfh_c->GetParentOrOuterDocument()); |
| EXPECT_EQ(rfh_a, rfh_d->GetParentOrOuterDocument()); |
| EXPECT_EQ(nullptr, rfh_e->GetParentOrOuterDocument()); |
| // The outermost document of a bfcached page is the bfcached main |
| // RenderFrameHost, not the primary main RenderFrameHost. |
| EXPECT_EQ(rfh_a, rfh_a->GetOutermostMainFrame()); |
| EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrame()); |
| EXPECT_EQ(rfh_a, rfh_c->GetOutermostMainFrame()); |
| EXPECT_EQ(rfh_a, rfh_d->GetOutermostMainFrame()); |
| EXPECT_EQ(rfh_e, rfh_e->GetOutermostMainFrame()); |
| EXPECT_EQ(nullptr, rfh_a->GetParentOrOuterDocumentOrEmbedder()); |
| EXPECT_EQ(rfh_a, rfh_b->GetParentOrOuterDocumentOrEmbedder()); |
| EXPECT_EQ(rfh_b, rfh_c->GetParentOrOuterDocumentOrEmbedder()); |
| EXPECT_EQ(rfh_a, rfh_d->GetParentOrOuterDocumentOrEmbedder()); |
| EXPECT_EQ(nullptr, rfh_e->GetParentOrOuterDocumentOrEmbedder()); |
| EXPECT_EQ(rfh_a, rfh_a->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(rfh_a, rfh_b->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(rfh_a, rfh_c->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(rfh_a, rfh_d->GetOutermostMainFrameOrEmbedder()); |
| EXPECT_EQ(rfh_e, rfh_e->GetOutermostMainFrameOrEmbedder()); |
| } |
| |
| // Tests that |RenderFrameHostImpl::ForEachRenderFrameHostIncludingSpeculative| |
| // and |WebContentsImpl::ForEachRenderFrameHostIncludingSpeculative| |
| // behave correctly when a FrameTreeNode has both a speculative RFH and a |
| // bfcached RFH. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| ForEachRenderFrameHostWithSpeculative) { |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| std::vector<RenderFrameDeletedObserver*> rfh_observers; |
| |
| // 1) Navigate to a. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver a_observer(rfh_a); |
| rfh_observers.push_back(&a_observer); |
| |
| // 2) Navigate to b. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver b_observer(rfh_b); |
| rfh_observers.push_back(&b_observer); |
| ASSERT_THAT(rfh_observers, Each(Not(Deleted()))); |
| |
| // 3) Begin navigation to c. |
| TestNavigationManager nav_manager(web_contents(), url_c); |
| shell()->LoadURL(url_c); |
| ASSERT_TRUE(nav_manager.WaitForRequestStart()); |
| |
| RenderFrameHostImpl* rfh_c = |
| rfh_b->frame_tree_node()->render_manager()->speculative_frame_host(); |
| ASSERT_TRUE(rfh_c); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_a->lifecycle_state()); |
| EXPECT_FALSE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_b->lifecycle_state()); |
| EXPECT_TRUE(rfh_b->GetPage().IsPrimary()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kSpeculative, |
| rfh_c->lifecycle_state()); |
| EXPECT_FALSE(rfh_c->GetPage().IsPrimary()); |
| |
| // When starting iteration from the bfcached RFH, we should not see the |
| // speculative RFH. |
| EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(rfh_a), |
| ::testing::ElementsAre(rfh_a)); |
| |
| // When starting iteration from the primary frame, we shouldn't see the |
| // bfcached RFH, but we should see the speculative RFH. |
| EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(rfh_b), |
| ::testing::UnorderedElementsAre(rfh_b, rfh_c)); |
| |
| // When starting iteration from the speculative RFH, we should only see |
| // the speculative RFH. In particular, we should not see the bfcached RFH. |
| EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(rfh_c), |
| ::testing::ElementsAre(rfh_c)); |
| |
| // When iterating over all RenderFrameHosts in a WebContents, we should see |
| // the RFHs of both the primary page and the bfcached page. |
| EXPECT_THAT(CollectAllRenderFrameHostsIncludingSpeculative(web_contents()), |
| ::testing::UnorderedElementsAre(rfh_a, rfh_b, rfh_c)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigationsAreFullyCommitted) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // During a navigation, the document being navigated *away from* can either be |
| // deleted or stored into the BackForwardCache. The document being navigated |
| // *to* can either be new or restored from the BackForwardCache. |
| // |
| // This test covers every combination: |
| // |
| // 1. Navigate to a cacheable page (()->A) |
| // 2. Navigate to an uncacheable page (A->B) |
| // 3. Go Back to a cached page (B->A) |
| // 4. Navigate to a cacheable page (A->C) |
| // 5. Go Back to a cached page (C->A) |
| // |
| // +-+-------+----------------+---------------+ |
| // |#|nav | curr_document | dest_document | |
| // +-+-------+----------------+---------------| |
| // |1|(()->A)| N/A | new | |
| // |2|(A->B) | cached | new | |
| // |3|(B->A) | deleted | restored | |
| // |4|(A->C) | cached | new | |
| // |5|(C->A) | cached | restored | |
| // +-+-------+----------------+---------------+ |
| // |
| // As part of these navigations we check that LastCommittedURL was updated, |
| // to verify that the frame wasn't simply swapped in without actually |
| // committing. |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL( |
| "b.com", "/back_forward_cache/page_with_dedicated_worker.html")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1. Navigate to a cacheable page (A). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2. Navigate from a cacheable page to an uncacheable page (A->B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3. Navigate from an uncacheable to a cached page page (B->A). |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a); |
| |
| // Page B should be deleted (not cached). |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| |
| ExpectRestored(FROM_HERE); |
| |
| // 4. Navigate from a cacheable page to a cacheable page (A->C). |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_c); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 5. Navigate from a cacheable page to a cached page (C->A). |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a); |
| |
| // Page C should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_c.deleted()); |
| EXPECT_TRUE(rfh_c->IsInBackForwardCache()); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| ProxiesAreStoredAndRestored) { |
| // This test makes assumption about where iframe processes live. |
| if (!AreAllSitesIsolatedForTesting()) |
| return; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // During a navigation, the document being navigated *away from* can either be |
| // deleted or stored into the BackForwardCache. The document being navigated |
| // *to* can either be new or restored from the BackForwardCache. |
| // |
| // This test covers every combination: |
| // |
| // 1. Navigate to a cacheable page (()->A) |
| // 2. Navigate to an uncacheable page (A->B) |
| // 3. Go Back to a cached page (B->A) |
| // 4. Navigate to a cacheable page (A->C) |
| // 5. Go Back to a cached page (C->A) |
| // |
| // +-+-------+----------------+---------------+ |
| // |#|nav | curr_document | dest_document | |
| // +-+-------+----------------+---------------| |
| // |1|(()->A)| N/A | new | |
| // |2|(A->B) | cached | new | |
| // |3|(B->A) | deleted | restored | |
| // |4|(A->C) | cached | new | |
| // |5|(C->A) | cached | restored | |
| // +-+-------+----------------+---------------+ |
| // |
| // We use pages with cross process iframes to verify that proxy storage and |
| // retrieval works well in every possible combination. |
| |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(i,j)")); |
| GURL url_b(embedded_test_server()->GetURL( |
| "b.com", "/back_forward_cache/page_with_dedicated_worker.html")); |
| GURL url_c(embedded_test_server()->GetURL( |
| "c.com", "/cross_site_iframe_factory.html?c(k,l,m)")); |
| |
| NavigationControllerImpl& controller = web_contents()->GetController(); |
| BackForwardCacheImpl& cache = controller.GetBackForwardCache(); |
| |
| // 1. Navigate to a cacheable page (A). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| std::string frame_tree_a = DepictFrameTree(rfh_a->frame_tree_node()); |
| |
| // 2. Navigate from a cacheable page to an uncacheable page (A->B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_EQ(0u, render_frame_host_manager()->GetProxyCount()); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // Verify proxies are stored as well. |
| auto* cached_entry = cache.GetEntry(rfh_a->nav_entry_id()); |
| EXPECT_EQ(2u, cached_entry->proxy_hosts_size()); |
| |
| // 3. Navigate from an uncacheable to a cached page page (B->A). |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // Note: Since we put the page B into BackForwardCache briefly, we do not |
| // create a transition proxy. So there should be only proxies for i.com and |
| // j.com. |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| |
| // Page B should be deleted (not cached). |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| |
| // Page A should still have the correct frame tree. |
| EXPECT_EQ(frame_tree_a, |
| DepictFrameTree(current_frame_host()->frame_tree_node())); |
| |
| // 4. Navigate from a cacheable page to a cacheable page (A->C). |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount()); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // Verify proxies are stored as well. |
| cached_entry = cache.GetEntry(rfh_a->nav_entry_id()); |
| EXPECT_EQ(2u, cached_entry->proxy_hosts_size()); |
| |
| // 5. Navigate from a cacheable page to a cached page (C->A). |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| |
| // Page A should still have the correct frame tree. |
| EXPECT_EQ(frame_tree_a, |
| DepictFrameTree(current_frame_host()->frame_tree_node())); |
| |
| // Page C should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_c.deleted()); |
| EXPECT_TRUE(rfh_c->IsInBackForwardCache()); |
| |
| // Verify proxies are stored as well. |
| cached_entry = cache.GetEntry(rfh_c->nav_entry_id()); |
| EXPECT_EQ(3u, cached_entry->proxy_hosts_size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| RestoredProxiesAreFunctional) { |
| // This test makes assumption about where iframe processes live. |
| if (!AreAllSitesIsolatedForTesting()) |
| return; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Page A is cacheable, while page B is not. |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(z)")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title2.html")); |
| |
| NavigationControllerImpl& controller = web_contents()->GetController(); |
| |
| // 1. Navigate to a cacheable page (A). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2. Navigate from a cacheable page to an uncacheable page (A->B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| DisableBFCacheForRFHForTesting(rfh_b); |
| |
| // 3. Navigate from an uncacheable to a cached page page (B->A). |
| // This restores the top frame's proxy in the z.com (iframe's) process. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // 4. Verify that the main frame's z.com proxy is still functional. |
| RenderFrameHostImpl* iframe = |
| rfh_a->frame_tree_node()->child_at(0)->current_frame_host(); |
| EXPECT_TRUE(ExecJs(iframe, "top.location.href = '" + url_c.spec() + "';")); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // We expect to have navigated through the proxy. |
| EXPECT_EQ(url_c, controller.GetLastCommittedEntry()->GetURL()); |
| } |
| |
| // Flaky on Android, see crbug.com/1135601 and on other platforms, see |
| // crbug.com/1128772. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DISABLED_LogIpcPostedToCachedFrame) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page. |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2) Navigate away. The first page should be in the cache. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); |
| |
| // 3) Post IPC tasks to the page, testing both mojo remote and associated |
| // remote objects. |
| |
| // Send a message via an associated interface - which will post a task with an |
| // IPC hash and will be routed to the per-thread task queue. |
| base::RunLoop run_loop; |
| rfh_a->RequestTextSurroundingSelection( |
| base::BindOnce( |
| [](base::RepeatingClosure quit_closure, const std::u16string& str, |
| uint32_t num, uint32_t num2) { quit_closure.Run(); }, |
| run_loop.QuitClosure()), |
| 1); |
| run_loop.Run(); |
| |
| // Post a non-associated interface. Will be routed to a frame-specific task |
| // queue with IPC set in SimpleWatcher. |
| base::RunLoop run_loop2; |
| rfh_a->GetHighPriorityLocalFrame()->DispatchBeforeUnload( |
| false, |
| base::BindOnce([](base::RepeatingClosure quit_closure, bool proceed, |
| base::TimeTicks start_time, |
| base::TimeTicks end_time) { quit_closure.Run(); }, |
| run_loop2.QuitClosure())); |
| run_loop2.Run(); |
| |
| // 4) Check the histogram. |
| std::vector<base::HistogramBase::Sample> samples = { |
| base::HistogramBase::Sample( |
| base::TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName( |
| "blink.mojom.HighPriorityLocalFrame")), |
| base::HistogramBase::Sample( |
| base::TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName( |
| "blink.mojom.LocalFrame"))}; |
| |
| for (base::HistogramBase::Sample sample : samples) { |
| FetchHistogramsFromChildProcesses(); |
| EXPECT_TRUE(HistogramContainsIntValue( |
| sample, histogram_tester_.GetAllSamples( |
| "BackForwardCache.Experimental." |
| "UnexpectedIPCMessagePostedToCachedFrame.MethodHash"))); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackforwardCacheForTesting) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Disable the BackForwardCache. |
| web_contents()->GetController().GetBackForwardCache().DisableForTesting( |
| BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING); |
| |
| // Navigate to a page that would normally be cacheable. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); |
| RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host()); |
| |
| // Navigate away. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); |
| |
| // The page should be deleted (not cached). |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| // Navigate from A to B, then cause JavaScript execution on A, then go back. |
| // Test the RenderFrameHost in the cache is evicted by JavaScript. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EvictionOnJavaScriptExecution) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_b->IsInBackForwardCache()); |
| |
| // 3) Execute JavaScript on A. |
| EvictByJavaScript(rfh_a); |
| |
| // RenderFrameHost A is evicted from the BackForwardCache: |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 4) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.EvictionOnJavaScriptExecution. |
| // Test case: A(B) -> C -> JS on B -> A(B) |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EvictionOnJavaScriptExecutionIframe) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1) Navigate to A(B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // 2) Navigate to C. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_c.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_c->IsInBackForwardCache()); |
| |
| // 3) Execute JavaScript on B. |
| // |
| EvictByJavaScript(rfh_b); |
| |
| // The A(B) page is evicted. So A and B are removed: |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| |
| // 4) Go back to A(B). |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EvictionOnJavaScriptExecutionInAnotherWorld) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Execute JavaScript on A in a new world. This ensures a new world. |
| const int32_t kNewWorldId = content::ISOLATED_WORLD_ID_CONTENT_END + 1; |
| EXPECT_TRUE(ExecJs(rfh_a, "console.log('hi');", |
| EXECUTE_SCRIPT_DEFAULT_OPTIONS, kNewWorldId)); |
| |
| // 3) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_b->IsInBackForwardCache()); |
| |
| // 4) Execute JavaScript on A in the new world. |
| EXPECT_FALSE(ExecJs(rfh_a, "console.log('hi');", |
| EXECUTE_SCRIPT_DEFAULT_OPTIONS, kNewWorldId)); |
| |
| // RenderFrameHost A is evicted from the BackForwardCache: |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 5) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Navigates from page A -> page B -> page C -> page B -> page C. Page B becomes |
| // ineligible for bfcache in pagehide handler, so Page A stays in bfcache |
| // without being evicted even after the navigation to Page C. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| PagehideMakesPageIneligibleForBackForwardCacheAndNotCountedInCacheSize) { |
| ASSERT_TRUE(CreateHttpsServer()->Start()); |
| GURL url_a(https_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(https_server()->GetURL( |
| "b.com", "/back_forward_cache/page_with_broadcastchannel.html")); |
| GURL url_c(https_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1) Navigate to a.com. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2) Navigate to b.com. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver deleted_observer_rfh_b(rfh_b); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| // Acquire broadcast in pagehide. Now b.com is not eligible for bfcache. |
| EXPECT_TRUE( |
| ExecJs(rfh_b, "setShouldAcquireBroadcastChannelInPageHide(true);")); |
| |
| // 3) Navigate to c.com. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| // RenderFrameHostImpl* rfh_c = current_frame_host(); |
| // Since the b.com is not eligible for bfcache, |rfh_a| should stay in |
| // bfcache. |
| deleted_observer_rfh_b.WaitUntilDeleted(); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Navigate back to b.com. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kBlocklistedFeatures}, |
| {blink::scheduler::WebSchedulerTrackedFeature::kBroadcastChannel}, {}, {}, |
| {}, FROM_HERE); |
| RenderFrameHostImpl* rfh_b_2 = current_frame_host(); |
| // Do not acquire broadcast channel. Now b.com is eligible for bfcache. |
| EXPECT_TRUE( |
| ExecJs(rfh_b_2, "setShouldAcquireBroadcastChannelInPageHide(false);")); |
| |
| // 5) Navigate forward to c.com. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| ExpectRestored(FROM_HERE); |
| // b.com was eligible for bfcache and should stay in bfcache. |
| EXPECT_TRUE(rfh_b_2->IsInBackForwardCache()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EvictPageWithInfiniteLoop) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| ExecuteScriptAsync(rfh_a, R"( |
| let i = 0; |
| while (true) { i++; } |
| )"); |
| |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| RenderProcessHost* process = rfh_a->GetProcess(); |
| RenderProcessHostWatcher destruction_observer( |
| process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // rfh_a should be destroyed (not kept in the cache). |
| destruction_observer.Wait(); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // rfh_b should still be the current frame. |
| EXPECT_EQ(current_frame_host(), rfh_b); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kTimeoutPuttingInCache}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Test the race condition where a document is evicted from the BackForwardCache |
| // while it is in the middle of being restored and before URL loader starts a |
| // response. |
| // |
| // ┌───────┐ ┌────────┐ |
| // │Browser│ │Renderer│ |
| // └───┬───┘ └───┬────┘ |
| // (Freeze & store the cache) │ |
| // │────────────────────────>│ |
| // │ │ |
| // (Navigate to cached document) │ |
| // │──┐ │ |
| // │ │ │ |
| // │EvictFromBackForwardCache│ |
| // │<────────────────────────│ |
| // │ │ │ |
| // │ x Navigation cancelled │ |
| // │ and reissued │ |
| // ┌───┴───┐ ┌───┴────┐ |
| // │Browser│ │Renderer│ |
| // └───────┘ └────────┘ |
| // |
| // When the eviction occurs, the in flight NavigationRequest to the cached |
| // document should be reissued (cancelled and replaced by a normal navigation). |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| ReissuesNavigationIfEvictedDuringNavigation_BeforeResponse) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| |
| // 1) Navigate to page A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to page B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_NE(rfh_a, rfh_b); |
| |
| // 3) Start navigation to page A, and cause the document to be evicted during |
| // the navigation immediately before navigation makes any meaningful progress. |
| web_contents()->GetController().GoBack(); |
| EvictByJavaScript(rfh_a); |
| |
| // rfh_a should have been deleted, and page A navigated to normally. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| RenderFrameHostImpl* rfh_a2 = current_frame_host(); |
| EXPECT_NE(rfh_a2, rfh_b); |
| EXPECT_EQ(rfh_a2->GetLastCommittedURL(), url_a); |
| |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kJavaScriptExecution}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // Similar to ReissuesNavigationIfEvictedDuringNavigation, except that |
| // BackForwardCache::Flush is the source of the eviction. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| FlushCacheDuringNavigationToCachedPage) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| |
| // 1) Navigate to page A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a1 = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a1(rfh_a1); |
| |
| // 2) Navigate to page B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b2 = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b2(rfh_b2); |
| EXPECT_FALSE(delete_observer_rfh_a1.deleted()); |
| EXPECT_TRUE(rfh_a1->IsInBackForwardCache()); |
| EXPECT_NE(rfh_a1, rfh_b2); |
| |
| // 3) Start navigation to page A, and flush the cache during the navigation. |
| TestNavigationManager navigation_manager(shell()->web_contents(), url_a); |
| web_contents()->GetController().GoBack(); |
| |
| EXPECT_TRUE(navigation_manager.WaitForResponse()); |
| |
| // Flush the cache, which contains the document being navigated to. |
| web_contents()->GetController().GetBackForwardCache().Flush(); |
| |
| // The navigation should get canceled, then reissued; ultimately resulting in |
| // a successful navigation using a new RenderFrameHost. |
| navigation_manager.WaitForNavigationFinished(); |
| |
| // rfh_a should have been deleted, and page A navigated to normally. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| delete_observer_rfh_a1.WaitUntilDeleted(); |
| EXPECT_TRUE(rfh_b2->IsInBackForwardCache()); |
| RenderFrameHostImpl* rfh_a3 = current_frame_host(); |
| EXPECT_EQ(rfh_a3->GetLastCommittedURL(), url_a); |
| } |
| |
| // Test that if the renderer process crashes while a document is in the |
| // BackForwardCache, it gets evicted. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EvictsFromCacheIfRendererProcessCrashes) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Crash A's renderer process while it is in the cache. |
| { |
| RenderProcessHost* process = rfh_a->GetProcess(); |
| RenderProcessHostWatcher crash_observer( |
| process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| EXPECT_TRUE(process->Shutdown(0)); |
| crash_observer.Wait(); |
| } |
| |
| // rfh_b should still be the current frame. |
| EXPECT_EQ(current_frame_host(), rfh_b); |
| |
| // 4) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kRendererProcessKilled}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| // The test is simulating a race condition. The scheduler tracked features are |
| // updated during the "freeze" event in a way that would have prevented the |
| // document from entering the BackForwardCache in the first place. |
| // |
| // TODO(https://crbug.com/996267): The document should be evicted. |
| // |
| // ┌───────┐ ┌────────┐ |
| // │browser│ │renderer│ |
| // └───┬───┘ └────┬───┘ |
| // (enter cache) │ |
| // │ Freeze() │ |
| // │─────────────────────────────>│ |
| // │ (onfreeze) |
| // │OnSchedulerTrackedFeaturesUsed│ |
| // │<─────────────────────────────│ |
| // │ (frozen) |
| // │ │ |
| // ┌───┴───┐ ┌────┴───┐ |
| // │browser│ │renderer│ |
| // └───────┘ └────────┘ |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| SchedulerTrackedFeaturesUpdatedWhileStoring) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // When the page will enter the BackForwardCache, just before being frozen, |
| // use a feature that would have been prevented the document from being |
| // cached. |
| EXPECT_TRUE(ExecJs(rfh_a, R"( |
| document.addEventListener('freeze', event => { |
| window.foo = new BroadcastChannel('foo'); |
| }); |
| )")); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // rfh_a should be evicted from the cache and destroyed. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| class BackForwardCacheBrowserTestWithSameSiteDisabled |
| : public BackForwardCacheBrowserTest { |
| public: |
| BackForwardCacheBrowserTestWithSameSiteDisabled() = default; |
| ~BackForwardCacheBrowserTestWithSameSiteDisabled() override = default; |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| same_site_back_forward_cache_enabled_ = false; |
| DisableFeature(features::kProactivelySwapBrowsingInstance); |
| // Ensure that the bot flags won't override the same-site back/forward cache |
| // disabling. |
| DisableFeature(features::kBackForwardCacheSameSiteForBots); |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled, |
| ConflictingBrowsingInstances) { |
| // This test assumes navigation from A1 to A2 will not switch |
| // BrowsingInstances, which is not true when either BackForwardCache or |
| // ProactivelySwapBrowsingInstance is enabled on same-site navigations. |
| DCHECK(!CanSameSiteMainFrameNavigationsChangeSiteInstances()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| GURL url_b3(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| |
| // 2) Navigate to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| RenderFrameHostImpl* rfh_a2 = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a2(current_frame_host()); |
| |
| // 3) Navigate to B3. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b3)); |
| EXPECT_TRUE(rfh_a2->IsInBackForwardCache()); |
| RenderFrameHostImpl* rfh_b3 = current_frame_host(); |
| // Make B3 ineligible for caching, so that navigating doesn't evict A2 |
| // due to the cache size limit. |
| DisableBFCacheForRFHForTesting(rfh_b3); |
| |
| // 4) Do a history navigation back to A1. At this point, A1 is going to have |
| // the same BrowsingInstance as A2. This should cause A2 to get |
| // evicted from the BackForwardCache due to its conflicting BrowsingInstance. |
| ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0)); |
| EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), url_a1); |
| delete_rfh_a2.WaitUntilDeleted(); |
| |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kBrowsingInstanceNotSwapped}, |
| {}, {ShouldSwapBrowsingInstance::kNo_SameSiteNavigation}, {}, {}, |
| FROM_HERE); |
| |
| // 5) Go to A2. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| |
| ExpectNotRestored( |
| { |
| BackForwardCacheMetrics::NotRestoredReason:: |
| kConflictingBrowsingInstance, |
| }, |
| {}, {}, {}, {}, FROM_HERE); |
| } |
| |
| // When same-site bfcache is disabled, we should not cache on same-site |
| // navigations. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled, |
| DoesNotCacheOnSameSiteNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| GURL url_a3( |
| embedded_test_server()->GetURL("subdomain.a.com", "/title3.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| RenderFrameHostImpl* rfh_a1 = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a1(rfh_a1); |
| auto browsing_instance_id = |
| rfh_a1->GetSiteInstance()->GetBrowsingInstanceId(); |
| |
| // 2) Navigate same-site and same-origin to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| RenderFrameHostImpl* rfh_a2 = current_frame_host(); |
| // The BrowsingInstance shouldn't have changed. |
| EXPECT_EQ(browsing_instance_id, |
| rfh_a2->GetSiteInstance()->GetBrowsingInstanceId()); |
| // The previous page should not be cached. |
| EXPECT_FALSE(rfh_a1->IsInBackForwardCache()); |
| |
| // 2) Navigate same-site but cross-origin to A3. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a3)); |
| RenderFrameHostImpl* rfh_a3 = current_frame_host(); |
| // The BrowsingInstance shouldn't have changed. |
| EXPECT_EQ(browsing_instance_id, |
| rfh_a3->GetSiteInstance()->GetBrowsingInstanceId()); |
| // The previous page should not be cached. |
| EXPECT_FALSE(rfh_a2->IsInBackForwardCache()); |
| } |
| |
| // Check that during a same-RenderFrameHost cross-document navigation, the |
| // disabled reasons is still tracked. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithSameSiteDisabled, |
| DisableForRenderFrameHostPersistsAcrossNavigations) { |
| // This test assumes navigation from A1 to A2 will not switch |
| // RenderFrameHosts which is not true when BackForwardCache, |
| // ProactivelySwapBrowsingInstance or RenderDocument is enabled on same-site |
| // main frame navigations. |
| DCHECK(!CanSameSiteMainFrameNavigationsChangeRenderFrameHosts()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| GURL url_b3(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| RenderFrameHostImpl* rfh_a1 = current_frame_host(); |
| RenderFrameDeletedObserver deleted_observer_rfh_a1(rfh_a1); |
| // Disable back-forward cache for A. |
| DisableBFCacheForRFHForTesting(rfh_a1); |
| |
| // 2) Navigate to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| EXPECT_FALSE(deleted_observer_rfh_a1.deleted()); |
| EXPECT_EQ(rfh_a1, current_frame_host()); |
| |
| // 3) Navigate to B3. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b3)); |
| deleted_observer_rfh_a1.WaitUntilDeleted(); |
| |
| // 4) Go back to A2. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kDisableForRenderFrameHostCalled}, |
| {}, {}, {RenderFrameHostDisabledForTestingReason()}, {}, |
| FROM_HERE); |
| } |
| |
| // The BackForwardCache caches same-website navigations. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SameSiteNavigationCaching) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| RenderFrameHostImpl* rfh_a1 = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a1(rfh_a1); |
| auto browsing_instance_id = |
| rfh_a1->GetSiteInstance()->GetBrowsingInstanceId(); |
| |
| // 2) Navigate to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| RenderFrameHostImpl* rfh_a2 = current_frame_host(); |
| EXPECT_NE(browsing_instance_id, |
| rfh_a2->GetSiteInstance()->GetBrowsingInstanceId()); |
| EXPECT_TRUE(rfh_a1->IsInBackForwardCache()); |
| EXPECT_NE(rfh_a1, rfh_a2); |
| } |
| |
| // Test that documents are evicted correctly from BackForwardCache after time to |
| // live. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, TimedEviction) { |
| // Inject mock time task runner to be used in the eviction timer, so we can, |
| // check for the functionality we are interested before and after the time to |
| // live. We don't replace ThreadTaskRunnerHandle::Get to ensure that it |
| // doesn't affect other unrelated callsites. |
| scoped_refptr<base::TestMockTimeTaskRunner> task_runner = |
| base::MakeRefCounted<base::TestMockTimeTaskRunner>(); |
| |
| web_contents()->GetController().GetBackForwardCache().SetTaskRunnerForTesting( |
| task_runner); |
| |
| base::TimeDelta time_to_live_in_back_forward_cache = |
| BackForwardCacheImpl::GetTimeToLiveInBackForwardCache(); |
| // This should match the value we set in EnableFeatureAndSetParams. |
| EXPECT_EQ(time_to_live_in_back_forward_cache, base::Seconds(3600)); |
| |
| base::TimeDelta delta = base::Milliseconds(1); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| |
| // 3) Fast forward to just before eviction is due. |
| task_runner->FastForwardBy(time_to_live_in_back_forward_cache - delta); |
| |
| // 4) Confirm A is still in BackForwardCache. |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 5) Fast forward to when eviction is due. |
| task_runner->FastForwardBy(delta); |
| |
| // 6) Confirm A is evicted. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| EXPECT_EQ(current_frame_host(), rfh_b); |
| |
| // 7) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kTimeout}, {}, |
| {}, {}, {}, FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| DisableBackForwardCachePreventsDocumentsFromBeingCached) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| url::Origin origin_a = url::Origin::Create(url_a); |
| url::Origin origin_b = url::Origin::Create(url_b); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| DisableBFCacheForRFHForTesting(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kDisableForRenderFrameHostCalled}, |
| {}, {}, {RenderFrameHostDisabledForTestingReason()}, {}, |
| FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackForwardIsNoOpIfRfhIsGone) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| url::Origin origin_a = url::Origin::Create(url_a); |
| url::Origin origin_b = url::Origin::Create(url_b); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| GlobalRenderFrameHostId rfh_a_id = rfh_a->GetGlobalId(); |
| DisableBFCacheForRFHForTesting(rfh_a_id); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // This should not die |
| DisableBFCacheForRFHForTesting(rfh_a_id); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kDisableForRenderFrameHostCalled}, |
| {}, {}, {RenderFrameHostDisabledForTestingReason()}, {}, |
| FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackForwardCacheIframe) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_c(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| DisableBFCacheForRFHForTesting(rfh_b); |
| |
| // 2) Navigate to C. A and B are deleted. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kDisableForRenderFrameHostCalled}, |
| {}, {}, {RenderFrameHostDisabledForTestingReason()}, {}, |
| FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackForwardEvictsIfAlreadyInCache) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| url::Origin origin_a = url::Origin::Create(url_a); |
| url::Origin origin_b = url::Origin::Create(url_b); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_a->is_evicted_from_back_forward_cache()); |
| |
| DisableBFCacheForRFHForTesting(rfh_a); |
| |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kDisableForRenderFrameHostCalled}, |
| {}, {}, {RenderFrameHostDisabledForTestingReason()}, {}, |
| FROM_HERE); |
| } |
| |
| // Confirm that same-document navigation and not history-navigation does not |
| // record metrics. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, MetricsNotRecorded) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| GURL url_b2(embedded_test_server()->GetURL("b.com", "/title1.html#2")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // 3) Navigate to B#2 (same document navigation). |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_b2)); |
| |
| // 4) Go back to B. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| |
| // 5) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| } |
| |
| // Test for functionality of domain specific controls in back-forward cache. |
| class BackForwardCacheBrowserTestWithDomainControlEnabled |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Sets the allowed websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string allowed_websites = |
| "https://a.allowed/back_forward_cache/, " |
| "https://b.allowed/back_forward_cache/allowed_path.html"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "allowed_websites", |
| allowed_websites); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check the RenderFrameHost allowed to enter the BackForwardCache are the ones |
| // matching with the "allowed_websites" feature params. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithDomainControlEnabled, |
| CachePagesWithMatchedURLs) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.allowed", "/back_forward_cache/allowed_path.html")); |
| GURL url_b(embedded_test_server()->GetURL( |
| "b.allowed", "/back_forward_cache/allowed_path.html?query=bar")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // 3) Check if rfh_a is stored in back-forward cache, since it matches to |
| // the list of allowed urls, it should be stored. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Now go back to the last stored page, which in our case should be A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| |
| // 5) Check if rfh_b is stored in back-forward cache, since it matches to |
| // the list of allowed urls, it should be stored. |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| } |
| |
| // We don't want to allow websites which doesn't match "allowed_websites" of |
| // feature params to be stored in back-forward cache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithDomainControlEnabled, |
| DoNotCachePagesWithUnMatchedURLs) { |
| DisableCheckingMetricsForAllSites(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.disallowed", "/back_forward_cache/disallowed_path.html")); |
| GURL url_b(embedded_test_server()->GetURL( |
| "b.allowed", "/back_forward_cache/disallowed_path.html")); |
| GURL url_c(embedded_test_server()->GetURL( |
| "c.disallowed", "/back_forward_cache/disallowed_path.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // 3) Since url of A doesn't match to the the list of allowed urls it should |
| // not be stored in back-forward cache. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 4) Navigate to C. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| |
| // 5) Since url of B doesn't match to the the list of allowed urls it should |
| // not be stored in back-forward cache. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| |
| // 6) Go back to B. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // Nothing is recorded when the domain does not match. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| } |
| |
| // Test the "blocked_websites" feature params in back-forward cache. |
| class BackForwardCacheBrowserTestWithBlockedWebsites |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Sets the blocked websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string blocked_websites = |
| "https://a.blocked/, " |
| "https://b.blocked/"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_websites", |
| blocked_websites); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check the disallowed page isn't bfcached when it's navigated from allowed |
| // page. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedWebsites, |
| NavigateFromAllowedPageToDisallowedPage) { |
| // Skip checking the AllSites metrics since BackForwardCacheMetrics stop |
| // recording except BackForwardCache.AllSites.* metrics when the target URL is |
| // disallowed by allowed_websites or blocked_websites. |
| DisableCheckingMetricsForAllSites(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.allowed", "/back_forward_cache/allowed_path.html")); |
| GURL url_b(embedded_test_server()->GetURL( |
| "b.blocked", "/back_forward_cache/disallowed_path.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // 3) Check if rfh_a is stored in back-forward cache, since it doesn't match |
| // to the blocked_websites, and allowed_websites are empty, so it should |
| // be stored. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Now go back to the last stored page, which in our case should be A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| ExpectRestored(FROM_HERE); |
| |
| // 5) Check if rfh_b is not stored in back-forward cache, since it matches to |
| // the blocked_websites. |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| EXPECT_TRUE(delete_observer_rfh_b.deleted()); |
| |
| // 6) Go forward to B. B should not restored from the back-forward cache. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| |
| // Nothing is recorded since B is disallowed. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| } |
| |
| // Check the allowed page is bfcached when it's navigated from disallowed |
| // page. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedWebsites, |
| NavigateFromDisallowedPageToAllowedPage) { |
| // Skip checking the AllSites metrics since BackForwardCacheMetrics stop |
| // recording except BackForwardCache.AllSites.* metrics when the target URL is |
| // disallowed by allowed_websites or blocked_websites. |
| DisableCheckingMetricsForAllSites(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.blocked", "/back_forward_cache/disallowed_path.html")); |
| GURL url_b(embedded_test_server()->GetURL( |
| "b.allowed", "/back_forward_cache/allowed_path.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // 3) Check if rfh_a is not stored in back-forward cache, since it matches to |
| // the blocked_websites. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| |
| // 4) Now go back to url_a which is not bfcached. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // Nothing is recorded since A is disallowed. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| |
| // 5) Check if rfh_b is stored in back-forward cache, since it doesn't match |
| // to the blocked_websites, and allowed_websites are empty, so it should |
| // be stored. |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| |
| // 6) Go forward to url_b which is bfcached. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Test BackForwardCache::IsAllowed() with several allowed_websites URL |
| // patterns. |
| class BackForwardCacheBrowserTestForAllowedWebsitesUrlPatterns |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Sets the allowed websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string allowed_websites = |
| "https://a.com/," |
| "https://b.com/path," |
| "https://c.com/path/"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "allowed_websites", |
| allowed_websites); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check if the URLs are allowed when allowed_websites are specified. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForAllowedWebsitesUrlPatterns, |
| AllowedWebsitesUrlPatterns) { |
| BackForwardCacheImpl& bfcache = |
| web_contents()->GetController().GetBackForwardCache(); |
| |
| // Doesn't match with any allowed_websites. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.org/"))); |
| |
| // Exact match with https://a.com/. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com/"))); |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com"))); |
| |
| // Match with https://a.com/ since we don't take into account the difference |
| // on port number. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com:123/"))); |
| |
| // Match with https://a.com/ since we don't take into account the difference |
| // on query. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com:123/?x=1"))); |
| |
| // Match with https://a.com/ since we don't take into account the difference |
| // on scheme. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("http://a.com/"))); |
| |
| // Match with https://a.com/ since we are checking the prefix on path. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.com/path"))); |
| |
| // Doesn't match with https://a.com/ since the host doesn't match with a.com. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://prefix.a.com/"))); |
| |
| // Doesn't match with https://b.com/path since the path prefix doesn't match. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/"))); |
| |
| // Exact match with https://b.com/path. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path"))); |
| |
| // Match with https://b.com/path since we are checking the prefix on path. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path/"))); |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path_abc"))); |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/path_abc?x=1"))); |
| |
| // Doesn't match with https://c.com/path/ since the path prefix doesn't match. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://c.com/path"))); |
| } |
| |
| // Test BackForwardCache::IsAllowed() with several blocked_websites URL |
| // patterns. |
| class BackForwardCacheBrowserTestForBlockedWebsitesUrlPatterns |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Sets the blocked websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string blocked_websites = |
| "https://a.com/," |
| "https://b.com/path," |
| "https://c.com/path/"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_websites", |
| blocked_websites); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check if the URLs are allowed when blocked_websites are specified. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForBlockedWebsitesUrlPatterns, |
| BlockedWebsitesUrlPatterns) { |
| BackForwardCacheImpl& bfcache = |
| web_contents()->GetController().GetBackForwardCache(); |
| |
| // Doesn't match with any blocked_websites. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://a.org/"))); |
| |
| // Exact match with https://a.com/. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com/"))); |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com"))); |
| |
| // Match with https://a.com/ since we don't take into account the difference |
| // on port number. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com:123/"))); |
| |
| // Match with https://a.com/ since we don't take into account the difference |
| // on query. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com:123/?x=1"))); |
| |
| // Match with https://a.com/ since we don't take into account the difference |
| // on scheme. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("http://a.com/"))); |
| |
| // Match with https://a.com/ since we are checking the prefix on path. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com/path"))); |
| |
| // Doesn't match with https://a.com/ since the host doesn't match with a.com. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://prefix.a.com/"))); |
| |
| // Doesn't match with https://b.com/path since the path prefix doesn't match. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://b.com/"))); |
| |
| // Exact match with https://b.com/path. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path"))); |
| |
| // Match with https://b.com/path since we are checking the prefix on path. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path/"))); |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path_abc"))); |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://b.com/path_abc?x=1"))); |
| |
| // Doesn't match with https://c.com/path/ since the path prefix doesn't match. |
| EXPECT_TRUE(bfcache.IsAllowed(GURL("https://c.com/path"))); |
| } |
| |
| // Test BackForwardCache::IsAllowed() with several allowed_websites and |
| // blocked_websites URL patterns. |
| class BackForwardCacheBrowserTestForWebsitesUrlPatterns |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Sets the allowed websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string allowed_websites = "https://a.com/"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "allowed_websites", |
| allowed_websites); |
| |
| // Sets the blocked websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string blocked_websites = "https://a.com/"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_websites", |
| blocked_websites); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check if the URLs are allowed when allowed_websites and blocked_websites are |
| // specified. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestForWebsitesUrlPatterns, |
| WebsitesUrlPatterns) { |
| BackForwardCacheImpl& bfcache = |
| web_contents()->GetController().GetBackForwardCache(); |
| |
| // https://a.com/ is not allowed since blocked_websites will be prioritized |
| // when the same website is specified in allowed_websites and |
| // blocked_websites. |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com/"))); |
| EXPECT_FALSE(bfcache.IsAllowed(GURL("https://a.com"))); |
| } |
| |
| // Test the "blocked_cgi_params" feature params in back-forward cache. |
| class BackForwardCacheBrowserTestWithBlockedCgiParams |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // Sets the blocked websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::string blocked_cgi_params = "ibp=1|tbm=1"; |
| EnableFeatureAndSetParams(features::kBackForwardCache, "blocked_cgi_params", |
| blocked_cgi_params); |
| |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| }; |
| |
| // Check the disallowed page isn't bfcached when it's navigated from allowed |
| // page. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedCgiParams, |
| NavigateFromAllowedPageToDisallowedPage) { |
| // Skip checking the AllSites metrics since BackForwardCacheMetrics stop |
| // recording except BackForwardCache.AllSites.* metrics when the target URL is |
| // disallowed by allowed_websites or blocked_websites. |
| DisableCheckingMetricsForAllSites(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_allowed( |
| embedded_test_server()->GetURL("a.llowed", "/title1.html?tbm=0")); |
| GURL url_not_allowed( |
| embedded_test_server()->GetURL("nota.llowed", "/title1.html?tbm=1")); |
| |
| // 1) Navigate to url_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_allowed(rfh_allowed); |
| |
| // 2) Navigate to url_not_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_not_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_not_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_not_allowed(rfh_not_allowed); |
| |
| // 3) Check that url_allowed is stored in back-forward cache. |
| EXPECT_FALSE(delete_observer_rfh_allowed.deleted()); |
| EXPECT_TRUE(rfh_allowed->IsInBackForwardCache()); |
| |
| // 4) Now go back to url_allowed. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(rfh_allowed, current_frame_host()); |
| ExpectRestored(FROM_HERE); |
| |
| // 5) Check that url_not_allowed is not stored in back-forward cache |
| delete_observer_rfh_not_allowed.WaitUntilDeleted(); |
| EXPECT_TRUE(delete_observer_rfh_not_allowed.deleted()); |
| |
| // 6) Go forward to url_not_allowed, it should not be restored from the |
| // back-forward cache. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| |
| // Nothing is recorded since it is disallowed. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| } |
| |
| // Check the allowed page is bfcached when it's navigated from disallowed |
| // page. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithBlockedCgiParams, |
| NavigateFromDisallowedPageToAllowedPage) { |
| // Skip checking the AllSites metrics since BackForwardCacheMetrics stop |
| // recording except BackForwardCache.AllSites.* metrics when the target URL is |
| // disallowed by allowed_websites or blocked_websites. |
| DisableCheckingMetricsForAllSites(); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_allowed( |
| embedded_test_server()->GetURL("a.llowed", "/title1.html?tbm=0")); |
| GURL url_not_allowed( |
| embedded_test_server()->GetURL("nota.llowed", "/title1.html?tbm=1")); |
| |
| // 1) Navigate to url_not_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_not_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_not_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_not_allowed(rfh_not_allowed); |
| |
| // 2) Navigate to url_allowed. |
| EXPECT_TRUE(NavigateToURL(shell(), url_allowed)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_allowed = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_allowed(rfh_allowed); |
| |
| // 3) Check that url_not_allowed is not stored in back-forward cache. |
| delete_observer_rfh_not_allowed.WaitUntilDeleted(); |
| EXPECT_TRUE(delete_observer_rfh_not_allowed.deleted()); |
| |
| // 4) Now go back to url_not_allowed. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // Nothing is recorded since it is disallowed. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| |
| // 5) Check that url_allowed is stored in back-forward cache |
| EXPECT_FALSE(delete_observer_rfh_allowed.deleted()); |
| EXPECT_TRUE(rfh_allowed->IsInBackForwardCache()); |
| |
| // 6) Go forward to url_allowed, it should be restored from the |
| // back-forward cache. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Check that if WebPreferences was changed while a page was bfcached, it will |
| // get up-to-date WebPreferences when it was restored. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WebPreferences) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| auto browsing_instance_id = rfh_a->GetSiteInstance()->GetBrowsingInstanceId(); |
| |
| // A should prefer light color scheme (which is the default). |
| EXPECT_EQ( |
| true, |
| EvalJs(web_contents(), |
| "window.matchMedia('(prefers-color-scheme: light)').matches")); |
| |
| // 2) Navigate to B. A should be stored in the back-forward cache. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| EXPECT_NE(browsing_instance_id, |
| rfh_b->GetSiteInstance()->GetBrowsingInstanceId()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_NE(rfh_a, rfh_b); |
| |
| blink::web_pref::WebPreferences prefs = |
| web_contents()->GetOrCreateWebPreferences(); |
| prefs.preferred_color_scheme = blink::mojom::PreferredColorScheme::kDark; |
| web_contents()->SetWebPreferences(prefs); |
| |
| // 3) Set WebPreferences to prefer dark color scheme. |
| EXPECT_EQ( |
| true, |
| EvalJs(web_contents(), |
| "window.matchMedia('(prefers-color-scheme: dark)').matches")); |
| |
| // 4) Go back to A, which should also prefer the dark color scheme now. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| |
| EXPECT_EQ( |
| true, |
| EvalJs(web_contents(), |
| "window.matchMedia('(prefers-color-scheme: dark)').matches")); |
| } |
| |
| // Check the BackForwardCache is disabled when there is a nested WebContents |
| // inside a page. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NestedWebContents) { |
| // 1) Navigate to a page. |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url(embedded_test_server()->GetURL("a.com", "/page_with_iframe.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameHostImpl* child = rfh_a->child_at(0)->current_frame_host(); |
| EXPECT_TRUE(child); |
| |
| // Create and attach an inner WebContents. |
| CreateAndAttachInnerContents(child); |
| RenderFrameDeletedObserver deleted(rfh_a); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| // The page has an inner WebContents so it should be deleted. |
| deleted.WaitUntilDeleted(); |
| |
| // 3) Go back to the page with an inner WebContents. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kHaveInnerContents}, {}, {}, |
| {}, {}, FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, RestoreWhilePendingCommit) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| GURL url3(embedded_test_server()->GetURL("c.com", "/main_document")); |
| |
| // Load a page and navigate away from it, so it is stored in the back-forward |
| // cache. |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| RenderFrameHost* rfh1 = current_frame_host(); |
| EXPECT_TRUE(NavigateToURL(shell(), url2)); |
| |
| // Try to navigate to a new page, but leave it in a pending state. |
| shell()->LoadURL(url3); |
| response.WaitForRequest(); |
| |
| // Navigate back and restore page from the cache, cancelling the previous |
| // navigation. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(rfh1, current_frame_host()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| IsInactiveAndDisallowActivationIsNoopWhenActive) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_FALSE(current_frame_host()->IsInactiveAndDisallowActivation( |
| DisallowActivationReasonId::kForTesting)); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| IsInactiveAndDisallowActivationDoesEvictForCachedFrames) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host()); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| const uint64_t reason = DisallowActivationReasonId::kForTesting; |
| EXPECT_TRUE(rfh_a->IsInactiveAndDisallowActivation(reason)); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kIgnoreEventAndEvict}, {}, |
| {}, {}, {reason}, FROM_HERE); |
| } |
| |
| // Test scenarios where the "BackForwardCache" content flag is enabled but |
| // the command line flag "DisableBackForwardCache" is turned on, resulting in |
| // the feature being disabled. |
| class BackForwardCacheDisabledThroughCommandLineBrowserTest |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitch(switches::kDisableBackForwardCache); |
| EnableFeatureAndSetParams(blink::features::kLoadingTasksUnfreezable, |
| "max_buffered_bytes_per_process", "1000"); |
| } |
| }; |
| |
| // Ensures that the back-forward cache trial stays inactivated. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledThroughCommandLineBrowserTest, |
| BFCacheDisabled) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // Ensure that the trial starts inactive. |
| EXPECT_FALSE(base::FieldTrialList::IsTrialActive( |
| base::FeatureList::GetFieldTrial(features::kBackForwardCache) |
| ->trial_name())); |
| |
| EXPECT_FALSE(IsBackForwardCacheEnabled()); |
| |
| // Ensure that we do not activate the trial when querying bfcache status, |
| // which is protected by low-memory setting. |
| EXPECT_FALSE(base::FieldTrialList::IsTrialActive( |
| base::FeatureList::GetFieldTrial(features::kBackForwardCache) |
| ->trial_name())); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // 3) A shouldn't be stored in back-forward cache because it's disabled. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // Nothing is recorded when back-forward cache is disabled. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| |
| // Ensure that the trial still hasn't been activated. |
| EXPECT_FALSE(base::FieldTrialList::IsTrialActive( |
| base::FeatureList::GetFieldTrial(features::kBackForwardCache) |
| ->trial_name())); |
| } |
| |
| // Ensures that the back-forward cache trial stays inactivated even when |
| // renderer code related to back-forward cache runs (in this case, network |
| // request loading). |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledThroughCommandLineBrowserTest, |
| BFCacheDisabled_NetworkRequests) { |
| net::test_server::ControllableHttpResponse image_response( |
| embedded_test_server(), "/image.png"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // Ensure that the trials starts inactive. |
| EXPECT_FALSE(base::FieldTrialList::IsTrialActive( |
| base::FeatureList::GetFieldTrial(features::kBackForwardCache) |
| ->trial_name())); |
| |
| EXPECT_FALSE(IsBackForwardCacheEnabled()); |
| |
| // Ensure that we do not activate the trials for kBackForwardCache and |
| // kLoadingTasksUnfreezable when querying bfcache or unfreezable loading tasks |
| // status. |
| EXPECT_FALSE(base::FieldTrialList::IsTrialActive( |
| base::FeatureList::GetFieldTrial(features::kBackForwardCache) |
| ->trial_name())); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // Request for an image and send a response to trigger loading code. This is |
| // to ensure kLoadingTasksUnfreezable won't trigger bfcache activation. |
| EXPECT_TRUE(ExecJs(rfh_a, R"( |
| var image = document.createElement("img"); |
| image.src = "image.png"; |
| document.body.appendChild(image); |
| )")); |
| image_response.WaitForRequest(); |
| image_response.Send(net::HTTP_OK, "image/png"); |
| image_response.Send("image_body"); |
| image_response.Done(); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // 3) A shouldn't be stored in back-forward cache because it's disabled. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // Nothing is recorded when back-forward cache is disabled. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| ExpectNotRestoredDidNotChange(FROM_HERE); |
| |
| // Ensure that the trials still haven't been activated. |
| EXPECT_FALSE(base::FieldTrialList::IsTrialActive( |
| base::FeatureList::GetFieldTrial(features::kBackForwardCache) |
| ->trial_name())); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| EvictingDocumentsInRelatedSiteInstancesDoesNotRestartNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html#part1")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title1.html#part2")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| |
| // 2) Navigate to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| |
| // 3) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // 4) Go back to A2, but do not wait for the navigation to commit. |
| web_contents()->GetController().GoBack(); |
| |
| // 5) Go back to A1. |
| // This will attempt to evict A2 from the cache because |
| // their navigation entries have related site instances, while a navigation |
| // to A2 is in flight. Ensure that we do not try to restart it as it should |
| // be superseded by a navigation to A1. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(url_a1, web_contents()->GetLastCommittedURL()); |
| } |
| |
| namespace { |
| |
| class ExecJsInDidFinishNavigation : public WebContentsObserver { |
| public: |
| explicit ExecJsInDidFinishNavigation(WebContents* web_contents) |
| : WebContentsObserver(web_contents) {} |
| |
| void DidFinishNavigation(NavigationHandle* navigation_handle) override { |
| if (!navigation_handle->IsInMainFrame() || |
| !navigation_handle->HasCommitted() || |
| navigation_handle->IsSameDocument()) { |
| return; |
| } |
| |
| ExecuteScriptAsync(navigation_handle->GetRenderFrameHost(), |
| "var foo = 42;"); |
| } |
| }; |
| |
| } // namespace |
| |
| // This test checks that the message posted from DidFinishNavigation |
| // (ExecuteScriptAsync) is received after the message restoring the page from |
| // the back-forward cache (PageMsg_RestorePageFromBackForwardCache). |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MessageFromDidFinishNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| EXPECT_TRUE(ExecJs(rfh_a, "window.alive = 'I am alive';")); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| ExecJsInDidFinishNavigation observer(shell()->web_contents()); |
| |
| // 3) Go back to A. Expect the page to be restored from the cache. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ("I am alive", EvalJs(rfh_a, "window.alive")); |
| |
| // Make sure that the javascript execution requested from DidFinishNavigation |
| // did not result in eviction. If the document was evicted, the document |
| // would be reloaded - check that it didn't happen and the tab is not |
| // loading. |
| EXPECT_FALSE(web_contents()->IsLoading()); |
| |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| } |
| |
| #if defined(OS_ANDROID) |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| ChildImportanceTestForBackForwardCachedPagesTest) { |
| web_contents()->SetPrimaryMainFrameImportance( |
| ChildProcessImportance::MODERATE); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| |
| // 3) Verify the importance of page after entering back-forward cache to be |
| // "NORMAL". |
| EXPECT_EQ(ChildProcessImportance::NORMAL, |
| rfh_a->GetProcess()->GetEffectiveImportance()); |
| |
| // 4) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // 5) Verify the importance was restored correctly after page leaves |
| // back-forward cache. |
| EXPECT_EQ(ChildProcessImportance::MODERATE, |
| rfh_a->GetProcess()->GetEffectiveImportance()); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, PageshowMetrics) { |
| // TODO(https://crbug.com/1099395): Do not check for unexpected messages |
| // because the input task queue is not currently frozen, causing flakes in |
| // this test. |
| DoNotFailForUnexpectedMessagesWhileCached(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| const char kHistogramName[] = |
| "BackForwardCache.MainFrameHasPageshowListenersOnRestore"; |
| |
| const GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url2(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to the page. |
| EXPECT_TRUE(NavigateToURL(shell(), url1)); |
| EXPECT_TRUE(ExecJs(current_frame_host(), R"( |
| window.foo = 42; |
| )")); |
| |
| // 2) Navigate away and back. |
| EXPECT_TRUE(NavigateToURL(shell(), url2)); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // As we don't get an explicit ACK when the page is restored (yet), force |
| // a round-trip to the renderer to effectively flush the queue. |
| EXPECT_EQ(42, EvalJs(current_frame_host(), "window.foo")); |
| |
| // Expect the back-forward restore without pageshow to be detected. |
| content::FetchHistogramsFromChildProcesses(); |
| EXPECT_THAT(histogram_tester_.GetAllSamples(kHistogramName), |
| ElementsAre(base::Bucket(0, 1))); |
| |
| EXPECT_TRUE(ExecJs(current_frame_host(), R"( |
| window.addEventListener("pageshow", () => {}); |
| )")); |
| |
| // 3) Navigate away and back (again). |
| EXPECT_TRUE(NavigateToURL(shell(), url2)); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| // As we don't get an explicit ACK when the page is restored (yet), force |
| // a round-trip to the renderer to effectively flush the queue. |
| EXPECT_EQ(42, EvalJs(current_frame_host(), "window.foo")); |
| |
| // Expect the back-forward restore with pageshow to be detected. |
| content::FetchHistogramsFromChildProcesses(); |
| EXPECT_THAT(histogram_tester_.GetAllSamples(kHistogramName), |
| ElementsAre(base::Bucket(0, 1), base::Bucket(1, 1))); |
| } |
| |
| // Navigate from A(B) to C and check IsActive status for RenderFrameHost A |
| // and B before and after entering back-forward cache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CheckIsActive) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1) Navigate to A(B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| |
| EXPECT_TRUE(rfh_a->IsActive()); |
| EXPECT_TRUE(rfh_b->IsActive()); |
| |
| // 2) Navigate to C. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| |
| EXPECT_FALSE(rfh_a->IsActive()); |
| EXPECT_FALSE(rfh_b->IsActive()); |
| } |
| |
| // Test that LifecycleStateImpl is updated correctly when page enters and |
| // restores back from BackForwardCache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CheckLifecycleStateTransition) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| |
| // 1) Navigate to A and check the LifecycleStateImpl of A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_a->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_a->GetLifecycleState()); |
| EXPECT_TRUE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_TRUE(rfh_a->IsInPrimaryMainFrame()); |
| |
| // 2) Navigate to B, now A enters BackForwardCache. Check the |
| // LifecycleStateImpl of both RenderFrameHost A and B. |
| { |
| ::testing::NiceMock<MockWebContentsObserver> state_change_observer( |
| web_contents()); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_a, RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kInBackForwardCache)); |
| // We don't know |rfh_b| yet, so we'll match any frame. |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| ::testing::Not(rfh_a), |
| RenderFrameHost::LifecycleState::kPendingCommit, |
| RenderFrameHost::LifecycleState::kActive)); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| } |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_a->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache, |
| rfh_a->GetLifecycleState()); |
| EXPECT_FALSE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_FALSE(rfh_a->IsInPrimaryMainFrame()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_b->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_b->GetLifecycleState()); |
| EXPECT_TRUE(rfh_b->GetPage().IsPrimary()); |
| EXPECT_TRUE(rfh_b->IsInPrimaryMainFrame()); |
| |
| // 3) Go back to A and check again the LifecycleStateImpl of both |
| // RenderFrameHost A and B. |
| { |
| ::testing::NiceMock<MockWebContentsObserver> state_change_observer( |
| web_contents()); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_a, RenderFrameHost::LifecycleState::kInBackForwardCache, |
| RenderFrameHost::LifecycleState::kActive)); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_b, RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kInBackForwardCache)); |
| |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| } |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_a->lifecycle_state()); |
| EXPECT_TRUE(rfh_a->GetPage().IsPrimary()); |
| EXPECT_TRUE(rfh_a->IsInPrimaryMainFrame()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_b->lifecycle_state()); |
| EXPECT_FALSE(rfh_b->GetPage().IsPrimary()); |
| EXPECT_FALSE(rfh_b->IsInPrimaryMainFrame()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CheckLifecycleStateTransitionWithSubframes) { |
| IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_c(embedded_test_server()->GetURL( |
| "c.com", "/cross_site_iframe_factory.html?c(d)")); |
| |
| // Navigate to A(B) and check the lifecycle states of A and B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host(); |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_b->IsInBackForwardCache()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_a->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_a->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_b->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_b->GetLifecycleState()); |
| |
| // Navigate to C(D), now A(B) enters BackForwardCache. |
| { |
| ::testing::NiceMock<MockWebContentsObserver> state_change_observer( |
| web_contents()); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_a, RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kInBackForwardCache)); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_b, RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kInBackForwardCache)); |
| // We don't know |rfh_c| and |rfh_d| yet, so we'll match any frame. |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| ::testing::Not(::testing::AnyOf(rfh_a, rfh_b)), |
| RenderFrameHost::LifecycleState::kPendingCommit, |
| RenderFrameHost::LifecycleState::kActive)) |
| .Times(2); |
| // Deletion of frame D's initial RFH. |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| ::testing::Not(::testing::AnyOf(rfh_a, rfh_b)), |
| RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kPendingDeletion)); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| } |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameHostImpl* rfh_d = rfh_c->child_at(0)->current_frame_host(); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_c->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_d->IsInBackForwardCache()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_a->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache, |
| rfh_a->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_b->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache, |
| rfh_b->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_c->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_c->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_d->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_d->GetLifecycleState()); |
| |
| // Go back to A(B), A(B) is restored and C(D) enters BackForwardCache. |
| { |
| ::testing::NiceMock<MockWebContentsObserver> state_change_observer( |
| web_contents()); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_a, RenderFrameHost::LifecycleState::kInBackForwardCache, |
| RenderFrameHost::LifecycleState::kActive)); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_b, RenderFrameHost::LifecycleState::kInBackForwardCache, |
| RenderFrameHost::LifecycleState::kActive)); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_c, RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kInBackForwardCache)); |
| EXPECT_CALL(state_change_observer, |
| RenderFrameHostStateChanged( |
| rfh_d, RenderFrameHost::LifecycleState::kActive, |
| RenderFrameHost::LifecycleState::kInBackForwardCache)); |
| |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| } |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| EXPECT_FALSE(rfh_b->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_c->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_d->IsInBackForwardCache()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_a->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_a->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kActive, |
| rfh_b->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kActive, |
| rfh_b->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_c->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache, |
| rfh_c->GetLifecycleState()); |
| EXPECT_EQ(RenderFrameHostImpl::LifecycleStateImpl::kInBackForwardCache, |
| rfh_d->lifecycle_state()); |
| EXPECT_EQ(RenderFrameHost::LifecycleState::kInBackForwardCache, |
| rfh_d->GetLifecycleState()); |
| } |
| |
| namespace { |
| |
| class EchoFakeWithFilter final : public mojom::Echo { |
| public: |
| explicit EchoFakeWithFilter(mojo::PendingReceiver<mojom::Echo> receiver, |
| std::unique_ptr<mojo::MessageFilter> filter) |
| : receiver_(this, std::move(receiver)) { |
| receiver_.SetFilter(std::move(filter)); |
| } |
| ~EchoFakeWithFilter() override = default; |
| |
| // mojom::Echo implementation |
| void EchoString(const std::string& input, |
| EchoStringCallback callback) override { |
| std::move(callback).Run(input); |
| } |
| |
| private: |
| mojo::Receiver<mojom::Echo> receiver_; |
| }; |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MessageReceivedOnAssociatedInterfaceWhileCached) { |
| DoNotFailForUnexpectedMessagesWhileCached(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| PageLifecycleStateManagerTestDelegate delegate( |
| rfh_a->render_view_host()->GetPageLifecycleStateManager()); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| delegate.WaitForInBackForwardCacheAck(); |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| mojo::Remote<mojom::Echo> remote; |
| EchoFakeWithFilter echo( |
| remote.BindNewPipeAndPassReceiver(), |
| rfh_a->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_)); |
| |
| base::RunLoop loop; |
| remote->EchoString( |
| "", base::BindLambdaForTesting([&](const std::string&) { loop.Quit(); })); |
| loop.Run(); |
| |
| ExpectBucketCount( |
| "BackForwardCache.UnexpectedRendererToBrowserMessage.InterfaceName", |
| base::HistogramBase::Sample( |
| static_cast<int32_t>(base::HashMetricName(mojom::Echo::Name_))), |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| MessageReceivedOnAssociatedInterfaceWhileCachedForProcessWithNonCachedPages) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("/title2.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| PageLifecycleStateManagerTestDelegate delegate( |
| rfh_a->render_view_host()->GetPageLifecycleStateManager()); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| delegate.WaitForInBackForwardCacheAck(); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| // Make sure both pages are on the same process (they are same site so they |
| // should). |
| ASSERT_EQ(rfh_a->GetProcess(), rfh_b->GetProcess()); |
| |
| mojo::Remote<mojom::Echo> remote; |
| EchoFakeWithFilter echo( |
| remote.BindNewPipeAndPassReceiver(), |
| rfh_a->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_)); |
| |
| remote->EchoString("", base::NullCallback()); |
| // Give the killing a chance to run. (We do not expect a kill but need to |
| // "wait" for it to not happen) |
| base::RunLoop().RunUntilIdle(); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| HighCacheSizeBackForwardCacheBrowserTest, |
| MessageReceivedOnAssociatedInterfaceForProcessWithMultipleCachedPages) { |
| DoNotFailForUnexpectedMessagesWhileCached(); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a_1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a_2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // Get url_a_1 and url_a_2 into the cache. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a_1)); |
| RenderFrameHostImpl* rfh_a_1 = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a_1(rfh_a_1); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_a_2)); |
| RenderFrameHostImpl* rfh_a_2 = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a_2(rfh_a_2); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| ASSERT_FALSE(delete_observer_rfh_a_1.deleted()); |
| ASSERT_FALSE(delete_observer_rfh_a_2.deleted()); |
| EXPECT_TRUE(rfh_a_1->IsInBackForwardCache()); |
| EXPECT_TRUE(rfh_a_2->IsInBackForwardCache()); |
| ASSERT_EQ(rfh_a_1->GetProcess(), rfh_a_2->GetProcess()); |
| |
| mojo::Remote<mojom::Echo> remote; |
| EchoFakeWithFilter echo( |
| remote.BindNewPipeAndPassReceiver(), |
| rfh_a_1->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_)); |
| |
| base::RunLoop loop; |
| remote->EchoString( |
| "", base::BindLambdaForTesting([&](const std::string&) { loop.Quit(); })); |
| loop.Run(); |
| |
| ExpectBucketCount( |
| "BackForwardCache.UnexpectedRendererToBrowserMessage.InterfaceName", |
| base::HistogramBase::Sample( |
| static_cast<int32_t>(base::HashMetricName(mojom::Echo::Name_))), |
| 1); |
| |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MessageReceivedOnAssociatedInterfaceWhileFreezing) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| url::Origin origin_a = url::Origin::Create(url_a); |
| url::Origin origin_b = url::Origin::Create(url_b); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| PageLifecycleStateManagerTestDelegate delegate( |
| rfh_a->render_view_host()->GetPageLifecycleStateManager()); |
| |
| mojo::Remote<mojom::Echo> remote; |
| EchoFakeWithFilter echo( |
| remote.BindNewPipeAndPassReceiver(), |
| rfh_a->CreateMessageFilterForAssociatedReceiver(mojom::Echo::Name_)); |
| |
| delegate.OnStoreInBackForwardCacheSent(base::BindLambdaForTesting( |
| [&]() { remote->EchoString("", base::NullCallback()); })); |
| |
| delegate.OnRestoreFromBackForwardCacheSent(base::BindLambdaForTesting( |
| [&]() { remote->EchoString("", base::NullCallback()); })); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // 3) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Tests that if a page is already ineligible to be saved in the back-forward |
| // cache at navigation time, we shouldn't try to proactively swap |
| // BrowsingInstances. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| ShouldNotSwapBrowsingInstanceWhenPageWillNotBeCached) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_1(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_2(embedded_test_server()->GetURL("/title2.html")); |
| GURL url_3(embedded_test_server()->GetURL("/title3.html")); |
| |
| // 1) Navigate to |url_1| . |
| EXPECT_TRUE(NavigateToURL(shell(), url_1)); |
| RenderFrameHostImpl* rfh_1 = current_frame_host(); |
| scoped_refptr<SiteInstanceImpl> site_instance_1 = |
| static_cast<SiteInstanceImpl*>(rfh_1->GetSiteInstance()); |
| |
| // 2) Navigate to |url_2|. |
| EXPECT_TRUE(NavigateToURL(shell(), url_2)); |
| RenderFrameHostImpl* rfh_2 = current_frame_host(); |
| RenderFrameDeletedObserver rfh_2_deleted_observer(rfh_2); |
| scoped_refptr<SiteInstanceImpl> site_instance_2 = |
| static_cast<SiteInstanceImpl*>(rfh_2->GetSiteInstance()); |
| |
| // |rfh_1| should get into the back-forward cache. |
| EXPECT_TRUE(rfh_1->IsInBackForwardCache()); |
| // Check that title1.html and title2.html are in different BrowsingInstances. |
| EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); |
| |
| // Disable the BackForwardCache for |rfh_2|. |
| DisableBFCacheForRFHForTesting(rfh_2->GetGlobalId()); |
| |
| // 3) Navigate to |url_3|. |
| EXPECT_TRUE(NavigateToURL(shell(), url_3)); |
| RenderFrameHostImpl* rfh_3 = current_frame_host(); |
| scoped_refptr<SiteInstanceImpl> site_instance_3 = |
| static_cast<SiteInstanceImpl*>(rfh_3->GetSiteInstance()); |
| |
| // Check that |url_2| and |url_3| are reusing the same SiteInstance (and |
| // BrowsingInstance). |
| EXPECT_EQ(site_instance_2, site_instance_3); |
| if (rfh_2 != rfh_3) { |
| // If we aren't reusing the RenderFrameHost then |rfh_2| will eventually |
| // get deleted because it's not saved in the back-forward cache. |
| rfh_2_deleted_observer.WaitUntilDeleted(); |
| } |
| } |
| |
| // We should try to reuse process on same-site renderer-initiated navigations. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| RendererInitiatedSameSiteNavigationReusesProcess) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_1(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_2(embedded_test_server()->GetURL("/title2.html")); |
| |
| // Navigate to title1.html. |
| EXPECT_TRUE(NavigateToURL(shell(), url_1)); |
| scoped_refptr<SiteInstanceImpl> site_instance_1 = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| // Navigate to title2.html. The navigation is document/renderer initiated. |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), url_2)); |
| scoped_refptr<SiteInstanceImpl> site_instance_2 = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| |
| // Check that title1.html and title2.html are in different BrowsingInstances |
| // but have the same renderer process. |
| EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); |
| EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess()); |
| } |
| |
| // We should try to reuse process on same-site browser-initiated navigations. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| BrowserInitiatedSameSiteNavigationReusesProcess) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_1(embedded_test_server()->GetURL("/title1.html")); |
| GURL url_2(embedded_test_server()->GetURL("/title2.html")); |
| |
| // 1) Navigate to title1.html. |
| EXPECT_TRUE(NavigateToURL(shell(), url_1)); |
| scoped_refptr<SiteInstanceImpl> site_instance_1 = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| // 2) Navigate to title2.html. The navigation is browser initiated. |
| EXPECT_TRUE(NavigateToURL(shell(), url_2)); |
| scoped_refptr<SiteInstanceImpl> site_instance_2 = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| |
| // Check that title1.html and title2.html are in different BrowsingInstances |
| // but have the same renderer process. |
| EXPECT_FALSE(site_instance_1->IsRelatedSiteInstance(site_instance_2.get())); |
| EXPECT_EQ(site_instance_1->GetProcess(), site_instance_2->GetProcess()); |
| |
| // 3) Do a back navigation to title1.html. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_1); |
| scoped_refptr<SiteInstanceImpl> site_instance_1_history_nav = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| |
| // We will reuse the SiteInstance and renderer process of |site_instance_1|. |
| EXPECT_EQ(site_instance_1_history_nav, site_instance_1); |
| EXPECT_EQ(site_instance_1_history_nav->GetProcess(), |
| site_instance_1->GetProcess()); |
| } |
| |
| // We should not try to reuse process on cross-site navigations. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CrossSiteNavigationDoesNotReuseProcess) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL a1_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL b_url(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| GURL a2_url(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| |
| // Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), a1_url)); |
| scoped_refptr<SiteInstanceImpl> a1_site_instance = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| // Navigate to B. The navigation is browser initiated. |
| EXPECT_TRUE(NavigateToURL(shell(), b_url)); |
| scoped_refptr<SiteInstanceImpl> b_site_instance = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| |
| // Check that A1 and B are in different BrowsingInstances and renderer |
| // processes. |
| EXPECT_FALSE(a1_site_instance->IsRelatedSiteInstance(b_site_instance.get())); |
| EXPECT_NE(a1_site_instance->GetProcess(), b_site_instance->GetProcess()); |
| |
| // Navigate to A2. The navigation is renderer-initiated. |
| EXPECT_TRUE(NavigateToURLFromRenderer(shell(), a2_url)); |
| scoped_refptr<SiteInstanceImpl> a2_site_instance = |
| web_contents()->GetMainFrame()->GetSiteInstance(); |
| |
| // Check that B and A2 are in different BrowsingInstances and renderer |
| // processes. |
| EXPECT_FALSE(b_site_instance->IsRelatedSiteInstance(a2_site_instance.get())); |
| EXPECT_NE(b_site_instance->GetProcess(), a2_site_instance->GetProcess()); |
| } |
| |
| // This observer keeps tracks whether a given RenderViewHost is deleted or not |
| // to avoid accessing it and causing use-after-free condition. |
| class RenderViewHostDeletedObserver : public WebContentsObserver { |
| public: |
| explicit RenderViewHostDeletedObserver(RenderViewHost* rvh) |
| : WebContentsObserver(WebContents::FromRenderViewHost(rvh)), |
| render_view_host_(rvh), |
| deleted_(false) {} |
| |
| void RenderViewDeleted(RenderViewHost* render_view_host) override { |
| if (render_view_host_ == render_view_host) |
| deleted_ = true; |
| } |
| |
| bool deleted() const { return deleted_; } |
| |
| private: |
| raw_ptr<RenderViewHost> render_view_host_; |
| bool deleted_; |
| }; |
| |
| // Tests that RenderViewHost is deleted on eviction along with |
| // RenderProcessHost. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| RenderViewHostDeletedOnEviction) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| NavigationControllerImpl& controller = web_contents()->GetController(); |
| BackForwardCacheImpl& cache = controller.GetBackForwardCache(); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| RenderViewHostDeletedObserver delete_observer_rvh_a( |
| rfh_a->GetRenderViewHost()); |
| |
| RenderProcessHost* process = rfh_a->GetProcess(); |
| RenderProcessHostWatcher destruction_observer( |
| process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| cache.Flush(); |
| |
| // 2) Navigate to B. A should be stored in cache, count of entries should |
| // be 1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(1u, cache.GetEntries().size()); |
| |
| // 3) Initiate eviction of rfh_a from BackForwardCache. Entries should be 0. |
| // RenderViewHost, RenderProcessHost and RenderFrameHost should all be |
| // deleted. |
| EXPECT_TRUE(rfh_a->IsInactiveAndDisallowActivation( |
| DisallowActivationReasonId::kForTesting)); |
| destruction_observer.Wait(); |
| ASSERT_TRUE(delete_observer_rvh_a.deleted()); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| EXPECT_EQ(0u, cache.GetEntries().size()); |
| } |
| |
| // Tests that cross-process sub-frame's RenderViewHost is deleted on root |
| // RenderFrameHost eviction from BackForwardCache along with its |
| // RenderProcessHost. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CrossProcessSubFrameRenderViewHostDeletedOnEviction) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* a1 = current_frame_host(); |
| RenderFrameHostImpl* b1 = a1->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b1(b1); |
| |
| RenderViewHostDeletedObserver delete_observer_rvh_b1(b1->GetRenderViewHost()); |
| |
| RenderProcessHost* process = b1->GetProcess(); |
| RenderProcessHostWatcher destruction_observer( |
| process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| |
| // 2) Navigate to URL B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(a1->IsInBackForwardCache()); |
| |
| // 3) Initiate eviction of rfh a1 from BackForwardCache. RenderViewHost, |
| // RenderProcessHost and RenderFrameHost of sub-frame b1 should all be deleted |
| // on eviction. |
| EXPECT_TRUE(a1->IsInactiveAndDisallowActivation( |
| DisallowActivationReasonId::kForTesting)); |
| destruction_observer.Wait(); |
| ASSERT_TRUE(delete_observer_rvh_b1.deleted()); |
| delete_observer_rfh_b1.WaitUntilDeleted(); |
| } |
| |
| // Tests that same-process sub-frame's RenderViewHost is deleted on root |
| // RenderFrameHost eviction from BackForwardCache along with its |
| // RenderProcessHost. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| SameProcessSubFrameRenderViewHostDeletedOnEviction) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(a)")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* a1 = current_frame_host(); |
| RenderFrameHostImpl* a2 = a1->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a2(a2); |
| |
| RenderViewHostDeletedObserver delete_observer_rvh_a2(a2->GetRenderViewHost()); |
| |
| RenderProcessHost* process = a2->GetProcess(); |
| RenderProcessHostWatcher destruction_observer( |
| process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); |
| |
| // 2) Navigate to URL B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(a1->IsInBackForwardCache()); |
| |
| // 3) Initiate eviction of rfh a1 from BackForwardCache. RenderViewHost, |
| // RenderProcessHost and RenderFrameHost of sub-frame a2 should all be |
| // deleted. |
| EXPECT_TRUE(a1->IsInactiveAndDisallowActivation( |
| DisallowActivationReasonId::kForTesting)); |
| destruction_observer.Wait(); |
| ASSERT_TRUE(delete_observer_rvh_a2.deleted()); |
| delete_observer_rfh_a2.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigationCancelledAfterJsEvictionWasDisabled) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| PageLifecycleStateManagerTestDelegate delegate( |
| rfh_a->render_view_host()->GetPageLifecycleStateManager()); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| |
| delegate.OnDisableJsEvictionSent(base::BindLambdaForTesting([&]() { |
| // Posted because Stop() will destroy the NavigationRequest but |
| // DisableJsEviction will be called from inside the navigation which may |
| // not be a safe place to destruct a NavigationRequest. |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&WebContentsImpl::Stop, |
| base::Unretained(web_contents()))); |
| })); |
| |
| // 3) Do not go back to A (navigation cancelled). |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 4) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kNavigationCancelledWhileRestoring}, |
| {}, {}, {}, {}, FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| SubframeNavigationDoesNotRecordMetrics) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1) Navigate to A(B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2) Navigate from B to C. |
| EXPECT_TRUE(NavigateFrameToURL(rfh_a->child_at(0), url_c)); |
| EXPECT_EQ(url_c, |
| rfh_a->child_at(0)->current_frame_host()->GetLastCommittedURL()); |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| |
| // 4) Go back from C to B. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_TRUE( |
| rfh_a->child_at(0)->current_frame_host()->GetLastCommittedURL().DomainIs( |
| "b.com")); |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| |
| // The reason why the frame is not cached in a subframe navigation is not |
| // recorded. |
| ExpectOutcomeDidNotChange(FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EnsureIsolationInfoForSubresourcesNotEmpty) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| BackForwardCacheImpl& cache = |
| web_contents()->GetController().GetBackForwardCache(); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| cache.Flush(); |
| |
| // 2) Navigate to B. A should be stored in cache, count of entries should |
| // be 1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(1u, cache.GetEntries().size()); |
| |
| // 3) GoBack to A. RenderFrameHost of A should be restored and B should be |
| // stored in cache, count of entries should be 1. IsolationInfoForSubresources |
| // of rfh_a should not be empty. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| EXPECT_EQ(1u, cache.GetEntries().size()); |
| EXPECT_FALSE(rfh_a->GetIsolationInfoForSubresources().IsEmpty()); |
| |
| // 4) GoForward to B. RenderFrameHost of B should be restored and A should be |
| // stored in cache, count of entries should be 1. IsolationInfoForSubresources |
| // of rfh_b should not be empty. |
| ASSERT_TRUE(HistoryGoForward(web_contents())); |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(1u, cache.GetEntries().size()); |
| EXPECT_FALSE(rfh_b->GetIsolationInfoForSubresources().IsEmpty()); |
| } |
| |
| // Regression test for crbug.com/1183313, but for is_overriding_user_agent. |
| // Checks that we won't restore an entry from the BackForwardCache if the |
| // is_overriding_user_agent value used in the entry differs from the one used |
| // in the restoring navigation. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoNotRestoreWhenIsOverridingUserAgentDiffers) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| NavigationControllerImpl& controller = web_contents()->GetController(); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| |
| const std::string user_agent_override = "foo"; |
| |
| // 1) Navigate to A without user agent override. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| params_capturer.Wait(); |
| EXPECT_FALSE(params_capturer.is_overriding_user_agent()); |
| EXPECT_NE(user_agent_override, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| } |
| |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // Enable user agent override for future navigations. |
| UserAgentInjector injector(shell()->web_contents(), user_agent_override); |
| |
| // 2) Navigate to B with user agent override. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| params_capturer.Wait(); |
| EXPECT_TRUE(params_capturer.is_overriding_user_agent()); |
| EXPECT_EQ(user_agent_override, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| } |
| |
| // A should be stored in the back-forward cache. |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| |
| // 3) Go back to A. RenderFrameHost of A should not be restored from the |
| // back-forward cache, and "is_overriding_user_agent" is set to true |
| // correctly. |
| { |
| RenderFrameDeletedObserver delete_observer(rfh_a); |
| FrameNavigateParamsCapturer params_capturer(root); |
| controller.GoBack(); |
| params_capturer.Wait(); |
| delete_observer.WaitUntilDeleted(); |
| EXPECT_TRUE(params_capturer.is_overriding_user_agent()); |
| EXPECT_EQ(user_agent_override, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kUserAgentOverrideDiffers}, |
| {}, {}, {}, {}, FROM_HERE); |
| } |
| |
| // B should be stored in the back-forward cache. |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| |
| // 4) Go forward to B. RenderFrameHost of B should be restored from the |
| // back-forward cache, and "is_overriding_user_agent" is set to true |
| // correctly. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| controller.GoForward(); |
| params_capturer.Wait(); |
| EXPECT_TRUE(params_capturer.is_overriding_user_agent()); |
| EXPECT_EQ(user_agent_override, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Stop overriding user agent from now on. |
| injector.set_is_overriding_user_agent(false); |
| |
| // 5) Go to C, which should not do a user agent override. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| params_capturer.Wait(); |
| EXPECT_FALSE(params_capturer.is_overriding_user_agent()); |
| EXPECT_NE(user_agent_override, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| } |
| |
| // B should be stored in the back-forward cache again. |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| |
| // 6) Go back to B. RenderFrameHost of B should not be restored from the |
| // back-forward cache, and "is_overriding_user_agent" is set to false |
| // correctly. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| RenderFrameDeletedObserver delete_observer(rfh_b); |
| controller.GoBack(); |
| params_capturer.Wait(); |
| delete_observer.WaitUntilDeleted(); |
| EXPECT_FALSE(params_capturer.is_overriding_user_agent()); |
| EXPECT_NE(user_agent_override, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kUserAgentOverrideDiffers}, |
| {}, {}, {}, {}, FROM_HERE); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| RestoreWhenUserAgentOverrideDiffers) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| NavigationControllerImpl& controller = web_contents()->GetController(); |
| FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) |
| ->GetPrimaryFrameTree() |
| .root(); |
| |
| // Enable user agent override for future navigations. |
| const std::string user_agent_override_1 = "foo"; |
| UserAgentInjector injector(shell()->web_contents(), user_agent_override_1); |
| |
| // 1) Start a new navigation to A with user agent override. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| params_capturer.Wait(); |
| EXPECT_TRUE(params_capturer.is_overriding_user_agent()); |
| EXPECT_EQ(user_agent_override_1, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| } |
| |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| |
| // 2) Navigate to another page. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // A should be stored in the back-forward cache. |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // Change the user agent override string. |
| const std::string user_agent_override_2 = "bar"; |
| injector.set_user_agent_override(user_agent_override_2); |
| |
| // 3) Go back to A, which should restore the page saved in the back-forward |
| // cache and use the old user agent. |
| // TODO(https://crbug.com/1194880): This should use the new UA override. |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| controller.GoBack(); |
| params_capturer.Wait(); |
| EXPECT_TRUE(params_capturer.is_overriding_user_agent()); |
| EXPECT_EQ(user_agent_override_1, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // 4) Navigate to another page, which should use the new user agent. Note that |
| // we didn't do this in step 2 instead because the UA override change during |
| // navigation would trigger a RendererPreferences to the active page (page A). |
| { |
| FrameNavigateParamsCapturer params_capturer(root); |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| params_capturer.Wait(); |
| EXPECT_TRUE(params_capturer.is_overriding_user_agent()); |
| EXPECT_EQ(user_agent_override_2, |
| EvalJs(shell()->web_contents(), "navigator.userAgent")); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| WebContentsDestroyedWhileRestoringThePageFromBFCache) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| Shell* shell = CreateBrowser(); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell, url_a)); |
| |
| // 2) Navigate to another page. |
| EXPECT_TRUE(NavigateToURL(shell, url_b)); |
| |
| // 3) Start navigating back. |
| TestNavigationManager nav_manager(shell->web_contents(), url_a); |
| shell->web_contents()->GetController().GoBack(); |
| nav_manager.WaitForFirstYieldAfterDidStartNavigation(); |
| |
| ::testing::NiceMock<MockWebContentsObserver> observer(shell->web_contents()); |
| EXPECT_CALL(observer, DidFinishNavigation(_)) |
| .WillOnce(::testing::Invoke([](NavigationHandle* handle) { |
| EXPECT_FALSE(handle->HasCommitted()); |
| EXPECT_TRUE(handle->IsServedFromBackForwardCache()); |
| // This call checks that |rfh_restored_from_back_forward_cache| is not |
| // deleted and the virtual |GetRoutingID| does not crash. |
| EXPECT_TRUE(NavigationRequest::From(handle) |
| ->rfh_restored_from_back_forward_cache() |
| ->GetRoutingID()); |
| })); |
| |
| shell->Close(); |
| } |
| |
| // Test if the delegate doesn't support BFCache that the reason is |
| // recorded correctly. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DelegateDoesNotSupportBackForwardCache) { |
| // Set the delegate to null to force the default behavior. |
| web_contents()->SetDelegate(nullptr); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| // BackForwardCache is empty. |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| // BackForwardCache contains only rfh_a. |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| ASSERT_TRUE(HistoryGoToOffset(web_contents(), -1)); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kBackForwardCacheDisabledForDelegate}, |
| {}, {}, {}, {}, FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NoThrottlesOnCacheRestore) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| bool did_register_throttles = false; |
| |
| // This will track for each navigation whether we attempted to register |
| // NavigationThrottles. |
| content::ShellContentBrowserClient::Get() |
| ->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting( |
| [&did_register_throttles](content::NavigationHandle* handle) |
| -> std::vector<std::unique_ptr<content::NavigationThrottle>> { |
| did_register_throttles = true; |
| return std::vector<std::unique_ptr<content::NavigationThrottle>>(); |
| })); |
| |
| // 2) Navigate to B. |
| ASSERT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| ASSERT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_TRUE(did_register_throttles); |
| did_register_throttles = false; |
| |
| // 3) Go back to A which is in the BackForward cache and will be restored via |
| // an IsPageActivation navigation. Ensure that we did not register |
| // NavigationThrottles for this navigation since we already ran their checks |
| // when we navigated to A in step 1. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| EXPECT_FALSE(did_register_throttles); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Tests that a back navigation from a crashed page has the process state |
| // tracked correctly by WebContentsImpl. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| BackNavigationFromCrashedPage) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| url::Origin origin_a = url::Origin::Create(url_a); |
| url::Origin origin_b = url::Origin::Create(url_b); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| EXPECT_FALSE(web_contents()->IsCrashed()); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kHidden); |
| EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin()); |
| EXPECT_EQ(origin_b, rfh_b->GetLastCommittedOrigin()); |
| EXPECT_FALSE(rfh_b->IsInBackForwardCache()); |
| EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kVisible); |
| EXPECT_FALSE(web_contents()->IsCrashed()); |
| |
| // 3) Crash B. |
| CrashTab(web_contents()); |
| EXPECT_TRUE(web_contents()->IsCrashed()); |
| EXPECT_TRUE(delete_observer_rfh_b.deleted()); |
| |
| // 4) Go back to A. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible); |
| EXPECT_FALSE(web_contents()->IsCrashed()); |
| |
| ExpectRestored(FROM_HERE); |
| } |
| |
| // Injects a blank subframe into the current document just before processing |
| // DidCommitNavigation for a specified URL. |
| class InjectCreateChildFrame : public DidCommitNavigationInterceptor { |
| public: |
| InjectCreateChildFrame(WebContents* web_contents, const GURL& url) |
| : DidCommitNavigationInterceptor(web_contents), url_(url) {} |
| |
| InjectCreateChildFrame(const InjectCreateChildFrame&) = delete; |
| InjectCreateChildFrame& operator=(const InjectCreateChildFrame&) = delete; |
| |
| bool was_called() { return was_called_; } |
| |
| private: |
| // DidCommitNavigationInterceptor implementation. |
| bool WillProcessDidCommitNavigation( |
| RenderFrameHost* render_frame_host, |
| NavigationRequest* navigation_request, |
| mojom::DidCommitProvisionalLoadParamsPtr*, |
| mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) |
| override { |
| if (!was_called_ && navigation_request && |
| navigation_request->GetURL() == url_) { |
| EXPECT_TRUE(ExecuteScript( |
| web_contents(), |
| "document.body.appendChild(document.createElement('iframe'));")); |
| } |
| was_called_ = true; |
| return true; |
| } |
| |
| bool was_called_ = false; |
| GURL url_; |
| }; |
| |
| // Verify that when A navigates to B, and A creates a subframe just before B |
| // commits, the subframe does not inherit a proxy in B's process from its |
| // parent. Otherwise, if A gets bfcached and later restored, the subframe's |
| // proxy would be (1) in a different BrowsingInstance than the rest of its |
| // page, and (2) preserved after the restore, which would cause crashes when |
| // later using that proxy (for example, when creating more subframes). See |
| // https://crbug.com/1243541. |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| InjectSubframeDuringPendingCrossBrowsingInstanceNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title2.html")); |
| |
| // 1) Navigate to A. |
| ASSERT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImplWrapper rfh_a(current_frame_host()); |
| EXPECT_EQ(0U, rfh_a->child_count()); |
| |
| // 2) Navigate to B, and inject a blank subframe just before it commits. |
| { |
| InjectCreateChildFrame injector(shell()->web_contents(), url_b); |
| ASSERT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(injector.was_called()); |
| } |
| |
| // `rfh_a` should be in BackForwardCache, and it should have a subframe. |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| ASSERT_EQ(1U, rfh_a->child_count()); |
| |
| // The new subframe should not have any proxies at this point. In |
| // particular, it shouldn't inherit a proxy in b.com from its parent. |
| EXPECT_TRUE(rfh_a->child_at(0) |
| ->render_manager() |
| ->GetAllProxyHostsForTesting() |
| .empty()); |
| |
| RenderFrameHostImplWrapper rfh_b(current_frame_host()); |
| |
| // 3) Go back. This should restore `rfh_a` from the cache, and `rfh_b` |
| // should go into the cache. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| EXPECT_EQ(rfh_a.get(), current_frame_host()); |
| EXPECT_TRUE(rfh_b->IsInBackForwardCache()); |
| |
| // 4) Add a grandchild frame to `rfh_a`. This shouldn't crash. |
| RenderFrameHostCreatedObserver frame_observer(shell()->web_contents(), 1); |
| EXPECT_TRUE(ExecuteScript( |
| rfh_a->child_at(0), |
| "document.body.appendChild(document.createElement('iframe'));")); |
| frame_observer.Wait(); |
| EXPECT_EQ(1U, rfh_a->child_at(0)->child_count()); |
| |
| // Make sure the grandchild is live. |
| EXPECT_TRUE(ExecuteScript(rfh_a->child_at(0)->child_at(0), "true")); |
| } |
| |
| class BackgroundForegroundProcessLimitBackForwardCacheBrowserTest |
| : public BackForwardCacheBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| EnableFeatureAndSetParams(features::kBackForwardCache, "cache_size", |
| base::NumberToString(kBackForwardCacheSize)); |
| EnableFeatureAndSetParams( |
| features::kBackForwardCache, "foreground_cache_size", |
| base::NumberToString(kForegroundBackForwardCacheSize)); |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| void ExpectCached(const RenderFrameHostImplWrapper& rfh, |
| bool cached, |
| bool backgrounded) { |
| EXPECT_FALSE(rfh.IsDestroyed()); |
| EXPECT_EQ(cached, rfh->IsInBackForwardCache()); |
| EXPECT_EQ(backgrounded, rfh->GetProcess()->IsProcessBackgrounded()); |
| } |
| // The number of pages the BackForwardCache can hold per tab. |
| const size_t kBackForwardCacheSize = 4; |
| const size_t kForegroundBackForwardCacheSize = 2; |
| }; |
| |
| // Test that a series of same-site navigations (which use the same process) |
| // uses the foreground limit. |
| IN_PROC_BROWSER_TEST_F( |
| BackgroundForegroundProcessLimitBackForwardCacheBrowserTest, |
| CacheEvictionSameSite) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| std::vector<RenderFrameHostImplWrapper> rfhs; |
| |
| for (size_t i = 0; i <= kBackForwardCacheSize * 2; ++i) { |
| SCOPED_TRACE(i); |
| GURL url(embedded_test_server()->GetURL( |
| "a.com", base::StringPrintf("/title1.html?i=%zu", i))); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| rfhs.emplace_back(current_frame_host()); |
| EXPECT_FALSE(rfhs.back()->GetProcess()->IsProcessBackgrounded()); |
| |
| for (size_t j = 0; j <= i; ++j) { |
| SCOPED_TRACE(j); |
| // The last page is active, the previous |kForegroundBackForwardCacheSize| |
| // should be in the cache, any before that should be deleted. |
| if (i - j <= kForegroundBackForwardCacheSize) { |
| // All of the processes should be in the foreground. |
| ExpectCached(rfhs[j], /*cached=*/i != j, |
| /*backgrounded=*/false); |
| } else { |
| ASSERT_TRUE(rfhs[j].WaitUntilRenderFrameDeleted()); |
| } |
| } |
| } |
| |
| // Navigate back but not to the initial about:blank. |
| for (size_t i = 0; i <= kBackForwardCacheSize * 2 - 1; ++i) { |
| SCOPED_TRACE(i); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The first |kBackForwardCacheSize| navigations should be restored from the |
| // cache. The rest should not. |
| if (i < kForegroundBackForwardCacheSize) { |
| ExpectRestored(FROM_HERE); |
| } else { |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kForegroundCacheLimit}, |
| {}, {}, {}, {}, FROM_HERE); |
| } |
| } |
| } |
| |
| // Test that a series of cross-site navigations (which use different processes) |
| // use the background limit. |
| // |
| // TODO(crbug.com/1203418): This test is flaky. It has been reenabled with |
| // improved failure output (https://crrev.com/c/2862346). It's OK to disable it |
| // again when it fails. |
| IN_PROC_BROWSER_TEST_F( |
| BackgroundForegroundProcessLimitBackForwardCacheBrowserTest, |
| CacheEvictionCrossSite) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| std::vector<RenderFrameHostImplWrapper> rfhs; |
| |
| for (size_t i = 0; i <= kBackForwardCacheSize * 2; ++i) { |
| SCOPED_TRACE(i); |
| GURL url(embedded_test_server()->GetURL(base::StringPrintf("a%zu.com", i), |
| "/title1.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| rfhs.emplace_back(current_frame_host()); |
| EXPECT_FALSE(rfhs.back()->GetProcess()->IsProcessBackgrounded()); |
| |
| for (size_t j = 0; j <= i; ++j) { |
| SCOPED_TRACE(j); |
| // The last page is active, the previous |kBackgroundBackForwardCacheSize| |
| // should be in the cache, any before that should be deleted. |
| if (i - j <= kBackForwardCacheSize) { |
| EXPECT_FALSE(rfhs[j].IsDestroyed()); |
| // Pages except the active one should be cached and in the background. |
| ExpectCached(rfhs[j], /*cached=*/i != j, |
| /*backgrounded=*/i != j); |
| } else { |
| ASSERT_TRUE(rfhs[j].WaitUntilRenderFrameDeleted()); |
| } |
| } |
| } |
| |
| // Navigate back but not to the initial about:blank. |
| for (size_t i = 0; i <= kBackForwardCacheSize * 2 - 1; ++i) { |
| SCOPED_TRACE(i); |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| // The first |kBackForwardCacheSize| navigations should be restored from the |
| // cache. The rest should not. |
| if (i < kBackForwardCacheSize) { |
| ExpectRestored(FROM_HERE); |
| } else { |
| ExpectNotRestored( |
| {BackForwardCacheMetrics::NotRestoredReason::kCacheLimit}, {}, {}, {}, |
| {}, FROM_HERE); |
| } |
| } |
| } |
| |
| // Test that the cache responds to processes switching from background to |
| // foreground. We set things up so that we have |
| // Cached sites: |
| // a0.com |
| // a1.com |
| // a2.com |
| // a3.com |
| // and the active page is a4.com. Then set the process for a[1-3] to |
| // foregrounded so that there are 3 entries whose processes are foregrounded. |
| // BFCache should evict the eldest (a1) leaving a0 because despite being older, |
| // it is backgrounded. Setting the priority directly is not ideal but there is |
| // no reliable way to cause the processes to go into the foreground just by |
| // navigating because proactive browsing instance swap makes it impossible to |
| // reliably create a new a1.com renderer in the same process as the old a1.com. |
| IN_PROC_BROWSER_TEST_F( |
| BackgroundForegroundProcessLimitBackForwardCacheBrowserTest, |
| ChangeToForeground) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| std::vector<RenderFrameHostImplWrapper> rfhs; |
| |
| // Navigate through a[0-3].com. |
| for (size_t i = 0; i < kBackForwardCacheSize; ++i) { |
| SCOPED_TRACE(i); |
| GURL url(embedded_test_server()->GetURL(base::StringPrintf("a%zu.com", i), |
| "/title1.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| rfhs.emplace_back(current_frame_host()); |
| EXPECT_FALSE(rfhs.back()->GetProcess()->IsProcessBackgrounded()); |
| } |
| // Check that a0-2 are cached and backgrounded. |
| for (size_t i = 0; i < kBackForwardCacheSize - 1; ++i) { |
| SCOPED_TRACE(i); |
| ExpectCached(rfhs[i], /*cached=*/true, /*backgrounded=*/true); |
| } |
| |
| // Navigate to a page which causes the processes for a[1-3] to be |
| // foregrounded. |
| GURL url(embedded_test_server()->GetURL("a4.com", "/title1.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), url)); |
| |
| // Assert that we really have set up the situation we want where the processes |
| // are shared and in the foreground. |
| RenderFrameHostImpl* rfh = current_frame_host(); |
| ASSERT_FALSE(rfh->GetProcess()->IsProcessBackgrounded()); |
| |
| rfhs[1]->GetProcess()->SetPriorityOverride( |
| /*foreground=*/true); |
| rfhs[2]->GetProcess()->SetPriorityOverride( |
| /*foreground=*/true); |
| rfhs[3]->GetProcess()->SetPriorityOverride( |
| /*foreground=*/true); |
| |
| // The page should be evicted. |
| ASSERT_TRUE(rfhs[1].WaitUntilRenderFrameDeleted()); |
| |
| // Check that a0 is cached and backgrounded. |
| ExpectCached(rfhs[0], /*cached=*/true, /*backgrounded=*/true); |
| // Check that a2-3 are cached and foregrounded. |
| ExpectCached(rfhs[2], /*cached=*/true, /*backgrounded=*/false); |
| ExpectCached(rfhs[3], /*cached=*/true, /*backgrounded=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackForwardCacheForScreenReader) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| BackForwardCacheDisabledTester tester; |
| |
| // Use Screen Reader. |
| content::testing::ScopedContentAXModeSetter ax_mode_setter( |
| ui::AXMode::kScreenReader); |
| |
| // Navigate to Page A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostWrapper rfh_a(current_frame_host()); |
| RenderFrameDeletedObserver deleted(rfh_a.get()); |
| int process_id = current_frame_host()->GetProcess()->GetID(); |
| int routing_id = current_frame_host()->GetRoutingID(); |
| |
| // Navigate away to Page B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| deleted.WaitUntilDeleted(); |
| |
| // Navigate back. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| auto reason = BackForwardCacheDisable::DisabledReason( |
| BackForwardCacheDisable::DisabledReasonId::kScreenReader); |
| ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason:: |
| kDisableForRenderFrameHostCalled}, |
| {}, {}, {reason}, {}, FROM_HERE); |
| EXPECT_TRUE( |
| tester.IsDisabledForFrameWithReason(process_id, routing_id, reason)); |
| } |
| |
| } // namespace content |