| // 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/metrics/metrics_hashes.h" |
| #include "base/task/post_task.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_mock_time_task_runner.h" |
| #include "base/time/time.h" |
| #include "components/network_session_configurator/common/network_switches.h" |
| #include "content/browser/frame_host/back_forward_cache_impl.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.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/navigation_handle_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_navigation_throttle.h" |
| #include "content/public/test/test_navigation_throttle_inserter.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/public/test/url_loader_interceptor.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/content_browser_test_utils_internal.h" |
| #include "net/base/filename_util.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 "services/device/public/cpp/test/scoped_geolocation_overrider.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::ElementsAre; |
| using testing::Not; |
| |
| namespace content { |
| |
| namespace { |
| |
| // Test about the BackForwardCache. |
| class BackForwardCacheBrowserTest : public ContentBrowserTest { |
| public: |
| ~BackForwardCacheBrowserTest() override = default; |
| |
| protected: |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kUseFakeUIForMediaStream); |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kIgnoreCertificateErrors); |
| feature_list_.InitAndEnableFeatureWithParameters( |
| features::kBackForwardCache, GetFeatureParams()); |
| |
| ContentBrowserTest::SetUpCommandLine(command_line); |
| } |
| |
| virtual base::FieldTrialParams GetFeatureParams() { |
| // Set a very long TTL before expiration (longer than the test timeout) so |
| // tests that are expecting deletion don't pass when they shouldn't. |
| return {{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}; |
| } |
| |
| void SetUpOnMainThread() override { |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ContentBrowserTest::SetUpOnMainThread(); |
| } |
| |
| WebContentsImpl* web_contents() const { |
| return static_cast<WebContentsImpl*>(shell()->web_contents()); |
| } |
| |
| RenderFrameHostImpl* current_frame_host() { |
| return web_contents()->GetFrameTree()->root()->current_frame_host(); |
| } |
| |
| RenderFrameHostManager* render_frame_host_manager() { |
| return web_contents()->GetFrameTree()->root()->render_manager(); |
| } |
| |
| void ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome outcome, |
| base::Location location) { |
| base::HistogramBase::Sample sample = base::HistogramBase::Sample(outcome); |
| AddSampleToBuckets(&expected_outcomes_, sample); |
| |
| EXPECT_EQ(expected_outcomes_, |
| histogram_tester_.GetAllSamples( |
| "BackForwardCache.HistoryNavigationOutcome")) |
| << location.ToString(); |
| } |
| |
| void ExpectOutcomeIsEmpty(base::Location location) { |
| EXPECT_THAT(histogram_tester_.GetAllSamples( |
| "BackForwardCache.HistoryNavigationOutcome"), |
| ElementsAre()) |
| << location.ToString(); |
| } |
| |
| void ExpectDisabledWithReason(const std::string& reason, |
| base::Location location) { |
| base::HistogramBase::Sample sample = |
| base::HistogramBase::Sample(base::HashMetricName(reason)); |
| AddSampleToBuckets(&expected_disabled_reasons_, sample); |
| |
| EXPECT_EQ(expected_disabled_reasons_, |
| histogram_tester_.GetAllSamples( |
| "BackForwardCache.HistoryNavigationOutcome." |
| "DisabledForRenderFrameHostReason")) |
| << location.ToString(); |
| } |
| |
| private: |
| void AddSampleToBuckets(std::vector<base::Bucket>* buckets, |
| base::HistogramBase::Sample sample) { |
| auto it = std::find_if( |
| buckets->begin(), buckets->end(), |
| [sample](const base::Bucket& bucket) { return bucket.min == sample; }); |
| if (it == buckets->end()) { |
| buckets->push_back(base::Bucket(sample, 1)); |
| } else { |
| it->count++; |
| } |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| |
| base::HistogramTester histogram_tester_; |
| std::vector<base::Bucket> expected_outcomes_; |
| std::vector<base::Bucket> expected_disabled_reasons_; |
| }; |
| |
| // 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; |
| } |
| |
| // Execute a custom callback when two RenderFrameHosts are swapped. This is |
| // useful for simulating race conditions happening when a page enters the |
| // BackForwardCache and receive inflight messages sent when it wasn't frozen |
| // yet. |
| class RenderFrameHostChangedCallback : public WebContentsObserver { |
| public: |
| RenderFrameHostChangedCallback( |
| WebContents* content, |
| base::OnceCallback<void(RenderFrameHost*, RenderFrameHost*)> callback) |
| : WebContentsObserver(content), callback_(std::move(callback)) {} |
| |
| private: |
| // WebContentsObserver: |
| void RenderFrameHostChanged(RenderFrameHost* old_host, |
| RenderFrameHost* new_host) override { |
| if (callback_) |
| std::move(callback_).Run(old_host, new_host); |
| } |
| |
| base::OnceCallback<void(RenderFrameHost*, RenderFrameHost*)> callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RenderFrameHostChangedCallback); |
| }; |
| |
| } // 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")); |
| const url::Origin origin_a = url::Origin::Create(url_a); |
| const 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)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| 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->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_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_EQ(origin_a, rfh_a->GetLastCommittedOrigin()); |
| EXPECT_EQ(origin_b, rfh_b->GetLastCommittedOrigin()); |
| 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); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // 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_observer_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_observer_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_observer_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_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // 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_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_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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| |
| // 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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| |
| // 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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| |
| // 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_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // 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_observer_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_observer_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_observer_rfh_b(rfh_b); |
| EXPECT_TRUE(ExecJs(rfh_b, "history.back();")); |
| delete_observer_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_observer_rfh_a_new(rfh_a_new); |
| EXPECT_TRUE(ExecJs(rfh_a_new, JsReplace("location = $1;", url_b.spec()))); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| delete_observer_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_observer_rfh_b_new(rfh_b_new); |
| EXPECT_TRUE(ExecJs(rfh_b_new, "history.back();")); |
| EXPECT_TRUE(WaitForLoadStop(web_contents())); |
| EXPECT_FALSE(delete_observer_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_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_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_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_FALSE(delete_observer_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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // 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_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(); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // 4) Go back to a new A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // 5) Flush B. |
| web_contents()->GetController().GetBackForwardCache().Flush(); |
| EXPECT_TRUE(delete_observer_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_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(url_a, web_contents()->GetVisibleURL()); |
| |
| // 4) Go forward to B. |
| web_contents()->GetController().GoForward(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(url_b, web_contents()->GetVisibleURL()); |
| } |
| |
| // Test only 1 document is kept in the at a time BackForwardCache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CacheSizeLimitedToOneDocumentPerTab) { |
| 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")); |
| const GURL url_c(embedded_test_server()->GetURL("c.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); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| // BackForwardCache contains only rfh_b. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // If/when the cache size is increased, this can be tested iteratively, see |
| // deleted code in: https://crrev.com/c/1782902. |
| |
| web_contents()->GetController().GoToOffset(-2); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted, |
| FROM_HERE); |
| } |
| |
| // Test documents are evicted from the BackForwardCache at some point. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| CacheEvictionWithIncreasedCacheSize) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // The number of document the BackForwardCache can hold per tab. |
| static constexpr size_t kBackForwardCacheLimit = 5; |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .set_cache_size_limit_for_testing(kBackForwardCacheLimit); |
| |
| 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_observer_rfh_a(rfh_a); |
| |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); // BackForwardCache size is 1. |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| 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_observer_rfh_a.deleted()); |
| EXPECT_EQ(i >= kBackForwardCacheLimit + 2, delete_observer_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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // 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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // 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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| |
| // 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()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.SubframeSurviveCache* |
| // Test case: a1(b2) -> b3 -> a4 -> b5 -> a1(b2). |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, SubframeSurviveCache4) { |
| // Increase the cache size so that a1(b2) is still in the cache when we |
| // reach b5. |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .set_cache_size_limit_for_testing(3); |
| |
| 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_observer_rfh_a(rfh_a); |
| |
| // 2. Navigate from a cacheable page to an uncacheable page (A->B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_b); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->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_observer_rfh_b.WaitUntilDeleted(); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| |
| // 4. Navigate from a cacheable page to a cacheable page (A->C). |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_EQ(web_contents()->GetLastCommittedURL(), url_c); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->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_observer_rfh_c.deleted()); |
| EXPECT_TRUE(rfh_c->is_in_back_forward_cache()); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| ProxiesAreStoredAndRestored) { |
| // This test makes assumption about where iframe processes live. |
| if (!AreAllSitesIsolatedForTesting()) |
| return; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // During a navigation, the document being navigated *away from* can either be |
| // deleted or stored into the BackForwardCache. The document being navigated |
| // *to* can either be new or restored from the BackForwardCache. |
| // |
| // This test covers every combination: |
| // |
| // 1. Navigate to a cacheable page (()->A) |
| // 2. Navigate to an uncacheable page (A->B) |
| // 3. Go Back to a cached page (B->A) |
| // 4. Navigate to a cacheable page (A->C) |
| // 5. Go Back to a cached page (C->A) |
| // |
| // +-+-------+----------------+---------------+ |
| // |#|nav | curr_document | dest_document | |
| // +-+-------+----------------+---------------| |
| // |1|(()->A)| N/A | new | |
| // |2|(A->B) | cached | new | |
| // |3|(B->A) | deleted | restored | |
| // |4|(A->C) | cached | new | |
| // |5|(C->A) | cached | restored | |
| // +-+-------+----------------+---------------+ |
| // |
| // We use pages with cross process iframes to verify that proxy storage and |
| // retrieval works well in every possible combination. |
| |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(i,j)")); |
| 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", "/cross_site_iframe_factory.html?c(k,l,m)")); |
| |
| NavigationControllerImpl& controller = web_contents()->GetController(); |
| BackForwardCacheImpl& cache = controller.GetBackForwardCache(); |
| |
| // 1. Navigate to a cacheable page (A). |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2. Navigate from a cacheable page to an uncacheable page (A->B). |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_EQ(0u, render_frame_host_manager()->GetProxyCount()); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // Verify proxies are stored as well. |
| auto* cached_entry = cache.GetEntry(rfh_a->nav_entry_id()); |
| EXPECT_EQ(2u, cached_entry->proxy_hosts.size()); |
| |
| // 3. Navigate from an uncacheable to a cached page page (B->A). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| // Note: We still have a transition proxy that will be used to perform the |
| // frame SwapOut. It gets deleted with rfh_b below. |
| EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount()); |
| |
| // Page B should be deleted (not cached). |
| delete_observer_rfh_b.WaitUntilDeleted(); |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| |
| // 4. Navigate from a cacheable page to a cacheable page (A->C). |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_EQ(3u, render_frame_host_manager()->GetProxyCount()); |
| RenderFrameHostImpl* rfh_c = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_c(rfh_c); |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // Verify proxies are stored as well. |
| cached_entry = cache.GetEntry(rfh_a->nav_entry_id()); |
| EXPECT_EQ(2u, cached_entry->proxy_hosts.size()); |
| |
| // 5. Navigate from a cacheable page to a cached page (C->A). |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(2u, render_frame_host_manager()->GetProxyCount()); |
| |
| // Page C should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_c.deleted()); |
| EXPECT_TRUE(rfh_c->is_in_back_forward_cache()); |
| |
| // Verify proxies are stored as well. |
| cached_entry = cache.GetEntry(rfh_c->nav_entry_id()); |
| EXPECT_EQ(3u, cached_entry->proxy_hosts.size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| RestoredProxiesAreFunctional) { |
| // This test makes assumption about where iframe processes live. |
| if (!AreAllSitesIsolatedForTesting()) |
| return; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Page A is cacheable, while page B is not. |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.com", "/cross_site_iframe_factory.html?a(z)")); |
| const GURL url_b(embedded_test_server()->GetURL( |
| "b.com", "/back_forward_cache/page_with_dedicated_worker.html")); |
| const GURL test_url(embedded_test_server()->GetURL("c.com", "/title1.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)); |
| |
| // 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. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->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 = '" + test_url.spec() + "';")); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // We expect to have navigated through the proxy. |
| EXPECT_EQ(test_url, controller.GetLastCommittedEntry()->GetURL()); |
| } |
| |
| 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_observer_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_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| // TODO(https://crbug.com/154571): Shared workers are not available on Android. |
| #if defined(OS_ANDROID) |
| #define MAYBE_PageWithSharedWorkerNotCached \ |
| DISABLED_PageWithSharedWorkerNotCached |
| #else |
| #define MAYBE_PageWithSharedWorkerNotCached PageWithSharedWorkerNotCached |
| #endif |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| MAYBE_PageWithSharedWorkerNotCached) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| EXPECT_TRUE(NavigateToURL( |
| shell(), |
| embedded_test_server()->GetURL( |
| "a.com", "/back_forward_cache/page_with_shared_worker.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 with the unsupported feature should be deleted (not cached). |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| SubframeWithDisallowedFeatureNotCached) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to a page with an iframe that contains a dedicated worker. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), |
| embedded_test_server()->GetURL( |
| "a.com", "/back_forward_cache/dedicated_worker_in_subframe.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_observer_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_observer_rfh_b(rfh_b); |
| EXPECT_FALSE(delete_observer_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_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| 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, |
| DoesNotCacheIfRecordingAudio) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to an empty page. |
| GURL url(embedded_test_server()->GetURL("/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| // Request for audio recording. |
| EXPECT_EQ("success", EvalJs(current_frame_host(), R"( |
| new Promise(resolve => { |
| navigator.mediaDevices.getUserMedia({audio: true}) |
| .then(m => { resolve("success"); }) |
| .catch(() => { resolve("error"); }); |
| }); |
| )")); |
| |
| RenderFrameDeletedObserver deleted(current_frame_host()); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page was still recording audio when we navigated away, so it shouldn't |
| // have been cached. |
| deleted.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheIfSubframeRecordingAudio) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Navigate to a page with an iframe. |
| GURL url(embedded_test_server()->GetURL("/page_with_iframe.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| RenderFrameHostImpl* rfh = current_frame_host(); |
| |
| // Request for audio recording from the subframe. |
| EXPECT_EQ("success", EvalJs(rfh->child_at(0)->current_frame_host(), R"( |
| new Promise(resolve => { |
| navigator.mediaDevices.getUserMedia({audio: true}) |
| .then(m => { resolve("success"); }) |
| .catch(() => { resolve("error"); }); |
| }); |
| )")); |
| |
| RenderFrameDeletedObserver deleted(current_frame_host()); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // The page was still recording audio when we navigated away, so it shouldn't |
| // have been cached. |
| deleted.WaitUntilDeleted(); |
| } |
| |
| 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_observer_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_observer_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_observer_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_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheLoadingSubframe) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/controlled"); |
| 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", "/back_forward_cache/controllable_subframe.html")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // The navigation finishes while the iframe is still loading. |
| navigation_manager.WaitForNavigationFinished(); |
| |
| // Wait for the iframe request to arrive, and leave it hanging with no |
| // response. |
| response.WaitForRequest(); |
| |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_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 should not have been added to cache, since it had a subframe that |
| // was still loading at the time it was navigated away from. |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheLoadingSubframeOfSubframe) { |
| net::test_server::ControllableHttpResponse response(embedded_test_server(), |
| "/controlled"); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 1) Navigate to a page with an iframe that contains yet another iframe, that |
| // hangs while loading. |
| GURL url(embedded_test_server()->GetURL( |
| "a.com", "/back_forward_cache/controllable_subframe_of_subframe.html")); |
| TestNavigationManager navigation_manager(shell()->web_contents(), url); |
| shell()->LoadURL(url); |
| |
| // The navigation finishes while the iframe within an iframe is still loading. |
| navigation_manager.WaitForNavigationFinished(); |
| |
| // Wait for the innermost iframe request to arrive, and leave it hanging with |
| // no response. |
| response.WaitForRequest(); |
| |
| 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 should not have been added to the cache, since it had an iframe |
| // that was still loading at the time it was navigated away from. |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| 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_observer_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. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, DoesNotCacheIfHttpError) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL error_url(embedded_test_server()->GetURL("a.com", "/page404.html")); |
| GURL url(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // Navigate to an error page. |
| EXPECT_TRUE(NavigateToURL(shell(), error_url)); |
| EXPECT_EQ(net::HTTP_NOT_FOUND, current_frame_host()->last_http_status_code()); |
| RenderFrameDeletedObserver delete_rfh_a(current_frame_host()); |
| |
| // Navigate away. |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| // The page did not return 200 (OK), so it shouldn't have been cached. |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCacheIfPageUnreachable) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL error_url(embedded_test_server()->GetURL("a.com", "/empty.html")); |
| GURL url(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| std::unique_ptr<URLLoaderInterceptor> url_interceptor = |
| URLLoaderInterceptor::SetupRequestFailForURL(error_url, |
| net::ERR_DNS_TIMED_OUT); |
| |
| // Start with a successful navigation to a document. |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| EXPECT_EQ(net::HTTP_OK, current_frame_host()->last_http_status_code()); |
| |
| // Navigate to an error page. |
| NavigationHandleObserver observer(shell()->web_contents(), error_url); |
| EXPECT_FALSE(NavigateToURL(shell(), error_url)); |
| EXPECT_TRUE(observer.is_error()); |
| EXPECT_EQ(net::ERR_DNS_TIMED_OUT, observer.net_error_code()); |
| EXPECT_EQ( |
| GURL(kUnreachableWebDataURL), |
| shell()->web_contents()->GetMainFrame()->GetSiteInstance()->GetSiteURL()); |
| EXPECT_EQ(net::OK, current_frame_host()->last_http_status_code()); |
| |
| RenderFrameDeletedObserver delete_rfh_a(current_frame_host()); |
| |
| // Navigate away. |
| EXPECT_TRUE(NavigateToURL(shell(), url)); |
| |
| // The page had a networking error, so it shouldn't have been cached. |
| delete_rfh_a.WaitUntilDeleted(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackforwardCacheForTesting) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Disable the BackForwardCache. |
| web_contents()->GetController().GetBackForwardCache().DisableForTesting( |
| BackForwardCacheImpl::TEST_ASSUMES_NO_CACHING); |
| |
| // Navigate to a page that would normally be cacheable. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("a.com", "/title1.html"))); |
| RenderFrameDeletedObserver delete_observer_rfh_a(current_frame_host()); |
| |
| // Navigate away. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), embedded_test_server()->GetURL("b.com", "/title1.html"))); |
| |
| // The page should be deleted (not cached). |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| } |
| |
| // Navigate from A to B, then cause JavaScript execution on A, then go back. |
| // Test the RenderFrameHost in the cache is evicted by JavaScript. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EvictionOnJavaScriptExecution) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // 3) Execute JavaScript on A. |
| // |
| // Run JavaScript on a page in the back-forward cache. The page should be |
| // evicted. As the frame is deleted, ExecJs returns false without executing. |
| EXPECT_FALSE(ExecJs(rfh_a, "console.log('hi');")); |
| |
| // RenderFrameHost A is evicted from the BackForwardCache: |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 4) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted, |
| 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->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_c->is_in_back_forward_cache()); |
| |
| // 3) Execute JavaScript on B. |
| // |
| // As the frame is deleted, ExecJs returns false without executing. |
| EXPECT_FALSE(ExecJs(rfh_b, "console.log('hi');")); |
| |
| // 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. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted, |
| 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->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // 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. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted, |
| FROM_HERE); |
| } |
| |
| // Similar to BackForwardCacheBrowserTest.EvictionOnJavaScriptExecution, but |
| // cause the race condition of eviction and restoring. |
| // |
| // ┌───────┐ ┌────────┐ |
| // │Browser│ │Renderer│ |
| // └───┬───┘ └───┬────┘ |
| // (Store to the bfcache) │ |
| // │ Freeze │ |
| // │────────────────────────>│ |
| // │ │ |
| // (Restore from the bfcache) │ |
| // │──┐ │ |
| // │ │ │ |
| // │EvictFromBackForwardCache│ |
| // │<────────────────────────│ |
| // │ │ │ |
| // │ │ Resume │ |
| // │ └─────────────────────>│ |
| // ┌───┴───┐ ┌───┴────┐ |
| // │Browser│ │Renderer│ |
| // └───────┘ └────────┘ |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EvictionOnJavaScriptExecutionRace) { |
| 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); |
| EXPECT_TRUE(ExecJs(rfh_a, "window.foo = 'initial document'")); |
| |
| // 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->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| |
| // 3) Execute JavaScript on A when restoring A. |
| // Execute JavaScript after committing but before swapping happens on the |
| // renderer. |
| RenderFrameHostChangedCallback host_changed_callback( |
| web_contents(), |
| base::BindOnce( |
| [](RenderFrameHostImpl* rfh_a, |
| RenderFrameDeletedObserver* delete_observer_rfh_a, |
| RenderFrameHost* old_host, RenderFrameHost* new_host) { |
| EXPECT_FALSE(delete_observer_rfh_a->deleted()); |
| EXPECT_EQ(rfh_a, new_host); |
| ExecuteScriptAsync(new_host, "console.log('hi');"); |
| }, |
| rfh_a, &delete_observer_rfh_a)); |
| |
| // Wait for two navigations to finish. The first one is the BackForwardCache |
| // navigation, the other is the reload caused by the eviction. |
| // |
| // 1. BFcache navigation start. |
| // 2. BFcache navigation commit & finish. |
| // 3. Evict & reload start. |
| // 4. Reload commit. |
| // 5. Reload finish. |
| // |
| // Each item is a single task. |
| TestNavigationObserver observer(web_contents(), |
| 2 /* number of navigations */); |
| observer.set_wait_event( |
| TestNavigationObserver::WaitEvent::kNavigationFinished); |
| web_contents()->GetController().GoBack(); |
| observer.WaitForNavigationFinished(); |
| |
| // A is not destroyed. A is reloaded instead. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_TRUE(rfh_b->is_in_back_forward_cache()); |
| EXPECT_NE("initial document", EvalJs(rfh_a, "window.foo")); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kRestored, |
| FROM_HERE); |
| } |
| |
| // Tests the events are fired when going back from the cache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Events) { |
| 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); |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| window.testObservedEvents = []; |
| let event_list = [ |
| 'visibilitychange', |
| 'pagehide', |
| 'pageshow', |
| 'freeze', |
| 'resume', |
| ]; |
| for (event_name of event_list) { |
| let result = event_name; |
| window.addEventListener(event_name, event => { |
| if (event.persisted) |
| result +='.persisted'; |
| window.testObservedEvents.push(result); |
| }); |
| document.addEventListener(event_name, |
| () => window.testObservedEvents.push(result)); |
| } |
| )")); |
| |
| // 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->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| // TODO(yuzus): Post message to the frozen page, and make sure that the |
| // messages arrive after the page visibility events, not before them. |
| |
| // 3) Go back to A. Confirm that expected events are fired. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| // visibilitychange events are added twice per each because it is fired for |
| // both window and document. |
| EXPECT_EQ( |
| ListValueOf("visibilitychange", "visibilitychange", "pagehide.persisted", |
| "freeze", "resume", "pageshow.persisted", "visibilitychange", |
| "visibilitychange"), |
| EvalJs(shell(), "window.testObservedEvents")); |
| } |
| |
| // Tests the events are fired when going back from the cache. |
| // Same as: BackForwardCacheBrowserTest.Events, but with a document-initiated |
| // navigation. This is a regression test for https://crbug.com/1000324 |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| EventsAfterDocumentInitiatedNavigation) { |
| 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); |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| window.testObservedEvents = []; |
| let event_list = [ |
| 'visibilitychange', |
| 'pagehide', |
| 'pageshow', |
| 'freeze', |
| 'resume', |
| ]; |
| for (event_name of event_list) { |
| let result = event_name; |
| window.addEventListener(event_name, event => { |
| if (event.persisted) |
| result +='.persisted'; |
| window.testObservedEvents.push(result); |
| }); |
| document.addEventListener(event_name, |
| () => window.testObservedEvents.push(result)); |
| } |
| )")); |
| |
| // 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_observer_rfh_b(rfh_b); |
| |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_b->is_in_back_forward_cache()); |
| // TODO(yuzus): Post message to the frozen page, and make sure that the |
| // messages arrive after the page visibility events, not before them. |
| |
| // 3) Go back to A. Confirm that expected events are fired. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| // visibilitychange events are added twice per each because it is fired for |
| // both window and document. |
| EXPECT_EQ( |
| ListValueOf("visibilitychange", "visibilitychange", "pagehide.persisted", |
| "freeze", "resume", "pageshow.persisted", "visibilitychange", |
| "visibilitychange"), |
| EvalJs(shell(), "window.testObservedEvents")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigationCancelledAtWillStartRequest) { |
| 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_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // Cancel all navigation attempts. |
| content::TestNavigationThrottleInserter throttle_inserter( |
| shell()->web_contents(), |
| base::BindLambdaForTesting( |
| [&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> { |
| auto throttle = std::make_unique<TestNavigationThrottle>(handle); |
| throttle->SetResponse(TestNavigationThrottle::WILL_START_REQUEST, |
| TestNavigationThrottle::SYNCHRONOUS, |
| NavigationThrottle::CANCEL_AND_IGNORE); |
| return throttle; |
| })); |
| |
| // 3) Go back to A, which will be cancelled by the NavigationThrottle. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // We should still be showing page B. |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigationCancelledAtWillProcessResponse) { |
| 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_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // Cancel all navigation attempts. |
| content::TestNavigationThrottleInserter throttle_inserter( |
| shell()->web_contents(), |
| base::BindLambdaForTesting( |
| [&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> { |
| auto throttle = std::make_unique<TestNavigationThrottle>(handle); |
| throttle->SetResponse(TestNavigationThrottle::WILL_PROCESS_RESPONSE, |
| TestNavigationThrottle::SYNCHRONOUS, |
| NavigationThrottle::CANCEL_AND_IGNORE); |
| return throttle; |
| })); |
| |
| // 3) Go back to A, which will be cancelled by the NavigationThrottle. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // We should still be showing page B. |
| EXPECT_EQ(rfh_b, current_frame_host()); |
| } |
| |
| // Test the race condition where a document is evicted from the BackForwardCache |
| // while it is in the middle of being restored. |
| // |
| // ┌───────┐ ┌────────┐ |
| // │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) { |
| 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->is_in_back_forward_cache()); |
| EXPECT_NE(rfh_a, rfh_b); |
| |
| // 3) Start navigation to page A, and cause the document to be evicted during |
| // the navigation. |
| TestNavigationManager navigation_manager(shell()->web_contents(), url_a); |
| web_contents()->GetController().GoBack(); |
| |
| EXPECT_TRUE(navigation_manager.WaitForRequestStart()); |
| // Try to execute javascript, this will cause the document we are restoring to |
| // get evicted from the cache. |
| EXPECT_FALSE(ExecJs(rfh_a, "console.log('hi');")); |
| |
| // The navigation should get reissued, and ultimately finish. |
| navigation_manager.WaitForNavigationFinished(); |
| |
| // rfh_a should have been deleted, and page A navigated to normally. |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| RenderFrameHostImpl* rfh_a2 = current_frame_host(); |
| EXPECT_NE(rfh_a2, rfh_b); |
| EXPECT_EQ(rfh_a2->GetLastCommittedURL(), url_a); |
| |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted, |
| FROM_HERE); |
| } |
| |
| // 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()); |
| 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_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_TRUE(rfh_a->is_in_back_forward_cache()); |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| |
| // 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); |
| rfh_a->GetProcess()->Shutdown(0); |
| |
| // The cached RenderFrameHost should be destroyed (not kept in the cache). |
| crash_observer.Wait(); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| |
| // rfh_b should still be the current frame. |
| EXPECT_EQ(current_frame_host(), rfh_b); |
| EXPECT_FALSE(delete_observer_rfh_b.deleted()); |
| |
| // 4) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcome(BackForwardCacheMetrics::HistoryNavigationOutcome::kEvicted, |
| FROM_HERE); |
| } |
| |
| // The test is simulating a race condition. The scheduler tracked features are |
| // updated during the "freeze" event in a way that would have prevented the |
| // document from entering the BackForwardCache in the first place. |
| // |
| // TODO(https://crbug.com/996267): The document should be evicted. |
| // |
| // ┌───────┐ ┌────────┐ |
| // │browser│ │renderer│ |
| // └───┬───┘ └────┬───┘ |
| // (enter cache) │ |
| // │ Freeze() │ |
| // │─────────────────────────────>│ |
| // │ (onfreeze) |
| // │OnSchedulerTrackedFeaturesUsed│ |
| // │<─────────────────────────────│ |
| // │ (frozen) |
| // │ │ |
| // ┌───┴───┐ ┌────┴───┐ |
| // │browser│ │renderer│ |
| // └───────┘ └────────┘ |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| SchedulerTrackedFeaturesUpdatedWhileStoring) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| // When the page will enter the BackForwardCache, just before being frozen, |
| // use a feature that would have been prevented the document from being |
| // cached. |
| EXPECT_TRUE(ExecJs(rfh_a, R"( |
| document.addEventListener('freeze', event => { |
| let canvas = document.createElement('canvas'); |
| document.body.appendChild(canvas); |
| gl = canvas.getContext('webgl'); |
| }); |
| )")); |
| |
| // 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(); |
| } |
| |
| // A fetch request starts during the "freeze" event. The current behavior is to |
| // send the request anyway. However evicting the page from the BackForwardCache |
| // might be a better behavior. |
| // |
| // ┌───────┐┌────────┐ ┌───────────────┐ |
| // │browser││renderer│ │network service│ |
| // └───┬───┘└───┬────┘ └───────┬───────┘ |
| // │Freeze()│ │ |
| // │───────>│ │ |
| // │ (onfreeze) │ |
| // │ │CreateLoaderAndStart│ |
| // │ │───────────────────>│ |
| // │ (frozen) │ |
| // ┌───┴───┐┌───┴────┐ ┌───────┴───────┐ |
| // │browser││renderer│ │network service│ |
| // └───────┘└────────┘ └───────────────┘ |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, FetchWhileStoring) { |
| net::test_server::ControllableHttpResponse fetch_response( |
| embedded_test_server(), "/fetch"); |
| 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); |
| |
| // Use "fetch" immediately before being frozen. |
| EXPECT_TRUE(ExecJs(rfh_a, R"( |
| document.addEventListener('freeze', event => { |
| my_fetch = fetch('/fetch', { keepalive: true}); |
| }); |
| )")); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| fetch_response.WaitForRequest(); |
| fetch_response.Send(net::HTTP_OK, "text/html", "TheResponse"); |
| fetch_response.Done(); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| } |
| |
| // Only HTTP/HTTPS main document can enter the BackForwardCache. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheHTTPDocumentOnly) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.AddDefaultHandlers(GetTestDataFilePath()); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK); |
| ASSERT_TRUE(https_server.Start()); |
| |
| GURL http_url(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL https_url(https_server.GetURL("a.com", "/title1.html")); |
| GURL file_url = net::FilePathToFileURL(GetTestFilePath("", "title1.html")); |
| GURL data_url = GURL("data:text/html,"); |
| GURL blank_url = GURL(url::kAboutBlankURL); |
| GURL webui_url = GetWebUIURL("gpu"); |
| |
| enum { STORED, DELETED }; |
| struct { |
| int expectation; |
| GURL url; |
| } test_cases[] = { |
| // Only document with HTTP/HTTPS URLs are allowed to enter the |
| // BackForwardCache. |
| {STORED, http_url}, |
| {STORED, https_url}, |
| |
| // Others aren't allowed. |
| {DELETED, file_url}, |
| {DELETED, data_url}, |
| {DELETED, webui_url}, |
| {DELETED, blank_url}, |
| }; |
| |
| char hostname[] = "a.unique"; |
| for (auto& test_case : test_cases) { |
| SCOPED_TRACE(testing::Message() |
| << std::endl |
| << "expectation = " << test_case.expectation << std::endl |
| << "url = " << test_case.url << std::endl); |
| |
| // 1) Navigate to. |
| EXPECT_TRUE(NavigateToURL(shell(), test_case.url)); |
| RenderFrameHostImpl* rfh = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer(rfh); |
| |
| // 2) Navigate away. |
| hostname[0]++; |
| GURL reset_url(embedded_test_server()->GetURL(hostname, "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), reset_url)); |
| |
| if (test_case.expectation == STORED) { |
| EXPECT_FALSE(delete_observer.deleted()); |
| EXPECT_TRUE(rfh->is_in_back_forward_cache()); |
| continue; |
| } |
| |
| // On Android, navigations to about:blank keeps the same RenderFrameHost. |
| // Obviously, it can't enter the BackForwardCache, because it is still used |
| // to display the current document. |
| if (test_case.url == blank_url && |
| !SiteIsolationPolicy::UseDedicatedProcessesForAllSites()) { |
| EXPECT_FALSE(delete_observer.deleted()); |
| EXPECT_FALSE(rfh->is_in_back_forward_cache()); |
| EXPECT_EQ(rfh, current_frame_host()); |
| continue; |
| } |
| |
| delete_observer.WaitUntilDeleted(); |
| } |
| } |
| |
| namespace { |
| |
| void RegisterServiceWorker(RenderFrameHostImpl* rfh) { |
| EXPECT_EQ("success", EvalJs(rfh, R"( |
| let controller_changed_promise = new Promise(resolve_controller_change => { |
| navigator.serviceWorker.oncontrollerchange = resolve_controller_change; |
| }); |
| |
| new Promise(async resolve => { |
| try { |
| await navigator.serviceWorker.register( |
| "./service-worker.js", {scope: "./"}) |
| } catch (e) { |
| resolve("error: registration has failed"); |
| } |
| |
| await controller_changed_promise; |
| |
| if (navigator.serviceWorker.controller) { |
| resolve("success"); |
| } else { |
| resolve("error: not controlled by service worker"); |
| } |
| }); |
| )")); |
| } |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DoesNotCachePagesWithServiceWorkers) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.AddDefaultHandlers(GetTestDataFilePath()); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK); |
| ASSERT_TRUE(https_server.Start()); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), https_server.GetURL("a.com", "/back_forward_cache/empty.html"))); |
| |
| // Register a service worker. |
| RegisterServiceWorker(current_frame_host()); |
| |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver deleted(rfh_a); |
| |
| // 2) Navigate away. |
| shell()->LoadURL(https_server.GetURL("b.com", "/title1.html")); |
| |
| EXPECT_FALSE(rfh_a->is_in_back_forward_cache()); |
| // The page is controlled by a service worker, so it shouldn't have been |
| // cached. |
| deleted.WaitUntilDeleted(); |
| |
| ExpectOutcomeIsEmpty(FROM_HERE); |
| } |
| |
| class BackForwardCacheBrowserTestWithServiceWorkerEnabled |
| : public BackForwardCacheBrowserTest { |
| public: |
| BackForwardCacheBrowserTestWithServiceWorkerEnabled() {} |
| ~BackForwardCacheBrowserTestWithServiceWorkerEnabled() override {} |
| |
| protected: |
| base::FieldTrialParams GetFeatureParams() override { |
| return {{"service_worker_supported", "true"}}; |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTestWithServiceWorkerEnabled, |
| CachedPagesWithServiceWorkers) { |
| net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); |
| https_server.AddDefaultHandlers(GetTestDataFilePath()); |
| https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK); |
| SetupCrossSiteRedirector(&https_server); |
| ASSERT_TRUE(https_server.Start()); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL( |
| shell(), https_server.GetURL("a.com", "/back_forward_cache/empty.html"))); |
| |
| // Register a service worker. |
| RegisterServiceWorker(current_frame_host()); |
| |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver deleted(rfh_a); |
| |
| // 2) Navigate away. |
| EXPECT_TRUE( |
| NavigateToURL(shell(), https_server.GetURL("b.com", "/title1.html"))); |
| |
| EXPECT_FALSE(deleted.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // 3) Go back to A. The navigation should be served from the cache. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_FALSE(deleted.deleted()); |
| EXPECT_EQ(rfh_a, current_frame_host()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CachePagesWithBeacon) { |
| constexpr char kKeepalivePath[] = "/keepalive"; |
| |
| net::test_server::ControllableHttpResponse keepalive(embedded_test_server(), |
| kKeepalivePath); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_ping(embedded_test_server()->GetURL("a.com", kKeepalivePath)); |
| |
| // 1) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| RenderFrameHostImpl* rfh_a = current_frame_host(); |
| RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a); |
| |
| EXPECT_TRUE( |
| ExecJs(shell(), JsReplace(R"(navigator.sendBeacon($1, "");)", url_ping))); |
| |
| // 2) Navigate to B. |
| GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // Ensure that the keepalive request is sent. |
| keepalive.WaitForRequest(); |
| // Don't actually send the response. |
| |
| // Page A should be in the cache. |
| EXPECT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| } |
| |
| // Regression test for https://crbug.com/993337. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| NavigateToTwoPagesOnSameSite) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL url_a1(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| GURL url_a2(embedded_test_server()->GetURL("a.com", "/title2.html")); |
| GURL url_b1(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| |
| // 1) Navigate to A1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a1)); |
| |
| // 2) Navigate to A2. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a2)); |
| RenderFrameHostImpl* rfh_a2 = current_frame_host(); |
| RenderFrameDeletedObserver delete_rfh_a2(current_frame_host()); |
| |
| // 3) Navigate to B1. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b1)); |
| EXPECT_TRUE(rfh_a2->is_in_back_forward_cache()); |
| RenderFrameHostImpl* rfh_b1 = current_frame_host(); |
| |
| // 4) Do a history navigation back to A1. |
| web_contents()->GetController().GoToIndex(0); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_TRUE(rfh_b1->is_in_back_forward_cache()); |
| |
| // As a result, rfh_a2 is deleted. The history navigation tried to restore an |
| // entry using the same BrowsingInstance. They both can't live together. |
| delete_rfh_a2.WaitUntilDeleted(); |
| } |
| |
| class GeolocationBackForwardCacheBrowserTest |
| : public BackForwardCacheBrowserTest { |
| protected: |
| GeolocationBackForwardCacheBrowserTest() : geo_override_(0.0, 0.0) {} |
| |
| device::ScopedGeolocationOverrider geo_override_; |
| }; |
| |
| // Test that a page which has queried geolocation in the past, but have no |
| // active geolocation query, can be bfcached. |
| IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest, |
| CacheAfterGeolocationRequest) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("/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(); |
| |
| // Query current position, and wait for the query to complete. |
| EXPECT_EQ("received", EvalJs(rfh_a, R"( |
| new Promise(resolve => { |
| navigator.geolocation.getCurrentPosition(() => resolve('received')); |
| }); |
| )")); |
| |
| RenderFrameDeletedObserver deleted(rfh_a); |
| |
| // 2) Navigate away. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| // The page has no inflight geolocation request when we navigated away, |
| // so it should have been cached. |
| EXPECT_FALSE(deleted.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| } |
| |
| // Test that a page which has an inflight geolocation query can be bfcached, |
| // and verify that the page does not observe any geolocation while the page |
| // was inside bfcache. |
| IN_PROC_BROWSER_TEST_F(GeolocationBackForwardCacheBrowserTest, |
| CancelGeolocationRequestInFlight) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL("/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(); |
| |
| // Continuously query current geolocation. |
| EXPECT_TRUE(ExecJs(rfh_a, R"( |
| window.longitude_log = []; |
| window.err_log = []; |
| window.wait_for_first_position = new Promise(resolve => { |
| navigator.geolocation.watchPosition( |
| pos => { |
| window.longitude_log.push(pos.coords.longitude); |
| resolve("resolved"); |
| }, |
| err => window.err_log.push(err) |
| ); |
| }) |
| )")); |
| geo_override_.UpdateLocation(0.0, 0.0); |
| EXPECT_EQ("resolved", EvalJs(rfh_a, "window.wait_for_first_position")); |
| |
| // Pause resolving Geoposition queries to keep the request inflight. |
| geo_override_.Pause(); |
| geo_override_.UpdateLocation(1.0, 1.0); |
| EXPECT_EQ(1u, geo_override_.GetGeolocationInstanceCount()); |
| |
| // 2) Navigate away. |
| base::RunLoop loop_until_close; |
| geo_override_.SetGeolocationCloseCallback(loop_until_close.QuitClosure()); |
| |
| RenderFrameDeletedObserver deleted(rfh_a); |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| |
| loop_until_close.Run(); |
| |
| // The page has no inflight geolocation request when we navigated away, |
| // so it should have been cached. |
| EXPECT_FALSE(deleted.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // Resume resolving Geoposition queries. |
| geo_override_.Resume(); |
| |
| // We update the location while the page is BFCached, but this location should |
| // not be observed. |
| geo_override_.UpdateLocation(2.0, 2.0); |
| |
| // 3) Navigate back to A. |
| |
| // The location when navigated back can be observed |
| geo_override_.UpdateLocation(3.0, 3.0); |
| |
| 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()); |
| |
| // Wait for an update after the user navigates back to A. |
| EXPECT_EQ("resolved", EvalJs(rfh_a, R"( |
| window.wait_for_position_after_resume = new Promise(resolve => { |
| navigator.geolocation.watchPosition( |
| pos => { |
| window.longitude_log.push(pos.coords.longitude); |
| resolve("resolved"); |
| }, |
| err => window.err_log.push(err) |
| ); |
| }) |
| )")); |
| |
| EXPECT_LE(0, EvalJs(rfh_a, "longitude_log.indexOf(0.0)").ExtractInt()) |
| << "Geoposition before the page is put into BFCache should be visible"; |
| EXPECT_EQ(-1, EvalJs(rfh_a, "longitude_log.indexOf(1.0)").ExtractInt()) |
| << "Geoposition while the page is put into BFCache should be invisible"; |
| EXPECT_EQ(-1, EvalJs(rfh_a, "longitude_log.indexOf(2.0)").ExtractInt()) |
| << "Geoposition while the page is put into BFCache should be invisible"; |
| EXPECT_LT(0, EvalJs(rfh_a, "longitude_log.indexOf(3.0)").ExtractInt()) |
| << "Geoposition when the page is restored from BFCache should be visible"; |
| EXPECT_EQ(0, EvalJs(rfh_a, "err_log.length")) |
| << "watchPosition API should have reported no errors"; |
| } |
| |
| // Test that documents are evicted correctly from BackForwardCache after time to |
| // live. |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, TimedEviction) { |
| // Inject mock time task runner to be used in the eviction timer, so we can, |
| // check for the functionality we are interested before and after the time to |
| // live. We don't replace ThreadTaskRunnerHandle::Get to ensure that it |
| // doesn't affect other unrelated callsites. |
| scoped_refptr<base::TestMockTimeTaskRunner> task_runner = |
| base::MakeRefCounted<base::TestMockTimeTaskRunner>(); |
| |
| web_contents()->GetController().GetBackForwardCache().SetTaskRunnerForTesting( |
| task_runner); |
| |
| base::TimeDelta time_to_live_in_back_forward_cache = |
| BackForwardCacheImpl::GetTimeToLiveInBackForwardCache(); |
| // This should match the value we set in GetFeatureParams. |
| EXPECT_EQ(time_to_live_in_back_forward_cache, |
| base::TimeDelta::FromSeconds(3600)); |
| |
| base::TimeDelta delta = base::TimeDelta::FromMilliseconds(1); |
| |
| 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_observer_rfh_a(rfh_a); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| RenderFrameHostImpl* rfh_b = current_frame_host(); |
| |
| // 3) Fast forward to just before eviction is due. |
| task_runner->FastForwardBy(time_to_live_in_back_forward_cache - delta); |
| |
| // 4) Confirm A is still in BackForwardCache. |
| ASSERT_FALSE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(rfh_a->is_in_back_forward_cache()); |
| |
| // 5) Fast forward to when eviction is due. |
| task_runner->FastForwardBy(delta); |
| |
| // 6) Confirm A is evicted. |
| EXPECT_TRUE(rfh_a->is_evicted_from_back_forward_cache()); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| EXPECT_EQ(current_frame_host(), rfh_b); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| BackForwardCacheBrowserTest, |
| DisableBackForwardCachePreventsDocumentsFromBeingCached) { |
| 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")); |
| const url::Origin origin_a = url::Origin::Create(url_a); |
| const 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); |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .DisableForRenderFrameHost(rfh_a->GetGlobalFrameRoutingId(), |
| "DisabledByBackForwardCacheBrowserTest"); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectDisabledWithReason("DisabledByBackForwardCacheBrowserTest", FROM_HERE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackForwardIsNoOpIfRfhIsGone) { |
| 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")); |
| const url::Origin origin_a = url::Origin::Create(url_a); |
| const 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); |
| GlobalFrameRoutingId rfh_a_id = rfh_a->GetGlobalFrameRoutingId(); |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .DisableForRenderFrameHost(rfh_a_id, |
| "DisabledByBackForwardCacheBrowserTest"); |
| |
| // 2) Navigate to B. |
| EXPECT_TRUE(NavigateToURL(shell(), url_b)); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| |
| // This should not die |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .DisableForRenderFrameHost(rfh_a_id, |
| "DisabledByBackForwardCacheBrowserTest"); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectDisabledWithReason("DisabledByBackForwardCacheBrowserTest", 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); |
| |
| BackForwardCache::DisableForRenderFrameHost(rfh_b, "test"); |
| |
| // 2) Navigate to C. A and B are immediately deleted. |
| EXPECT_TRUE(NavigateToURL(shell(), url_c)); |
| EXPECT_TRUE(delete_observer_rfh_a.deleted()); |
| EXPECT_TRUE(delete_observer_rfh_b.deleted()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, |
| DisableBackForwardEvictsIfAlreadyInCache) { |
| base::HistogramTester histogram_tester; |
| |
| 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")); |
| const url::Origin origin_a = url::Origin::Create(url_a); |
| const 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->is_in_back_forward_cache()); |
| EXPECT_FALSE(rfh_a->is_evicted_from_back_forward_cache()); |
| |
| web_contents() |
| ->GetController() |
| .GetBackForwardCache() |
| .DisableForRenderFrameHost(rfh_a->GetGlobalFrameRoutingId(), |
| "DisabledByBackForwardCacheBrowserTest"); |
| |
| EXPECT_TRUE(rfh_a->is_evicted_from_back_forward_cache()); |
| delete_observer_rfh_a.WaitUntilDeleted(); |
| |
| // 3) Go back to A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectDisabledWithReason("DisabledByBackForwardCacheBrowserTest", 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()); |
| const GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html")); |
| const GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html")); |
| const 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(ExecJs(shell(), JsReplace("location = $1", url_b2.spec()))); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| |
| // 4) Go back to B. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcomeIsEmpty(FROM_HERE); |
| |
| // 5) Navigate to A. |
| EXPECT_TRUE(NavigateToURL(shell(), url_a)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| ExpectOutcomeIsEmpty(FROM_HERE); |
| } |
| |
| // Test for functionality of domain specific controls in back-forward cache. |
| class BackForwardCacheBrowserTestWithDomainControlEnabled |
| : public BackForwardCacheBrowserTest { |
| protected: |
| base::FieldTrialParams GetFeatureParams() override { |
| // Sets the allowed websites for testing, additionally adding the params |
| // used by BackForwardCacheBrowserTest. |
| std::map<std::string, std::string> domain_control_params = { |
| {"allowed_websites", |
| "https://a.allowed/back_forward_cache/, " |
| "https://b.allowed/back_forward_cache/allowed_path.html"}}; |
| std::map<std::string, std::string> browser_test_params = |
| BackForwardCacheBrowserTest::GetFeatureParams(); |
| domain_control_params.insert(browser_test_params.begin(), |
| browser_test_params.end()); |
| return domain_control_params; |
| } |
| }; |
| |
| // 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()); |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.allowed", "/back_forward_cache/allowed_path.html")); |
| const 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->is_in_back_forward_cache()); |
| |
| // 4) Now go back to the last stored page, which in our case should be A. |
| web_contents()->GetController().GoBack(); |
| EXPECT_TRUE(WaitForLoadStop(shell()->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->is_in_back_forward_cache()); |
| } |
| |
| // 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) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const GURL url_a(embedded_test_server()->GetURL( |
| "a.disallowed", "/back_forward_cache/disallowed_path.html")); |
| const GURL url_b(embedded_test_server()->GetURL( |
| "b.allowed", "/back_forward_cache/disallowed_path.html")); |
| const 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(); |
| } |
| } // namespace content |