| // Copyright 2021 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "base/types/expected.h" | 
 | #include "content/browser/back_forward_cache_browsertest.h" | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/functional/bind.h" | 
 | #include "base/memory/raw_ptr.h" | 
 | #include "base/metrics/metrics_hashes.h" | 
 | #include "base/task/single_thread_task_runner.h" | 
 | #include "base/test/bind.h" | 
 | #include "base/test/test_mock_time_task_runner.h" | 
 | #include "build/build_config.h" | 
 | #include "content/browser/accessibility/browser_accessibility.h" | 
 | #include "content/browser/renderer_host/back_forward_cache_disable.h" | 
 | #include "content/browser/renderer_host/back_forward_cache_impl.h" | 
 | #include "content/browser/renderer_host/navigation_request.h" | 
 | #include "content/browser/renderer_host/render_frame_host_impl.h" | 
 | #include "content/browser/web_contents/web_contents_impl.h" | 
 | #include "content/common/content_navigation_policy.h" | 
 | #include "content/common/features.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_features.h" | 
 | #include "content/public/common/content_switches.h" | 
 | #include "content/public/test/accessibility_notification_waiter.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/fenced_frame_test_util.h" | 
 | #include "content/public/test/mock_web_contents_observer.h" | 
 | #include "content/public/test/test_navigation_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/content_browser_test_utils_internal.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" | 
 | #include "third_party/blink/public/common/features.h" | 
 | #include "ui/accessibility/ax_action_data.h" | 
 | #include "ui/accessibility/ax_node_id_forward.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 { | 
 |  | 
 | using NotRestoredReason = BackForwardCacheMetrics::NotRestoredReason; | 
 |  | 
 | // 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()->ForEachRenderFrameHostWithAction( | 
 |         [&](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) { | 
 |   // Sometimes messages arrive from a renderer to browser for the page in | 
 |   // back/forward cache (message on content.mojom.FrameHost), because the input | 
 |   // task queue is currently not frozen. Do not fail for unexpected messages. | 
 |   DoNotFailForUnexpectedMessagesWhileCached(); | 
 |   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", "/title1.html")); | 
 |   GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); | 
 |  | 
 |   // 1. Navigate to a cacheable page (A). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2. Navigate from a cacheable page to an uncacheable page (A->B). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   // Page A should be in the cache. | 
 |   EXPECT_FALSE(rfh_a.IsDestroyed()); | 
 |   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); | 
 |  | 
 |   // Evict page B and wait until it is deleted. | 
 |   rfh_b->DisableBackForwardCache(RenderFrameHostDisabledForTestingReason()); | 
 |   ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // 4. Navigate from a cacheable page to a cacheable page (A->C). | 
 |   ASSERT_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(rfh_a.IsDestroyed()); | 
 |   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); | 
 | } | 
 |  | 
 | // Disabled due to flakiness on Linux and Mac https://crbug.com/1287467 | 
 | // Disabled on Chrome OS due to flakiness https://crbug.com/1290834 | 
 | #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS) | 
 | #define MAYBE_ProxiesAreStoredAndRestored DISABLED_ProxiesAreStoredAndRestored | 
 | #else | 
 | #define MAYBE_ProxiesAreStoredAndRestored ProxiesAreStoredAndRestored | 
 | #endif | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        MAYBE_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", "/title1.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). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   EXPECT_EQ(2u, render_frame_host_manager() | 
 |                     ->current_frame_host() | 
 |                     ->browsing_context_state() | 
 |                     ->GetProxyCount()); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   std::string frame_tree_a = DepictFrameTree(rfh_a->frame_tree_node()); | 
 |  | 
 |   // 2. Navigate from a cacheable page to an uncacheable page (A->B). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_EQ(0u, render_frame_host_manager() | 
 |                     ->current_frame_host() | 
 |                     ->browsing_context_state() | 
 |                     ->GetProxyCount()); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   // Page A should be in the cache. | 
 |   EXPECT_FALSE(rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Verify proxies are stored as well. | 
 |   auto cached_entry = cache.GetOrEvictEntry(rfh_a->nav_entry_id()); | 
 |   EXPECT_TRUE(cached_entry.has_value()); | 
 |   EXPECT_EQ(2u, cached_entry.value()->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() | 
 |                     ->current_frame_host() | 
 |                     ->browsing_context_state() | 
 |                     ->GetProxyCount()); | 
 |  | 
 |   // Evict page B and wait until it is deleted. | 
 |   rfh_b->DisableBackForwardCache(RenderFrameHostDisabledForTestingReason()); | 
 |   ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted()); | 
 |   EXPECT_EQ(2u, render_frame_host_manager() | 
 |                     ->current_frame_host() | 
 |                     ->browsing_context_state() | 
 |                     ->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). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_c)); | 
 |   EXPECT_EQ(3u, render_frame_host_manager() | 
 |                     ->current_frame_host() | 
 |                     ->browsing_context_state() | 
 |                     ->GetProxyCount()); | 
 |   RenderFrameHostImplWrapper rfh_c(current_frame_host()); | 
 |  | 
 |   // Page A should be in the cache. | 
 |   EXPECT_FALSE(rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // Verify proxies are stored as well. | 
 |   cached_entry = cache.GetOrEvictEntry(rfh_a->nav_entry_id()); | 
 |   EXPECT_TRUE(cached_entry.has_value()); | 
 |   EXPECT_EQ(2u, cached_entry.value()->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() | 
 |                     ->current_frame_host() | 
 |                     ->browsing_context_state() | 
 |                     ->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(rfh_c.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh_c->IsInBackForwardCache()); | 
 |  | 
 |   // Verify proxies are stored as well. | 
 |   cached_entry = cache.GetOrEvictEntry(rfh_c->nav_entry_id()); | 
 |   EXPECT_TRUE(cached_entry.has_value()); | 
 |   EXPECT_EQ(3u, cached_entry.value()->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 mojo 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(); | 
 |  | 
 |   // 4) Check the histogram. | 
 |   base::HistogramBase::Sample sample = base::HistogramBase::Sample( | 
 |       base::TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName( | 
 |           "blink.mojom.LocalFrame")); | 
 |  | 
 |   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_REQUIRES_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({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({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({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | // Navigate from A(B)->C. Send postMessage from A to B upon pagehide, and | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, PostMessageDelivered) { | 
 |   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). | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   RenderFrameHostImplWrapper rfh_b(rfh_a->child_at(0)->current_frame_host()); | 
 |   // Register message handler for b.com. | 
 |   ASSERT_TRUE(ExecJs(rfh_b.get(), R"( | 
 |       localStorage.setItem('postMessage_dispatched', 'not_dispatched'); | 
 |       window.addEventListener('message', (event) => { | 
 |           console.log(`Received message: ${event.data}`); | 
 |           localStorage.setItem('postMessage_dispatched', 'dispatched'); | 
 |       }); | 
 |   )")); | 
 |   // Register pagehide handler for a.com. Inside pagehide handler, send a | 
 |   // postMessage to b.com. | 
 |   ASSERT_TRUE(ExecJs(rfh_a.get(), R"( | 
 |       window.addEventListener("pagehide", (event) => { | 
 |         document.getElementById('child-0') | 
 |           .contentWindow.postMessage('foo', '*'); | 
 |       }, false); | 
 |       )")); | 
 |  | 
 |   // 2) Navigate to C. This will invoke pagehide handler and postMessage. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_c)); | 
 |   // Onmessage event should be queued and not triggered in back/forward cache. | 
 |   // Thus JavaScript execution does not happen and the page does not get | 
 |   // evicted. | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 4) Go back to A(B). Make sure that JavaSc | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   EXPECT_EQ("dispatched", | 
 |             GetLocalStorage(rfh_b.get(), "postMessage_dispatched")); | 
 | } | 
 |  | 
 | // 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( | 
 |       {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()); | 
 | } | 
 |  | 
 | class BackForwardCacheEntryTimeoutBrowserTest | 
 |     : public BackForwardCacheBrowserTest { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |     feature_list_.InitAndEnableFeature(features::kBackForwardCacheEntryTimeout); | 
 |   } | 
 |  | 
 |  private: | 
 |   base::test::ScopedFeatureList feature_list_; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheEntryTimeoutBrowserTest, BusyPagehide) { | 
 |   ASSERT_TRUE(embedded_test_server()->Start()); | 
 |   GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(embedded_test_server()->GetURL("b.test", "/title1.html")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh(current_frame_host()); | 
 |   ASSERT_TRUE(ExecJs(rfh.get(), R"( | 
 |       addEventListener("pagehide", () => {while(1){}}); | 
 |   )")); | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   ASSERT_TRUE(rfh.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kTimeoutPuttingInCache}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheEntryTimeoutBrowserTest, | 
 |                        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)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |  | 
 |   ExecuteScriptAsync(rfh_a.get(), R"( | 
 |     let i = 0; | 
 |     while (true) { i++; } | 
 |   )"); | 
 |  | 
 |   RenderProcessHost* process = rfh_a.get()->GetProcess(); | 
 |   RenderProcessHostWatcher destruction_observer( | 
 |       process, RenderProcessHostWatcher::WATCH_FOR_HOST_DESTRUCTION); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   // rfh_a should be destroyed (not kept in the cache). | 
 |   destruction_observer.Wait(); | 
 |   EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // rfh_b should still be the current frame. | 
 |   EXPECT_EQ(current_frame_host(), rfh_b.get()); | 
 |  | 
 |   // 3) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kTimeoutPuttingInCache}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 |  | 
 |   // Make sure that the tree reasons match the flattened reasons. | 
 |   EXPECT_THAT( | 
 |       GetTreeResult()->GetDocumentResult(), | 
 |       MatchesDocumentResult( | 
 |           NotRestoredReasons({NotRestoredReason::kTimeoutPuttingInCache}), | 
 |           BlockListedFeatures())); | 
 | } | 
 |  | 
 | // 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. | 
 |   // The BFCache entry will be evicted before the back navigation completes, so | 
 |   // the old navigation will be reset and a new navigation will be restarted. | 
 |   // This observer is waiting for the two navigation requests to complete. | 
 |   TestNavigationObserver observer(web_contents(), | 
 |                                   /* expected_number_of_navigations= */ 2, | 
 |                                   MessageLoopRunner::QuitMode::IMMEDIATE, | 
 |                                   /* ignore_uncommitted_navigations= */ false); | 
 |   web_contents()->GetController().GoBack(); | 
 |   EXPECT_TRUE(web_contents()->IsLoading()); | 
 |   EvictByJavaScript(rfh_a); | 
 |   EXPECT_FALSE(web_contents()->IsLoading()); | 
 |  | 
 |   // rfh_a should have been deleted, and page A navigated to normally. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |   observer.Wait(); | 
 |   RenderFrameHostImpl* rfh_a2 = current_frame_host(); | 
 |   EXPECT_NE(rfh_a2, rfh_b); | 
 |   EXPECT_EQ(rfh_a2->GetLastCommittedURL(), url_a); | 
 |  | 
 |   ExpectNotRestored({NotRestoredReason::kJavaScriptExecution, | 
 |                      NotRestoredReason::kNavigationCancelledWhileRestoring}, | 
 |                     {}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Test that the reissued BFCache navigation (see | 
 | // `ReissuesNavigationIfEvictedDuringNavigation_BeforeResponse` above) is | 
 | // cancelled when there is another navigation request initiated to the same | 
 | // `FrameTreeNode` before the restarting task is executed. | 
 | IN_PROC_BROWSER_TEST_F( | 
 |     BackForwardCacheBrowserTest, | 
 |     ReissuedBackForwardCacheNavigationIsCancelledWhenNewNavigationIsCreated) { | 
 |   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")); | 
 |   GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.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 a back navigation to page A, and cause the document to be evicted | 
 |   // during the navigation immediately before the navigation makes any | 
 |   // meaningful progress. | 
 |  | 
 |   // The BFCache entry will be evicted before the original back navigation to | 
 |   // page A completes, so the navigation will be reset and a new non-BFCache | 
 |   // navigation to page A will be restarted. Before the restarted navigation | 
 |   // task is executed, a new navigation to page C will be manually initiated, | 
 |   // which cancels the restarting task. | 
 |  | 
 |   // Uses `TestActivationManager` to ensure that the BFCache entry is evicted | 
 |   // during the back navigation. | 
 |   TestActivationManager activation_manager(web_contents(), url_a); | 
 |   web_contents()->GetController().GoBack(); | 
 |   ASSERT_TRUE(activation_manager.WaitForBeforeChecks()); | 
 |   EvictByJavaScript(rfh_a); | 
 |   activation_manager.WaitForNavigationFinished(); | 
 |   ASSERT_FALSE(activation_manager.was_committed()); | 
 |   ASSERT_FALSE(activation_manager.was_activated()); | 
 |   // `rfh_a` should have been deleted. | 
 |   delete_observer_rfh_a.WaitUntilDeleted(); | 
 |  | 
 |   // 4) Initiate another navigation, so the restarting task will be cancelled. | 
 |   // This `observer` is for navigation to page C. | 
 |   TestNavigationObserver observer(web_contents()); | 
 |   web_contents()->GetController().LoadURLWithParams( | 
 |       NavigationController::LoadURLParams(url_c)); | 
 |   observer.WaitForNavigationFinished(); | 
 |   // Now the destination of the navigation is `url_c` after two navigation | 
 |   // requests complete. | 
 |   EXPECT_EQ(current_frame_host()->GetLastCommittedURL(), url_c); | 
 | } | 
 |  | 
 | // 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 before activation | 
 |   // checks finish (i.e. before disabling JS eviction in the renderer). | 
 |   // The BFCache entry will be evicted before the back navigation completes, | 
 |   // so the old navigation will be reset and a new navigation will be | 
 |   // restarted. This observer is waiting for the two navigation requests to | 
 |   // complete. | 
 |   TestNavigationObserver observer(web_contents(), | 
 |                                   /* expected_number_of_navigations= */ 2, | 
 |                                   MessageLoopRunner::QuitMode::IMMEDIATE, | 
 |                                   /* ignore_uncommitted_navigations= */ false); | 
 |   { | 
 |     // In a scope to make sure the activation_manager is deleted before the | 
 |     // reissued navigation begins. | 
 |     TestActivationManager activation_manager(shell()->web_contents(), url_a); | 
 |  | 
 |     web_contents()->GetController().GoBack(); | 
 |  | 
 |     // Wait for the activating navigation to start. | 
 |     EXPECT_TRUE(activation_manager.WaitForBeforeChecks()); | 
 |  | 
 |     // 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. Ensure the | 
 |     // initial activating navigation isn't committed. | 
 |     activation_manager.WaitForNavigationFinished(); | 
 |     EXPECT_FALSE(activation_manager.was_committed()); | 
 |   } | 
 |  | 
 |   // rfh_a should have been deleted, and page A navigated to normally. | 
 |   delete_observer_rfh_a1.WaitUntilDeleted(); | 
 |   observer.Wait(); | 
 |   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({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(); | 
 | } | 
 |  | 
 | // 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 SingleThreadTaskRunner::GetCurrentDefault 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( | 
 |           BackForwardCacheImpl::kNotInCCNSContext); | 
 |   // 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)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   rfh_a->GetBackForwardCacheMetrics()->SetObserverForTesting(this); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper 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(rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 5) Fast forward to when eviction is due. | 
 |   task_runner->FastForwardBy(delta); | 
 |  | 
 |   // 6) Confirm A is evicted. | 
 |   EXPECT_EQ(current_frame_host(), rfh_b.get()); | 
 |  | 
 |   // 7) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kTimeout}, {}, {}, {}, {}, FROM_HERE); | 
 |   // Make sure that the tree reasons match the flattened reasons. | 
 |   EXPECT_THAT( | 
 |       GetTreeResult()->GetDocumentResult(), | 
 |       MatchesDocumentResult(NotRestoredReasons({NotRestoredReason::kTimeout}), | 
 |                             BlockListedFeatures())); | 
 | } | 
 |  | 
 | 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({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({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({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({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")); | 
 |   GURL url_inner(embedded_test_server()->GetURL("a.com", "/title2.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. | 
 |   auto* inner_contents = CreateAndAttachInnerContents(child); | 
 |   EXPECT_TRUE(NavigateToURL(inner_contents, url_inner)); | 
 |   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({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({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 BUILDFLAG(IS_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)); | 
 |   ASSERT_TRUE(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)); | 
 |   ASSERT_TRUE(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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->GetSiteInstance(); | 
 |   // Navigate to B. The navigation is browser initiated. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), b_url)); | 
 |   scoped_refptr<SiteInstanceImpl> b_site_instance = | 
 |       web_contents()->GetPrimaryMainFrame()->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()->GetPrimaryMainFrame()->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, AcrossTasksDanglingUntriaged> 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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | 
 |         FROM_HERE, base::BindOnce(&WebContentsImpl::Stop, | 
 |                                   base::Unretained(web_contents()))); | 
 |   })); | 
 |  | 
 |   // 3) Do not go back to A (navigation cancelled). | 
 |   web_contents()->GetController().GoBack(); | 
 |   ASSERT_TRUE(WaitForLoadStop(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({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({NotRestoredReason::kNavigationCancelledWhileRestoring, | 
 |                        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({NotRestoredReason::kNavigationCancelledWhileRestoring, | 
 |                        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. | 
 |   TestActivationManager activation_manager(shell->web_contents(), url_a); | 
 |   shell->web_contents()->GetController().GoBack(); | 
 |   EXPECT_TRUE(activation_manager.WaitForBeforeChecks()); | 
 |  | 
 |   ::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) | 
 |                         ->GetRenderFrameHostRestoredFromBackForwardCache() | 
 |                         ->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({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); | 
 | } | 
 |  | 
 | // Test that when two back navigations are created to the same history entry one | 
 | // after another without waiting for the first one to commit, the second one | 
 | // should be committed as a normal back navigation without restoring the BFCache | 
 | // entry. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        TwoBackNavigationsToTheSameEntry) { | 
 |   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 cacheable page A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2) Navigate away. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b); | 
 |  | 
 |   // Page A should be in BFCache. | 
 |   EXPECT_FALSE(rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Navigate back to A, but before the `CommitDeferringCondition` check | 
 |   // happens, start another navigation back to the same entry A. | 
 |   TestActivationManager activation_manager(web_contents(), url_a); | 
 |   web_contents()->GetController().GoBack(); | 
 |   ASSERT_TRUE(activation_manager.WaitForAfterChecks()); | 
 |   ASSERT_TRUE(HistoryGoToIndex(web_contents(), 0)); | 
 |   EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a); | 
 |  | 
 |   // 4) Page A should not be restored from BFCache because the first navigation | 
 |   // is cancelled and the second navigation should be a non-BFCache navigation. | 
 |   ExpectNotRestored({NotRestoredReason::kNavigationCancelledWhileRestoring}, {}, | 
 |                     {}, {}, {}, 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(ExecJs( | 
 |           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); | 
 |  | 
 |     TestNavigationObserver navigation_observer(shell()->web_contents(), 1); | 
 |     shell()->LoadURL(url_b); | 
 |     navigation_observer.Wait(); | 
 |     // We cannot use NavigateToURL which will automatically wait for particular | 
 |     // url in the navigation above because running a nested message loop in the | 
 |     // injector confuses TestNavigationObserver by changing the order of | 
 |     // notifications. | 
 |     EXPECT_EQ(url_b, shell()->web_contents()->GetLastCommittedURL()); | 
 |  | 
 |     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( | 
 |       ExecJs(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(ExecJs(rfh_a->child_at(0)->child_at(0), "true")); | 
 | } | 
 |  | 
 | class BackForwardCacheBrowserTestWithFlagForScreenReader | 
 |     : public BackForwardCacheBrowserTest, | 
 |       public ::testing::WithParamInterface<bool> { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     if (IsBackForwardCacheEnabledForScreenReader()) { | 
 |       EnableFeatureAndSetParams( | 
 |           features::kEnableBackForwardCacheForScreenReader, "", "true"); | 
 |     } else { | 
 |       DisableFeature(features::kEnableBackForwardCacheForScreenReader); | 
 |     } | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 |  | 
 |   bool IsBackForwardCacheEnabledForScreenReader() { return GetParam(); } | 
 | }; | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          BackForwardCacheBrowserTestWithFlagForScreenReader, | 
 |                          ::testing::Bool()); | 
 |  | 
 | IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForScreenReader, | 
 |                        ScreenReaderOn) { | 
 |   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. | 
 |   EnableAccessibilityForWebContents(shell()->web_contents()); | 
 |  | 
 |   // Navigate to Page A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   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)); | 
 |   if (IsBackForwardCacheEnabledForScreenReader()) { | 
 |     EXPECT_TRUE(rfh_a.get()); | 
 |     EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |     // Navigate back. | 
 |     ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |     ExpectRestored(FROM_HERE); | 
 |   } else { | 
 |     EXPECT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |     // Navigate back. | 
 |     ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |     auto reason = BackForwardCacheDisable::DisabledReason( | 
 |         BackForwardCacheDisable::DisabledReasonId::kScreenReader); | 
 |     ExpectNotRestored({NotRestoredReason::kDisableForRenderFrameHostCalled}, {}, | 
 |                       {}, {reason}, {}, FROM_HERE); | 
 |     EXPECT_TRUE( | 
 |         tester.IsDisabledForFrameWithReason(process_id, routing_id, reason)); | 
 |   } | 
 | } | 
 |  | 
 | class BackForwardCacheBrowserTestWithFlagForAXEvents | 
 |     : public BackForwardCacheBrowserTest, | 
 |       public ::testing::WithParamInterface<bool> { | 
 |  protected: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     EnableFeatureAndSetParams(features::kEnableBackForwardCacheForScreenReader, | 
 |                               "", "true"); | 
 |     if (ShouldEvictOnAXEvents()) { | 
 |       EnableFeatureAndSetParams(features::kEvictOnAXEvents, "", "true"); | 
 |     } else { | 
 |       DisableFeature(features::kEvictOnAXEvents); | 
 |     } | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |   } | 
 |  | 
 |   bool ShouldEvictOnAXEvents() { return GetParam(); } | 
 | }; | 
 |  | 
 | INSTANTIATE_TEST_SUITE_P(All, | 
 |                          BackForwardCacheBrowserTestWithFlagForAXEvents, | 
 |                          ::testing::Bool()); | 
 |  | 
 | // Verify that the page will be evicted upon accessibility events if the | 
 | // flag to evict on ax events is off, and evicted otherwise. | 
 | IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForAXEvents, | 
 |                        EvictOnAccessibilityEventsOrNot) { | 
 |   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()->web_contents(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   // Use Screen Reader. | 
 |   EnableAccessibilityForWebContents(shell()->web_contents()); | 
 |  | 
 |   // Wait until we receive the kLoadComplete AX event. This means that the | 
 |   // kLoadStart event has definitely already passed and any kLoadStart we see | 
 |   // from this frame in the future is newly generated. | 
 |   AccessibilityNotificationWaiter waiter_complete( | 
 |       shell()->web_contents(), ui::kAXModeComplete, | 
 |       ax::mojom::Event::kLoadComplete); | 
 |   ASSERT_TRUE(waiter_complete.WaitForNotification()); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell()->web_contents(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   EXPECT_TRUE(rfh_a.get()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 3) Set the callback for generated events, and expect that this is never | 
 |   // fired. | 
 |   BrowserAccessibilityManager* manager = | 
 |       rfh_a->GetOrCreateBrowserAccessibilityManager(); | 
 |   manager->SetGeneratedEventCallbackForTesting( | 
 |       base::BindRepeating([](RenderFrameHostImpl* render_frame_host, | 
 |                              ui::AXEventGenerator::Event event, | 
 |                              ui::AXNodeID event_target_id) { FAIL(); })); | 
 |   // Generate an event. | 
 |   blink::mojom::AXUpdatesAndEventsPtr updates_and_events = | 
 |       blink::mojom::AXUpdatesAndEvents::New(); | 
 |   ui::AXTreeUpdate update; | 
 |   update.root_id = 1; | 
 |   updates_and_events->updates.emplace_back(update); | 
 |   updates_and_events->events.emplace_back( | 
 |       /*id=*/0, ax::mojom::Event::kChildrenChanged); | 
 |   // If any events are generated and fired, they will be fired synchronously | 
 |   // in the same task of |HandleAXEventsForTests()| and and result in a test | 
 |   // fail. | 
 |   rfh_a->HandleAXEventsForTests(rfh_a->GetAXTreeID(), | 
 |                                 std::move(updates_and_events)); | 
 |  | 
 |   // Reset the callback before restoring the page so that we will not fail when | 
 |   // events are generated. | 
 |   manager->SetGeneratedEventCallbackForTesting( | 
 |       GeneratedEventCallbackForTesting()); | 
 |  | 
 |   // 4) Navigate back. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   if (ShouldEvictOnAXEvents()) { | 
 |     const uint64_t reason = DisallowActivationReasonId::kAXEvent; | 
 |     ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, | 
 |                       {reason}, FROM_HERE); | 
 |   } else { | 
 |     AccessibilityNotificationWaiter waiter_start(shell()->web_contents(), | 
 |                                                  ui::kAXModeComplete, | 
 |                                                  ax::mojom::Event::kLoadStart); | 
 |     // Ensure that |rfh_a| is successfully restored from bfcache and that we see | 
 |     // LOAD_START event. | 
 |     EXPECT_EQ(current_frame_host(), rfh_a.get()); | 
 |     ExpectRestored(FROM_HERE); | 
 |  | 
 |     ASSERT_TRUE(waiter_start.WaitForNotification()); | 
 |     auto* waiter_start_rfhi = static_cast<RenderFrameHostImpl*>( | 
 |         waiter_start.event_browser_accessibility_manager()->delegate()); | 
 |     EXPECT_EQ(waiter_start_rfhi, rfh_a.get()); | 
 |   } | 
 | } | 
 |  | 
 | 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({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); | 
 |     // Note: do NOT use .com domains here because a4.com is on the HSTS preload | 
 |     // list, which will cause our test requests to timeout. | 
 |     GURL url(embedded_test_server()->GetURL(base::StringPrintf("a%zu.test", 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({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.test | 
 | //   a1.test | 
 | //   a2.test | 
 | //   a3.test | 
 | // and the active page is a4.test. 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.test renderer in the same process as the old | 
 | // a1.test. | 
 | // | 
 | // Note that we do NOT use .com domains because a4.com is on the HSTS preload | 
 | // list.  Since our test server doesn't use HTTPS, using a4.com results in the | 
 | // test timing out. | 
 | 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.test", 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.test", "/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); | 
 | } | 
 |  | 
 | // Test that the BackForwardCacheTimeToLiveControl feature works and takes | 
 | // precedence over the default value | 
 | // `kDefaultTimeToLiveInBackForwardCacheInSeconds`. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, TestTimeToLiveParameter) { | 
 |   // 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 SingleThreadTaskRunner::GetCurrentDefault 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( | 
 |           BackForwardCacheImpl::kNotInCCNSContext); | 
 |   // This should match the value set via EnableFeatureAndSetParams by | 
 |   // parent test class `BackForwardCacheBrowserTest`. | 
 |   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)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper 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(rfh_a.IsDestroyed()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 5) Fast forward to when eviction is due. | 
 |   task_runner->FastForwardBy(delta); | 
 |  | 
 |   // 6) Confirm A is evicted. | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |   EXPECT_EQ(current_frame_host(), rfh_b.get()); | 
 |  | 
 |   // 7) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kTimeout}, {}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Test that when we navigate away from an error page and back with no error | 
 | // that we don't serve the error page from BFCache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        ErrorDocumentNotCachedWithSecondError) { | 
 |   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")); | 
 |  | 
 |   // Navigate to a.com. | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), url_a)); | 
 |  | 
 |   // Navigate to b.com and block due to an error. | 
 |   NavigateAndBlock(url_b, /*history_offset=*/0); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   // Navigate back to a.com. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted()); | 
 |  | 
 |   // Navigate forward to b.com again and block with an error again. | 
 |   NavigateAndBlock(url_b, /*history_offset=*/1); | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead, | 
 |        NotRestoredReason::kErrorDocument}, | 
 |       {}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // Test that when we navigate away from an error page and back with no error | 
 | // that we don't serve the error page from BFCache. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, | 
 |                        ErrorDocumentNotCachedWithoutSecondError) { | 
 |   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")); | 
 |  | 
 |   // Navigate to a.com. | 
 |   ASSERT_TRUE(NavigateToURL(web_contents(), url_a)); | 
 |  | 
 |   // Navigate to b.com and block due to an error. | 
 |   NavigateAndBlock(url_b, /*history_offset=*/0); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |  | 
 |   int history_entry_id = | 
 |       web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID(); | 
 |  | 
 |   // Navigate back to a.com. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ASSERT_TRUE(rfh_b.WaitUntilRenderFrameDeleted()); | 
 |   ExpectRestored(FROM_HERE); | 
 |  | 
 |   // Navigate forward to b.com again with no error. | 
 |   ASSERT_TRUE(HistoryGoForward(web_contents())); | 
 |  | 
 |   // Check that we indeed got a new history entry. | 
 |   ASSERT_NE( | 
 |       history_entry_id, | 
 |       web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID()); | 
 |   // The reasons from the old entry should be copied to the new entry. | 
 |   ExpectNotRestored( | 
 |       {NotRestoredReason::kHTTPStatusNotOK, NotRestoredReason::kNoResponseHead, | 
 |        NotRestoredReason::kErrorDocument}, | 
 |       {}, {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | class BackForwardCacheBrowserTestWithFencedFrames | 
 |     : public BackForwardCacheBrowserTest { | 
 |  public: | 
 |   BackForwardCacheBrowserTestWithFencedFrames() = default; | 
 |   ~BackForwardCacheBrowserTestWithFencedFrames() override = default; | 
 |  | 
 |   test::FencedFrameTestHelper& fenced_frame_test_helper() { | 
 |     return *fenced_frame_test_helper_; | 
 |   } | 
 |  | 
 |  private: | 
 |   void SetUpCommandLine(base::CommandLine* command_line) override { | 
 |     EnableFeatureAndSetParams(blink::features::kFencedFrames, "", ""); | 
 |     EnableFeatureAndSetParams(features::kPrivacySandboxAdsAPIsOverride, "", ""); | 
 |     BackForwardCacheBrowserTest::SetUpCommandLine(command_line); | 
 |  | 
 |     fenced_frame_test_helper_ = std::make_unique<test::FencedFrameTestHelper>(); | 
 |   } | 
 |  | 
 |   std::unique_ptr<test::FencedFrameTestHelper> fenced_frame_test_helper_; | 
 | }; | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithFencedFrames, | 
 |                        CachesFencedFramesSimple) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |   GURL url_a(https_server()->GetURL("a.test", "/fenced_frames/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2) Create fenced frame and wait for it to load. | 
 |   const GURL fenced_frame_url = | 
 |       https_server()->GetURL("c.test", "/fenced_frames/title1.html"); | 
 |   RenderFrameHostImplWrapper fenced_frame( | 
 |       fenced_frame_test_helper().CreateFencedFrame( | 
 |           web_contents()->GetPrimaryMainFrame(), fenced_frame_url)); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(fenced_frame.get())); | 
 |  | 
 |   // 3) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(fenced_frame->IsInBackForwardCache()); | 
 |  | 
 |   // 4) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectRestored(FROM_HERE); | 
 |   EXPECT_FALSE(fenced_frame->IsInBackForwardCache()); | 
 | } | 
 |  | 
 | // Test that the back/forward cache can store documents containing a fenced | 
 | // frame in their contents. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithFencedFrames, | 
 |                        InnerFrameStorageSupport) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |   GURL url_a(https_server()->GetURL( | 
 |       "a.test", "/fenced_frames/basic_fenced_frame_src.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1. Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |   EXPECT_FALSE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 2. Retrieve the rfh for the fenced frame | 
 |   EXPECT_EQ(1u, rfh_a->frame_tree_node()->child_count()); | 
 |   RenderFrameHostImplWrapper first_delegate_frame( | 
 |       rfh_a->frame_tree_node()->child_at(0)->current_frame_host()); | 
 |   RenderFrameHostImplWrapper first_fenced_frame( | 
 |       FrameTreeNode::GloballyFindByID( | 
 |           first_delegate_frame->inner_tree_main_frame_tree_node_id()) | 
 |           ->current_frame_host()); | 
 |   EXPECT_FALSE(first_delegate_frame->IsInBackForwardCache()); | 
 |   ASSERT_TRUE(first_fenced_frame); | 
 |   EXPECT_FALSE(first_fenced_frame->IsInBackForwardCache()); | 
 |  | 
 |   // 3. Add a second fenced frame. | 
 |   GURL title_fenced_frame( | 
 |       https_server()->GetURL("a.test", "/fenced_frames/title1.html")); | 
 |   RenderFrameHostImplWrapper second_fenced_frame( | 
 |       fenced_frame_test_helper().CreateFencedFrame(rfh_a.get(), | 
 |                                                    title_fenced_frame)); | 
 |   ASSERT_TRUE(second_fenced_frame); | 
 |   EXPECT_EQ(2u, rfh_a->frame_tree_node()->child_count()); | 
 |   RenderFrameHostImplWrapper second_delegate_frame( | 
 |       rfh_a->frame_tree_node()->child_at(1)->current_frame_host()); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(second_fenced_frame.get())); | 
 |  | 
 |   // 4. Add a nested fenced frame. | 
 |   RenderFrameHostImplWrapper nested_fenced_frame( | 
 |       fenced_frame_test_helper().CreateFencedFrame(second_fenced_frame.get(), | 
 |                                                    title_fenced_frame)); | 
 |   ASSERT_TRUE(nested_fenced_frame); | 
 |   EXPECT_EQ(1u, second_fenced_frame->frame_tree_node()->child_count()); | 
 |   RenderFrameHostImplWrapper nested_delegate_frame( | 
 |       second_fenced_frame->frame_tree_node() | 
 |           ->child_at(0) | 
 |           ->current_frame_host()); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(nested_fenced_frame.get())); | 
 |  | 
 |   StartRecordingEvents(first_fenced_frame.get()); | 
 |   StartRecordingEvents(second_fenced_frame.get()); | 
 |   StartRecordingEvents(nested_fenced_frame.get()); | 
 |  | 
 |   // 5. Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   EXPECT_FALSE(rfh_b->IsInBackForwardCache()); | 
 |  | 
 |   // 6. Confirm A and its inner frames are in BackForwardCache. | 
 |   ASSERT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(first_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(first_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(second_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(second_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(nested_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(nested_fenced_frame->IsInBackForwardCache()); | 
 |  | 
 |   // 7. Navigate back restoring A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(current_frame_host(), rfh_a.get()); | 
 |   EXPECT_FALSE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(first_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(first_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(second_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(second_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(nested_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(nested_fenced_frame->IsInBackForwardCache()); | 
 |  | 
 |   // visibilitychange events are added twice per each because it is fired for | 
 |   // both window and document. | 
 |   base::Value matching_events = | 
 |       ListValueOf("window.pagehide.persisted", "document.visibilitychange", | 
 |                   "window.visibilitychange", "document.freeze", | 
 |                   "document.resume", "document.visibilitychange", | 
 |                   "window.visibilitychange", "window.pageshow.persisted"); | 
 |  | 
 |   MatchEventList(first_fenced_frame.get(), matching_events.Clone()); | 
 |   MatchEventList(second_fenced_frame.get(), matching_events.Clone()); | 
 |   MatchEventList(nested_fenced_frame.get(), matching_events.Clone()); | 
 |  | 
 |   // 8. Navigate forward to B, storing A again in BackForwardCache. | 
 |   ASSERT_TRUE(HistoryGoForward(web_contents())); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(first_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(first_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(second_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(second_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(nested_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_TRUE(nested_fenced_frame->IsInBackForwardCache()); | 
 |  | 
 |   // 9. Navigate back restoring A one more time. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   EXPECT_EQ(current_frame_host(), rfh_a.get()); | 
 |   EXPECT_FALSE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(first_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(first_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(second_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(second_fenced_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(nested_delegate_frame->IsInBackForwardCache()); | 
 |   EXPECT_FALSE(nested_fenced_frame->IsInBackForwardCache()); | 
 | } | 
 |  | 
 | // Test that documents are evicted correctly through the outermost main frame. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithFencedFrames, | 
 |                        OuterDocumentTimeEviction) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |   // 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 SingleThreadTaskRunner::GetCurrentDefault 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( | 
 |           BackForwardCacheImpl::kNotInCCNSContext); | 
 |   // 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); | 
 |  | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1. Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2. Add a fenced frame to initial page A. | 
 |   GURL fenced_frame_url( | 
 |       https_server()->GetURL("a.test", "/fenced_frames/empty.html")); | 
 |   RenderFrameHostImplWrapper fenced_frame_rfh( | 
 |       fenced_frame_test_helper().CreateFencedFrame(rfh_a.get(), | 
 |                                                    fenced_frame_url)); | 
 |   ASSERT_TRUE(fenced_frame_rfh); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(fenced_frame_rfh.get())); | 
 |  | 
 |   // 3. Navigate to B. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   RenderFrameHostImplWrapper rfh_b(current_frame_host()); | 
 |   EXPECT_FALSE(rfh_b->IsBackForwardCacheEvictionTimeRunningForTesting()); | 
 |  | 
 |   // 4. Fast forward to just before eviction is due. | 
 |   task_runner->FastForwardBy(time_to_live_in_back_forward_cache - delta); | 
 |  | 
 |   // 5. Confirm A is still in BackForwardCache. | 
 |   ASSERT_TRUE(rfh_a); | 
 |   EXPECT_TRUE(rfh_a->IsBackForwardCacheEvictionTimeRunningForTesting()); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |   EXPECT_FALSE( | 
 |       fenced_frame_rfh->IsBackForwardCacheEvictionTimeRunningForTesting()); | 
 |   EXPECT_TRUE(fenced_frame_rfh->IsInBackForwardCache()); | 
 |  | 
 |   // 6. Fast forward to when eviction is due. | 
 |   task_runner->FastForwardBy(delta); | 
 |  | 
 |   // 7. Confirm A is evicted. | 
 |   ASSERT_TRUE(fenced_frame_rfh.WaitUntilRenderFrameDeleted()); | 
 |   ASSERT_TRUE(rfh_a.WaitUntilRenderFrameDeleted()); | 
 |   EXPECT_EQ(current_frame_host(), rfh_b.get()); | 
 |  | 
 |   // 8. Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({BackForwardCacheMetrics::NotRestoredReason::kTimeout}, {}, | 
 |                     {}, {}, {}, FROM_HERE); | 
 | } | 
 |  | 
 | // This test checks that the TreeResults generated are correct. | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithFencedFrames, | 
 |                        TreeResults) { | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |   GURL url_a(https_server()->GetURL("a.test", "/title1.html")); | 
 |  | 
 |   // 1. Navigate to A. | 
 |   ASSERT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2. Add fenced frames. | 
 |   GURL fenced_frame_url_a( | 
 |       https_server()->GetURL("a.test", "/fenced_frames/title1.html?value=a")); | 
 |   GURL fenced_frame_url_b( | 
 |       https_server()->GetURL("b.test", "/fenced_frames/title1.html?value=b")); | 
 |   GURL fenced_frame_url_c( | 
 |       https_server()->GetURL("c.test", "/fenced_frames/title1.html?value=c")); | 
 |   RenderFrameHostImplWrapper fenced_frame_a( | 
 |       fenced_frame_test_helper().CreateFencedFrame(rfh_a.get(), | 
 |                                                    fenced_frame_url_a)); | 
 |   RenderFrameHostImplWrapper fenced_frame_b( | 
 |       fenced_frame_test_helper().CreateFencedFrame(rfh_a.get(), | 
 |                                                    fenced_frame_url_b)); | 
 |   RenderFrameHostImplWrapper fenced_frame_c( | 
 |       fenced_frame_test_helper().CreateFencedFrame(fenced_frame_b.get(), | 
 |                                                    fenced_frame_url_c)); | 
 |   EXPECT_TRUE(fenced_frame_a); | 
 |   EXPECT_TRUE(fenced_frame_b); | 
 |   EXPECT_TRUE(fenced_frame_c); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(fenced_frame_a.get())); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(fenced_frame_b.get())); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(fenced_frame_c.get())); | 
 |   fenced_frame_c->UseDummyStickyBackForwardCacheDisablingFeatureForTesting(); | 
 |  | 
 |   // 3. Generate a tree. | 
 |   BackForwardCacheCanStoreDocumentResultWithTree can_store_result = | 
 |       web_contents() | 
 |           ->GetController() | 
 |           .GetBackForwardCache() | 
 |           .GetCurrentBackForwardCacheEligibility(rfh_a.get()); | 
 |   ASSERT_TRUE(can_store_result.tree_reasons); | 
 |  | 
 |   // 4. Check that tree results refers only to the fenced frames. We should | 
 |   // not see the delegate frames in this list. | 
 |   EXPECT_EQ(url_a, can_store_result.tree_reasons->GetUrl()); | 
 |   EXPECT_EQ(2u, can_store_result.tree_reasons->GetChildren().size()); | 
 |   EXPECT_THAT( | 
 |       can_store_result.tree_reasons->GetDocumentResult(), | 
 |       MatchesDocumentResult(NotRestoredReasons(), BlockListedFeatures())); | 
 |  | 
 |   // 5. Ensure that each fenced frame is correct. Any frames inside a fenced | 
 |   // frame should be always considered cross origin. | 
 |   auto& child_a_results = can_store_result.tree_reasons->GetChildren().at(0); | 
 |   EXPECT_EQ(fenced_frame_url_a, child_a_results->GetUrl()); | 
 |   EXPECT_FALSE(child_a_results->IsSameOrigin()); | 
 |   EXPECT_EQ(0u, child_a_results->GetChildren().size()); | 
 |  | 
 |   auto& child_b_results = can_store_result.tree_reasons->GetChildren().at(1); | 
 |   EXPECT_EQ(fenced_frame_url_b, child_b_results->GetUrl()); | 
 |   EXPECT_FALSE(child_b_results->IsSameOrigin()); | 
 |   EXPECT_EQ(1u, child_b_results->GetChildren().size()); | 
 |  | 
 |   auto& child_c_results = child_b_results->GetChildren().at(0); | 
 |   EXPECT_EQ(fenced_frame_url_c, child_c_results->GetUrl()); | 
 |   EXPECT_FALSE(child_c_results->IsSameOrigin()); | 
 |  | 
 |   // 6. Check the blocked reasons are set correctly on the fenced frame. | 
 |   EXPECT_THAT(child_c_results->GetDocumentResult(), | 
 |               MatchesDocumentResult( | 
 |                   NotRestoredReasons({NotRestoredReason::kBlocklistedFeatures}), | 
 |                   BlockListedFeatures( | 
 |                       {blink::scheduler::WebSchedulerTrackedFeature::kDummy}))); | 
 |  | 
 |   // 7. Ensure that the web exposed reasons do not replicate any of | 
 |   // fenced frame results. | 
 |   blink::mojom::BackForwardCacheNotRestoredReasonsPtr web_reasons = | 
 |       can_store_result.tree_reasons->GetWebExposedNotRestoredReasons(); | 
 |   EXPECT_TRUE(web_reasons->same_origin_details); | 
 |   EXPECT_EQ(web_reasons->blocked, blink::mojom::BFCacheBlocked::kNo); | 
 |   EXPECT_EQ(2u, web_reasons->same_origin_details->children.size()); | 
 |   EXPECT_FALSE( | 
 |       web_reasons->same_origin_details->children.at(0)->same_origin_details); | 
 |   EXPECT_FALSE( | 
 |       web_reasons->same_origin_details->children.at(1)->same_origin_details); | 
 | } | 
 |  | 
 | IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithFencedFrames, | 
 |                        EvictionOnInnerFrameTree) { | 
 |   DoNotFailForUnexpectedMessagesWhileCached(); | 
 |   CreateHttpsServer(); | 
 |   ASSERT_TRUE(https_server()->Start()); | 
 |   GURL url_a(https_server()->GetURL("a.test", "/fenced_frames/title1.html")); | 
 |   GURL url_b(https_server()->GetURL("b.test", "/title1.html")); | 
 |  | 
 |   // 1) Navigate to A. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_a)); | 
 |   RenderFrameHostImplWrapper rfh_a(current_frame_host()); | 
 |  | 
 |   // 2) Create fenced frame and wait for it to load. | 
 |   const GURL fenced_frame_url = | 
 |       https_server()->GetURL("c.test", "/fenced_frames/title1.html"); | 
 |   RenderFrameHostImpl* fenced_frame = static_cast<RenderFrameHostImpl*>( | 
 |       fenced_frame_test_helper().CreateFencedFrame( | 
 |           web_contents()->GetPrimaryMainFrame(), fenced_frame_url)); | 
 |   EXPECT_TRUE(WaitForDOMContentLoaded(fenced_frame)); | 
 |   RenderFrameDeletedObserver delete_observer_fenced_frame(fenced_frame); | 
 |  | 
 |   // 3) Navigate to B. | 
 |   EXPECT_TRUE(NavigateToURL(shell(), url_b)); | 
 |   EXPECT_TRUE(rfh_a->IsInBackForwardCache()); | 
 |  | 
 |   // 4) Execute JS inside inner fenced frame. | 
 |   EvictByJavaScript(fenced_frame); | 
 |  | 
 |   // FencedFrame is evicted from the BackForwardCache: | 
 |   delete_observer_fenced_frame.WaitUntilDeleted(); | 
 |  | 
 |   // 4) Go back to A. | 
 |   ASSERT_TRUE(HistoryGoBack(web_contents())); | 
 |   ExpectNotRestored({NotRestoredReason::kJavaScriptExecution}, {}, {}, {}, {}, | 
 |                     FROM_HERE); | 
 | } | 
 |  | 
 | }  // namespace content |