| // 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/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 "base/types/expected.h" |
| #include "build/build_config.h" |
| #include "content/browser/accessibility/browser_accessibility.h" |
| #include "content/browser/back_forward_cache_browsertest.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/scoped_accessibility_mode_override.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/dns/mock_host_resolver.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/40800266): 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); |
| nav_manager.WaitForSpeculativeRenderFrameHostCreation(); |
| |
| 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(crbug.com/41477477): 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(crbug.com/40702446): 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(crbug.com/40758687): 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); |
| } |
| |
| // Tests what metrics are recorded when the BackForwardCache feature is |
| // disabled. |
| class BackForwardCacheDisabledBrowserTest : public ContentBrowserTest { |
| protected: |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| } |
| |
| void SetUp() override { |
| feature_list_.InitAndDisableFeature(features::kBackForwardCache); |
| ContentBrowserTest::SetUp(); |
| } |
| |
| void ExpectHistoryNavigationOutcomeCount(std::string histogram_suffix, |
| int all_sites_count, |
| int feature_enabled_count) { |
| histogram_tester_.ExpectTotalCount( |
| "BackForwardCache.AllSites.HistoryNavigationOutcome" + histogram_suffix, |
| all_sites_count); |
| histogram_tester_.ExpectTotalCount( |
| "BackForwardCache.HistoryNavigationOutcome" + histogram_suffix, |
| feature_enabled_count); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledBrowserTest, |
| MetricsOnHistoryNavigation) { |
| GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.test", "/title2.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImplWrapper rfh_a( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| if (!rfh_a.IsRenderFrameDeleted()) { |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| } |
| |
| // 3) Confirm the metrics recorded when going back to A, which should not be |
| // restored from BFCache. |
| ASSERT_TRUE(HistoryGoBack(shell()->web_contents())); |
| |
| ExpectHistoryNavigationOutcomeCount("", /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".NotRestoredReason", |
| /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BlocklistedFeature", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".DisabledForRenderFrameHostReason2", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".DisallowActivationReason", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BrowsingInstanceNotSwappedReason", |
| |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledBrowserTest, |
| MetricsOnHistoryNavigation_SameSite) { |
| GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.test", "/title2.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImplWrapper rfh_a( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| |
| // 2) Navigate to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| if (!rfh_a.IsRenderFrameDeleted()) { |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| } |
| |
| // 3) Confirm the metrics recorded when going back to A, which should not be |
| // restored from BFCache. |
| ASSERT_TRUE(HistoryGoBack(shell()->web_contents())); |
| |
| ExpectHistoryNavigationOutcomeCount("", /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".NotRestoredReason", |
| /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BlocklistedFeature", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".DisabledForRenderFrameHostReason2", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BrowsingInstanceNotSwappedReason", |
| /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledBrowserTest, |
| MetricsOnHistoryNavigation_BlocklistedFeature) { |
| GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.test", "/title2.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImplWrapper rfh_a( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| rfh_a->UseDummyStickyBackForwardCacheDisablingFeatureForTesting(); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| if (!rfh_a.IsRenderFrameDeleted()) { |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| } |
| |
| // 3) Confirm the metrics recorded when going back to A, which should not be |
| // restored from BFCache. |
| ASSERT_TRUE(HistoryGoBack(shell()->web_contents())); |
| |
| ExpectHistoryNavigationOutcomeCount("", /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| // The page is not BFCached because the feature is enabled and the blocklisted |
| // feature. |
| ExpectHistoryNavigationOutcomeCount(".NotRestoredReason", |
| /*all_sites_count=*/2, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BlocklistedFeature", |
| /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".DisabledForRenderFrameHostReason2", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BrowsingInstanceNotSwappedReason", |
| |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheDisabledBrowserTest, |
| MetricsOnHistoryNavigation_DisabledForRFH) { |
| GURL url_a(embedded_test_server()->GetURL("a.test", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.test", "/title2.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImplWrapper rfh_a( |
| shell()->web_contents()->GetPrimaryMainFrame()); |
| rfh_a->DisableBackForwardCache(RenderFrameHostDisabledForTestingReason()); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| if (!rfh_a.IsRenderFrameDeleted()) { |
| EXPECT_FALSE(rfh_a->IsInBackForwardCache()); |
| } |
| |
| // 3) Confirm the metrics recorded when going back to A, which should not be |
| // restored from BFCache. |
| ASSERT_TRUE(HistoryGoBack(shell()->web_contents())); |
| |
| ExpectHistoryNavigationOutcomeCount("", /*all_sites_count=*/1, |
| /*feature_enabled_count=*/0); |
| // The page is not BFCached because the feature is enabled and the RFH |
| // disabling call. |
| ExpectHistoryNavigationOutcomeCount(".NotRestoredReason", |
| /*all_sites_count=*/2, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BlocklistedFeature", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| // Note: The "all sites" version doesn't exist for the |
| // `DisabledForRenderFrameHostReason2` histogram, so we expect it to be 0 too. |
| ExpectHistoryNavigationOutcomeCount(".DisabledForRenderFrameHostReason2", |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| ExpectHistoryNavigationOutcomeCount(".BrowsingInstanceNotSwappedReason", |
| |
| /*all_sites_count=*/0, |
| /*feature_enabled_count=*/0); |
| } |
| |
| 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. |
| ScopedAccessibilityModeOverride scoped_accessibility_mode( |
| shell()->web_contents(), ui::kAXModeComplete); |
| |
| // 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. |
| ScopedAccessibilityModeOverride scoped_accessibility_mode( |
| shell()->web_contents(), ui::kAXModeComplete); |
| |
| // 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([](BrowserAccessibilityManager* manager, |
| ui::AXEventGenerator::Event event, |
| ui::AXNodeID event_target_id) { FAIL(); })); |
| // Generate an event. |
| ui::AXUpdatesAndEvents updates_and_events; |
| 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 BackForwardCacheBrowserTestWithFlagForAXLocationChange |
| : public BackForwardCacheBrowserTest, |
| public ::testing::WithParamInterface<bool> { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| EnableFeatureAndSetParams(features::kEnableBackForwardCacheForScreenReader, |
| "", "true"); |
| if (ShouldEvictOnAXLocationChange()) { |
| DisableFeature(features::kDoNotEvictOnAXLocationChange); |
| } else { |
| EnableFeatureAndSetParams(features::kDoNotEvictOnAXLocationChange, "", |
| ""); |
| } |
| BackForwardCacheBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| bool ShouldEvictOnAXLocationChange() { return GetParam(); } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| BackForwardCacheBrowserTestWithFlagForAXLocationChange, |
| ::testing::Bool()); |
| |
| IN_PROC_BROWSER_TEST_P(BackForwardCacheBrowserTestWithFlagForAXLocationChange, |
| EvictOnAXLocationChangeOrNot) { |
| 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. |
| ScopedAccessibilityModeOverride scoped_accessibility_mode( |
| shell()->web_contents(), ui::kAXModeComplete); |
| |
| // 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()); |
| ASSERT_TRUE(rfh_a.get()); |
| EXPECT_TRUE(rfh_a->IsInBackForwardCache()); |
| |
| // 3) Set the callback for location change. |
| BrowserAccessibilityManager* manager = |
| rfh_a->GetOrCreateBrowserAccessibilityManager(); |
| // This callback will count the number of times location change happens. |
| // Note that this callback runs even when the page is in back/forward cache. |
| int location_change_counter_for_testing = 0; |
| manager->SetLocationChangeCallbackForTesting(base::BindRepeating( |
| [](int* location_change_counter_for_testing) { |
| // Increment the location change count. |
| *location_change_counter_for_testing += 1; |
| }, |
| &location_change_counter_for_testing)); |
| |
| // Generate a location change event. |
| std::vector<blink::mojom::LocationChangesPtr> changes_1; |
| ui::AXRelativeBounds relative_bounds_1; |
| relative_bounds_1.bounds = |
| gfx::RectF(/*x=*/1, /*y=*/2, /*width=*/3, /*height=*/4); |
| changes_1.push_back(blink::mojom::LocationChanges::New(0, relative_bounds_1)); |
| rfh_a->HandleAXLocationChanges(rfh_a->GetAXTreeID(), std::move(changes_1), |
| /*reset_token=*/1); |
| |
| // Generate another location change event. |
| std::vector<blink::mojom::LocationChangesPtr> changes_2; |
| ui::AXRelativeBounds relative_bounds_2; |
| relative_bounds_2.bounds = |
| gfx::RectF(/*x=*/2, /*y=*/3, /*width=*/4, /*height=*/5); |
| changes_2.push_back(blink::mojom::LocationChanges::New(0, relative_bounds_2)); |
| rfh_a->HandleAXLocationChanges(rfh_a->GetAXTreeID(), std::move(changes_2), |
| /*reset_token=*/1); |
| |
| // 4) Navigate back. |
| ASSERT_TRUE(HistoryGoBack(web_contents())); |
| |
| if (ShouldEvictOnAXLocationChange()) { |
| const uint64_t reason = DisallowActivationReasonId::kAXLocationChange; |
| ExpectNotRestored({NotRestoredReason::kIgnoreEventAndEvict}, {}, {}, {}, |
| {reason}, FROM_HERE); |
| EXPECT_EQ(0, location_change_counter_for_testing); |
| } 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); |
| |
| // Location change should have happened twice. |
| EXPECT_EQ(2, location_change_counter_for_testing); |
| |
| 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()->GetPriority() == |
| base::Process::Priority::kBestEffort); |
| } |
| // 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_NE(rfhs.back()->GetProcess()->GetPriority(), |
| base::Process::Priority::kBestEffort); |
| |
| 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/40179515): 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_NE(rfhs.back()->GetProcess()->GetPriority(), |
| base::Process::Priority::kBestEffort); |
| |
| 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_NE(rfhs.back()->GetProcess()->GetPriority(), |
| base::Process::Priority::kBestEffort); |
| } |
| // 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_NE(rfh->GetProcess()->GetPriority(), |
| base::Process::Priority::kBestEffort); |
| |
| rfhs[1]->GetProcess()->OnMediaStreamAdded(); |
| rfhs[2]->GetProcess()->OnMediaStreamAdded(); |
| rfhs[3]->GetProcess()->OnMediaStreamAdded(); |
| |
| // 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::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::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(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 |