| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/command_line.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "content/browser/frame_host/back_forward_cache.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/controllable_http_response.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h" |
| |
| using testing::Each; |
| using testing::Not; |
| |
| namespace content { |
| |
| namespace { |
| |
| // Test about the BackForwardCache. |
| class BackForwardCacheBrowserTest : public ContentBrowserTest { |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| feature_list_.InitAndEnableFeature(features::kBackForwardCache); |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| } |
| |
| WebContentsImpl* web_contents() const { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| RenderFrameHostImpl* current_frame_host() { |
| return web_contents()->GetFrameTree()->root()->current_frame_host(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Match RenderFrameHostImpl* that are in the BackForwardCache. |
| MATCHER(InBackForwardCache, "") { |
| return arg->is_in_back_forward_cache(); |
| } |
| |
| // Match RenderFrameDeleteObserver* which observed deletion of the RenderFrame. |
| MATCHER(Deleted, "") { |
| return arg->deleted(); |
| } |
| |
| // Helper function to pass an initializer list to the EXPECT_THAT macro. This is |
| // indeed the identity function. |
| std::initializer_list<RenderFrameHostImpl*> Elements( |
| std::initializer_list<RenderFrameHostImpl*> t) { |
| return t; |
| } |
| |
| } // namespace |
| |
| // Navigate from A to B and go back. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Basic) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const 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_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kHidden); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kVisible); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_EQ(rfh_a->GetVisibilityState(), PageVisibilityState::kVisible); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| EXPECT_EQ(rfh_b->GetVisibilityState(), PageVisibilityState::kHidden); |
| } |
| |
| // Navigate from A to B and go back. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicDocumentInitiated) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const 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_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(ExecJs(shell(), JsReplace("location = $1;", url_b.spec()))); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // The two pages are using different BrowsingInstances. |
| EXPECT_FALSE(rfh_a->GetSiteInstance()->IsRelatedSiteInstance( |
| rfh_b->GetSiteInstance())); |
| |
| // 3) Go back to A. |
| EXPECT_TRUE(ExecJs(shell(), "history.back();")); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| } |
| |
| // Navigate from back and forward repeatedly. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigateBackForwardRepeatedly) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const 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_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| |
| // 4) Go forward to B. |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // 5) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| |
| // 6) Go forward to B. |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| } |
| |
| // The current page can't enter the BackForwardCache if another page can script |
| // it. This can happen when one document opens a popup using window.open() for |
| // instance. It prevents the BackForwardCache from being used. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, WindowOpen) { |
| // This test assumes cross-site navigation staying in the same |
| // BrowsingInstance to use a different SiteInstance. Otherwise, it will |
| // timeout at step 2). |
| if (!SiteIsolationPolicy::UseDedicatedProcessesForAllSites()) |
| return; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A and open a popup. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a(rfh_a); |
| EXPECT_EQ(1u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount()); |
| Shell* popup = OpenPopup(rfh_a, url_a, ""); |
| EXPECT_EQ(2u, rfh_a->GetSiteInstance()->GetRelatedActiveContentsCount()); |
| |
| // 2) Navigate to B. The previous document can't enter the BackForwardCache, |
| // because of the popup. |
| EXPECT_TRUE(ExecJs(rfh_a, JsReplace("location = $1;", url_b.spec()))); |
| delete_rfh_a.WaitUntilDeleted(); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| EXPECT_EQ(2u, rfh_b->GetSiteInstance()->GetRelatedActiveContentsCount()); |
| |
| // 3) Go back to A. The previous document can't enter the BackForwardCache, |
| // because of the popup. |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| EXPECT_TRUE(ExecJs(rfh_b, "history.back();")); |
| delete_rfh_b.WaitUntilDeleted(); |
| |
| // 4) Make the popup drop the window.opener connection. It happens when the |
| // user does an omnibox-initiated navigation, which happens in a new |
| // BrowsingInstance. |
| RenderFrameHostImpl* rfh_a_new = current_frame_host(); |
| EXPECT_EQ(2u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount()); |
| EXPECT_TRUE(NavigateToURL(popup, url_b)); |
| EXPECT_EQ(1u, rfh_a_new->GetSiteInstance()->GetRelatedActiveContentsCount()); |
| |
| // 5) Navigate to B again. In theory, the current document should be able to |
| // enter the BackForwardCache. It can't, because the RenderFrameHost still |
| // "remembers" it had access to the popup. See |
| // RenderFrameHostImpl::scheduler_tracked_features(). |
| RenderFrameDeletedObserver delete_rfh_a_new(rfh_a_new); |
| EXPECT_TRUE(ExecJs(rfh_a_new, JsReplace("location = $1;", url_b.spec()))); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| delete_rfh_a_new.WaitUntilDeleted(); |
| |
| // 6) Go back to A. The current document can finally enter the |
| // BackForwardCache, because it is alone in its BrowsingInstance and has never |
| // been related to any other document. |
| RenderFrameHostImpl* rfh_b_new = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b_new(rfh_b_new); |
| EXPECT_TRUE(ExecJs(rfh_b_new, "history.back();")); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| EXPECT_FALSE(delete_rfh_b_new.deleted()); |
| EXPECT_TRUE(rfh_b_new->is_in_back_forward_cache()); |
| } |
| |
| // Navigate from A(B) to C and go back. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BasicIframe) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| const 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_rfh_a(rfh_a); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| |
| // 2) Navigate to C. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_c(rfh_c); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_c->is_in_back_forward_cache()); |
| |
| // 3) Go back to A(B). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| EXPECT_FALSE(delete_rfh_c.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_c->is_in_back_forward_cache()); |
| } |
| |
| // Ensure flushing the BackForwardCache works properly. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BackForwardCacheFlush) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const 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_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| |
| // 3) Flush A. |
| web_contents()->GetController().back_forward_cache().Flush(); |
| EXPECT_TRUE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| |
| // 4) Go back to a new A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| |
| // 5) Flush B. |
| web_contents()->GetController().back_forward_cache().Flush(); |
| EXPECT_TRUE(delete_rfh_b.deleted()); |
| } |
| |
| // Check the visible URL in the omnibox is properly updated when restoring a |
| // document from the BackForwardCache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VisibleURL) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Go to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| |
| // 2) Go to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_EQ(url_a, web_contents()->GetVisibleURL()); |
| |
| // 4) Go forward to B. |
| web_contents()->GetController().GoForward(); |
| EXPECT_EQ(url_b, web_contents()->GetVisibleURL()); |
| } |
| |
| // Test documents are evicted from the BackForwardCache at some point. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheEviction) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); // BackForwardCache size is 0. |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a(rfh_a); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); // BackForwardCache size is 1. |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| |
| // The number of document the BackForwardCache can hold per tab. |
| static constexpr size_t kBackForwardCacheLimit = 3; |
| |
| for (size_t i = 2; i < kBackForwardCacheLimit; ++i) { |
| EXPECT_TRUE(NavigateToURL(shell(), i % 2 ? url_b : url_a)); |
| // After |i+1| navigations, |i| documents went into the BackForwardCache. |
| // When |i| is greater than the BackForwardCache size limit, they are |
| // evicted: |
| EXPECT_EQ(i >= kBackForwardCacheLimit + 1, delete_rfh_a.deleted()); |
| EXPECT_EQ(i >= kBackForwardCacheLimit + 2, delete_rfh_b.deleted()); |
| } |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.SubframeSurviveCache* |
| // Test case: a1(b2) -> c3 -> a1(b2) |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache1) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| const GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| std::vector<RenderFrameDeletedObserver*> rfh_observer; |
| |
| // 1) Navigate to a1(b2). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* a1 = current_frame_host(); |
| RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2); |
| rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer}); |
| EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';")); |
| |
| // 2) Navigate to c3. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| RenderFrameHostImpl* c3 = current_frame_host(); |
| RenderFrameDeletedObserver c3_observer(c3); |
| rfh_observer.push_back(&c3_observer); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache())); |
| EXPECT_THAT(c3, Not(InBackForwardCache())); |
| |
| // 3) Go back to a1(b2). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache()))); |
| EXPECT_THAT(c3, InBackForwardCache()); |
| |
| // Even after a new IPC round trip with the renderer, b2 must still be alive. |
| EXPECT_EQ("I am alive", EvalJs(b2, "window.alive")); |
| EXPECT_FALSE(b2_observer.deleted()); |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.SubframeSurviveCache* |
| // Test case: a1(b2) -> b3 -> a1(b2). |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache2) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| std::vector<RenderFrameDeletedObserver*> rfh_observer; |
| |
| // 1) Navigate to a1(b2). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* a1 = current_frame_host(); |
| RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2); |
| rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer}); |
| EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';")); |
| |
| // 2) Navigate to b3. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* b3 = current_frame_host(); |
| RenderFrameDeletedObserver b3_observer(b3); |
| rfh_observer.push_back(&b3_observer); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache())); |
| EXPECT_THAT(b3, Not(InBackForwardCache())); |
| |
| // 3) Go back to a1(b2). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_EQ(a1, current_frame_host()); |
| EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache()))); |
| EXPECT_THAT(b3, InBackForwardCache()); |
| |
| // Even after a new IPC round trip with the renderer, b2 must still be alive. |
| EXPECT_EQ("I am alive", EvalJs(b2, "window.alive")); |
| EXPECT_FALSE(b2_observer.deleted()); |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.tSubframeSurviveCache* |
| // Test case: a1(b2) -> b3(a4) -> a1(b2) -> b3(a4) |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache3) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| const GURL url_b(embedded_test_server()->GetURL( |
| "b.com", "/cross_site_iframe_factory.html?b(a)")); |
| |
| std::vector<RenderFrameDeletedObserver*> rfh_observer; |
| |
| // 1) Navigate to a1(b2). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* a1 = current_frame_host(); |
| RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2); |
| rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer}); |
| EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';")); |
| |
| // 2) Navigate to b3(a4) |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* b3 = current_frame_host(); |
| RenderFrameHostImpl* a4 = b3->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver b3_observer(b3), a4_observer(a4); |
| rfh_observer.insert(rfh_observer.end(), {&b3_observer, &a4_observer}); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache())); |
| EXPECT_THAT(Elements({b3, a4}), Each(Not(InBackForwardCache()))); |
| EXPECT_TRUE(ExecJs(a4, "window.alive = 'I am alive';")); |
| |
| // 3) Go back to a1(b2). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_EQ(a1, current_frame_host()); |
| EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache()))); |
| EXPECT_THAT(Elements({b3, a4}), Each(InBackForwardCache())); |
| |
| // Even after a new IPC round trip with the renderer, b2 must still be alive. |
| EXPECT_EQ("I am alive", EvalJs(b2, "window.alive")); |
| EXPECT_FALSE(b2_observer.deleted()); |
| |
| // 4) Go forward to b3(a4). |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_EQ(b3, current_frame_host()); |
| EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache())); |
| EXPECT_THAT(Elements({b3, a4}), Each(Not(InBackForwardCache()))); |
| |
| // Even after a new IPC round trip with the renderer, a4 must still be alive. |
| EXPECT_EQ("I am alive", EvalJs(a4, "window.alive")); |
| EXPECT_FALSE(a4_observer.deleted()); |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.SubframeSurviveCache* |
| // Test case: a1(b2) -> b3 -> a4 -> b5 -> a1(b2). |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache4) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_ab(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(b)")); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| std::vector<RenderFrameDeletedObserver*> rfh_observer; |
| |
| // 1) Navigate to a1(b2). |
| EXPECT_TRUE(NavigateToURL(shell(), url_ab)); |
| RenderFrameHostImpl* a1 = current_frame_host(); |
| RenderFrameHostImpl* b2 = a1->child_at(0)->current_frame_host(); |
| RenderFrameDeletedObserver a1_observer(a1), b2_observer(b2); |
| rfh_observer.insert(rfh_observer.end(), {&a1_observer, &b2_observer}); |
| EXPECT_TRUE(ExecJs(b2, "window.alive = 'I am alive';")); |
| |
| // 2) Navigate to b3. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* b3 = current_frame_host(); |
| RenderFrameDeletedObserver b3_observer(b3); |
| rfh_observer.push_back(&b3_observer); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({a1, b2}), Each(InBackForwardCache())); |
| EXPECT_THAT(b3, Not(InBackForwardCache())); |
| |
| // 3) Navigate to a4. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* a4 = current_frame_host(); |
| RenderFrameDeletedObserver a4_observer(a4); |
| rfh_observer.push_back(&a4_observer); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| |
| // 4) Navigate to b5 |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* b5 = current_frame_host(); |
| RenderFrameDeletedObserver b5_observer(b5); |
| rfh_observer.push_back(&b5_observer); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({a1, b2, b3, a4}), Each(InBackForwardCache())); |
| EXPECT_THAT(b5, Not(InBackForwardCache())); |
| |
| // 3) Go back to a1(b2). |
| web_contents()->GetController().GoToOffset(-3); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(a1, current_frame_host()); |
| ASSERT_THAT(rfh_observer, Each(Not(Deleted()))); |
| EXPECT_THAT(Elements({b3, a4, b5}), Each(InBackForwardCache())); |
| EXPECT_THAT(Elements({a1, b2}), Each(Not(InBackForwardCache()))); |
| |
| // Even after a new IPC round trip with the renderer, b2 must still be alive. |
| EXPECT_EQ("I am alive", EvalJs(b2, "window.alive")); |
| EXPECT_FALSE(b2_observer.deleted()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigationsAreFullyCommitted) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // During a navigation, the document being navigated *away from* can either be |
| // deleted or stored into the BackForwardCache. The document being navigated |
| // *to* can either be new or restored from the BackForwardCache. |
| // |
| // This test covers every combination: |
| // |
| // 1. Navigate to a cacheable page (()->A) |
| // 2. Navigate to an uncacheable page (A->B) |
| // 3. Go Back to a cached page (B->A) |
| // 4. Navigate to a cacheable page (A->C) |
| // 5. Go Back to a cached page (C->A) |
| // |
| // +-+-------+----------------+---------------+ |
| // |#|nav | curr_document | dest_document | |
| // +-+-------+----------------+---------------| |
| // |1|(()->A)| N/A | new | |
| // |2|(A->B) | cached | new | |
| // |3|(B->A) | deleted | restored | |
| // |4|(A->C) | cached | new | |
| // |5|(C->A) | cached | restored | |
| // +-+-------+----------------+---------------+ |
| // |
| // As part of these navigations we check that LastCommittedURL was updated, |
| // to verify that the frame wasn't simply swapped in without actually |
| // committing. |
| |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url_b(embedded_test_server()->GetURL( |
| "b.com", "/back_forward_cache/page_with_dedicated_worker.html")); |
| const GURL url_c(embedded_test_server()->GetURL("c.com", "/title1.html")); |
| |
| // 1. Navigate to a cacheable page (A). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a(rfh_a); |
| |
| // 2. Navigate from a cacheable page to an uncacheable page (A->B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // 3. Navigate from an uncacheable to a cached page page (B->A). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a); |
| |
| // Page B should be deleted (not cached). |
| delete_rfh_b.WaitUntilDeleted(); |
| |
| // 4. Navigate from a cacheable page to a cacheable page (A->C). |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_c); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_c(rfh_c); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // 5. Navigate from a cacheable page to a cached page (C->A). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_a); |
| |
| // Page C should be in the cache. |
| EXPECT_FALSE(delete_rfh_c.deleted()); |
| EXPECT_TRUE(rfh_c->is_in_back_forward_cache()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| PageWithDedicatedWorkerNotCached) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), |
| embedded_test_server()->GetURL( |
| "a.com", "/back_forward_cache/page_with_dedicated_worker.html"))); |
| RenderFrameDeletedObserver delete_rfh_a(current_frame_host()); |
| |
| // Navigate away. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); |
| |
| // The page with the unsupported feature should be deleted (not cached). |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| // Check that unload event handlers are not dispatched when the page goes |
| // into BackForwardCache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| ConfirmUnloadEventNotFired) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const 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_rfh_a(rfh_a); |
| |
| // 2) Set unload handler and check the title. |
| EXPECT_TRUE(ExecJs(rfh_a, |
| "document.title = 'loaded!';" |
| "window.addEventListener('unload', () => {" |
| " document.title = 'unloaded!';" |
| "});")); |
| { |
| base::string16 title_when_loaded = base::UTF8ToUTF16("loaded!"); |
| TitleWatcher title_watcher(web_contents(), title_when_loaded); |
| EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded); |
| } |
| |
| // 3) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // 4) Go back to A and check the title again. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_FALSE(delete_rfh_b.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| { |
| base::string16 title_when_loaded = base::UTF8ToUTF16("loaded!"); |
| TitleWatcher title_watcher(web_contents(), title_when_loaded); |
| EXPECT_EQ(title_watcher.WaitAndGetTitle(), title_when_loaded); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheIfMainFrameStillLoading) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/main_document"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page that doesn't finish loading. |
| GURL url(embedded_test_server()->GetURL("a.com", "/main_document")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // The navigation starts. |
| EXPECT_TRUE(navigation_manager.WaitForRequestStart()); |
| navigation_manager.ResumeNavigation(); |
| |
| // The server sends the first part of the response and waits. |
| response.WaitForRequest(); |
| response.Send( |
| "HTTP/1.1 200 OK\r\n" |
| "Content-Type: text/html; charset=utf-8\r\n" |
| "\r\n" |
| "<html><body> ... "); |
| |
| // The navigation finishes while the body is still loading. |
| navigation_manager.WaitForNavigationFinished(); |
| RenderFrameDeletedObserver delete_rfh_a(current_frame_host()); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page was still loading when we navigated away, so it shouldn't have |
| // been cached. |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheIfImageStillLoading) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page with an image that loads forever. |
| GURL url(embedded_test_server()->GetURL("a.com", |
| "/infinitely_loading_image.html")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // The navigation finishes while the image is still loading. |
| navigation_manager.WaitForNavigationFinished(); |
| RenderFrameDeletedObserver delete_rfh_a(current_frame_host()); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page was still loading when we navigated away, so it shouldn't have |
| // been cached. |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| // Test is flaky (https://crbug.com/986742). |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DISABLED_LoadingSubframeDoesNotPreventCaching) { |
| // Note: This test is only documenting current behavior. Not trying to say it |
| // should work this way... |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page with an iframe that loads forever. |
| GURL url(embedded_test_server()->GetURL("a.com", |
| "/infinitely_loading_iframe.html")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // The navigation finishes while the iframe is still loading. |
| navigation_manager.WaitForNavigationFinished(); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a(rfh_a); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // The page with the infinitely loading iframe was cached. |
| EXPECT_FALSE(delete_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfWebGL) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page with WebGL usage |
| GURL url(embedded_test_server()->GetURL( |
| "example.com", "/back_forward_cache/page_with_webgl.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| RenderFrameDeletedObserver delete_rfh_a(current_frame_host()); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page had an active WebGL context when we navigated away, |
| // so it shouldn't have been cached. |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| } // namespace content |