Initial BackForwardCache implementation.
− Add the BackForwardCache feature. Disabled by default.
− On cross-process navigations, keep the old RenderFrameHost. Store it in a
|BackForwardCache| object.
− On history navigation, find the previous document in the BFCache and swap it
back if possible.
− The BackForwardCache works as a least recently used cache.
A basic cache eviction is implemented when it is full.
Major things that aren't supported yet:
- Freezing the frame in the BackForwardCache. They are still able to execute
JavaScript in the background. They can still interact using IPCs with the
browser process and causes crashes.
- Removing a RenderFrameHost from the BackForwardCache cleanly.
- Handling same-process navigations.
Bug: 914321
Change-Id: I1b83fe59dc4868b5a6093738d2f5c65f67d60a78
Reviewed-on: https://chromium-review.googlesource.com/c/1358434
Commit-Queue: Arthur Sonzogni <[email protected]>
Reviewed-by: Alex Moshchuk <[email protected]>
Reviewed-by: Camille Lamy <[email protected]>
Cr-Commit-Position: refs/heads/master@{#616284}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 79884b64..a969923 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -837,6 +837,8 @@
"font_list_async.cc",
"frame_host/ancestor_throttle.cc",
"frame_host/ancestor_throttle.h",
+ "frame_host/back_forward_cache.cc",
+ "frame_host/back_forward_cache.h",
"frame_host/blocked_scheme_navigation_throttle.cc",
"frame_host/blocked_scheme_navigation_throttle.h",
"frame_host/cross_process_frame_connector.cc",
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
new file mode 100644
index 0000000..8e426c5
--- /dev/null
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -0,0 +1,195 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/command_line.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/frame_host/back_forward_cache.h"
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/common/content_features.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/browser/shell.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+namespace content {
+
+namespace {
+
+// Test about the BackForwardCache.
+class BackForwardCacheBrowserTest : public ContentBrowserTest {
+ protected:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ feature_list_.InitAndEnableFeature(features::kBackForwardCache);
+ }
+
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ }
+
+ WebContentsImpl* web_contents() const {
+ return static_cast<WebContentsImpl*>(shell()->web_contents());
+ }
+
+ RenderFrameHostImpl* current_frame_host() {
+ return web_contents()->GetFrameTree()->root()->current_frame_host();
+ }
+
+ private:
+ base::test::ScopedFeatureList feature_list_;
+};
+
+} // namespace
+
+// Navigate from A to B and go back.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, Basic) {
+ 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.
+ NavigateToURL(shell(), url_a);
+ RenderFrameHostImpl* rfh_a = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_a(rfh_a);
+
+ // 2) Navigate to B.
+ NavigateToURL(shell(), url_b);
+ RenderFrameHostImpl* rfh_b = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_b(rfh_b);
+ EXPECT_FALSE(delete_rfh_a.deleted());
+ EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
+ EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
+
+ // 3) Go back to A.
+ web_contents()->GetController().GoBack();
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_FALSE(delete_rfh_a.deleted());
+ EXPECT_FALSE(delete_rfh_b.deleted());
+ EXPECT_EQ(rfh_a, current_frame_host());
+ EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
+ EXPECT_TRUE(rfh_b->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());
+ 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).
+ NavigateToURL(shell(), url_a);
+ RenderFrameHostImpl* rfh_a = current_frame_host();
+ RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_a(rfh_a);
+ RenderFrameDeletedObserver delete_rfh_b(rfh_b);
+
+ // 2) Navigate to C.
+ NavigateToURL(shell(), url_c);
+ RenderFrameHostImpl* rfh_c = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_c(rfh_c);
+ EXPECT_FALSE(delete_rfh_a.deleted());
+ EXPECT_FALSE(delete_rfh_b.deleted());
+ EXPECT_TRUE(rfh_a->is_in_back_forward_cache());
+ EXPECT_TRUE(rfh_b->is_in_back_forward_cache());
+ EXPECT_FALSE(rfh_c->is_in_back_forward_cache());
+
+ // 3) Go back to A(B).
+ web_contents()->GetController().GoBack();
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_FALSE(delete_rfh_a.deleted());
+ EXPECT_FALSE(delete_rfh_b.deleted());
+ EXPECT_FALSE(delete_rfh_c.deleted());
+ EXPECT_EQ(rfh_a, current_frame_host());
+ EXPECT_FALSE(rfh_a->is_in_back_forward_cache());
+ EXPECT_FALSE(rfh_b->is_in_back_forward_cache());
+ EXPECT_TRUE(rfh_c->is_in_back_forward_cache());
+}
+
+// Ensure flushing the BackForwardCache works properly.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, BackForwardCacheFlush) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ 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.
+ NavigateToURL(shell(), url_a);
+ RenderFrameHostImpl* rfh_a = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_a(rfh_a);
+
+ // 2) Navigate to B.
+ NavigateToURL(shell(), url_b);
+ RenderFrameHostImpl* rfh_b = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_b(rfh_b);
+ EXPECT_FALSE(delete_rfh_a.deleted());
+
+ // 3) Flush A.
+ web_contents()->GetController().back_forward_cache().Flush();
+ EXPECT_TRUE(delete_rfh_a.deleted());
+ EXPECT_FALSE(delete_rfh_b.deleted());
+
+ // 4) Go back to a new A.
+ web_contents()->GetController().GoBack();
+ EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+ EXPECT_TRUE(delete_rfh_a.deleted());
+ EXPECT_FALSE(delete_rfh_b.deleted());
+
+ // 5) Flush B.
+ web_contents()->GetController().back_forward_cache().Flush();
+ EXPECT_TRUE(delete_rfh_b.deleted());
+}
+
+// Check the visible URL in the omnibox is properly updated when restoring a
+// document from the BackForwardCache.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, VisibleURL) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+ GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+ // 1) Go to A.
+ NavigateToURL(shell(), url_a);
+
+ // 2) Go to B.
+ NavigateToURL(shell(), url_b);
+
+ // 3) Go back to A.
+ web_contents()->GetController().GoBack();
+ EXPECT_EQ(url_a, web_contents()->GetVisibleURL());
+
+ // 4) Go forward to B.
+ web_contents()->GetController().GoForward();
+ EXPECT_EQ(url_b, web_contents()->GetVisibleURL());
+}
+
+// Test documents are evicted from the BackForwardCache at some point.
+IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, CacheEviction) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
+ GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
+
+ NavigateToURL(shell(), url_a); // BackForwardCache size is 0.
+ RenderFrameHostImpl* rfh_a = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_a(rfh_a);
+
+ NavigateToURL(shell(), url_b); // BackForwardCache size is 1.
+ RenderFrameHostImpl* rfh_b = current_frame_host();
+ RenderFrameDeletedObserver delete_rfh_b(rfh_b);
+
+ // The number of document the BackForwardCache can hold per tab.
+ static constexpr size_t kBackForwardCacheLimit = 3;
+
+ for (size_t i = 2; i < kBackForwardCacheLimit; ++i) {
+ NavigateToURL(shell(), i % 2 ? url_b : url_a);
+ // After |i+1| navigations, |i| documents went into the BackForwardCache.
+ // When |i| is greater than the BackForwardCache size limit, they are
+ // evicted:
+ EXPECT_EQ(i >= kBackForwardCacheLimit + 1, delete_rfh_a.deleted());
+ EXPECT_EQ(i >= kBackForwardCacheLimit + 2, delete_rfh_b.deleted());
+ }
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/back_forward_cache.cc b/content/browser/frame_host/back_forward_cache.cc
new file mode 100644
index 0000000..3c27c01
--- /dev/null
+++ b/content/browser/frame_host/back_forward_cache.cc
@@ -0,0 +1,80 @@
+// 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 "content/browser/frame_host/back_forward_cache.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/public/common/navigation_policy.h"
+
+namespace content {
+
+namespace {
+
+// The number of document the BackForwardCache can hold per tab.
+static constexpr size_t kBackForwardCacheLimit = 3;
+
+} // namespace
+
+BackForwardCache::BackForwardCache() = default;
+BackForwardCache::~BackForwardCache() = default;
+
+bool BackForwardCache::CanStoreDocument(RenderFrameHostImpl* rfh) {
+ // Use the BackForwardCache only for the main frame.
+ if (rfh->GetParent())
+ return false;
+
+ if (!IsBackForwardCacheEnabled())
+ return false;
+
+ // TODO(arthursonzogni): In a lot of other cases, a document must not be in
+ // the BackForwardCache. The main frame needs to be checked, but also its
+ // iframes.
+ // * Document using plugin.
+ // * Document not fully loaded.
+ // * Document with unload handlers.
+ // * Error pages.
+ // * AppCache?
+ // * ...
+
+ return true;
+}
+
+void BackForwardCache::StoreDocument(std::unique_ptr<RenderFrameHostImpl> rfh) {
+ DCHECK(CanStoreDocument(rfh.get()));
+
+ rfh->EnterBackForwardCache();
+ render_frame_hosts_.push_front(std::move(rfh));
+
+ // Remove the last recently used document if the BackForwardCache list is
+ // full.
+ if (render_frame_hosts_.size() > kBackForwardCacheLimit) {
+ // TODO(arthursonzogni): Handle RenderFrame deletion appropriately.
+ render_frame_hosts_.pop_back();
+ }
+}
+
+std::unique_ptr<RenderFrameHostImpl> BackForwardCache::RestoreDocument(
+ int navigation_entry_id) {
+ // Select the RenderFrameHostImpl matching the navigation entry.
+ auto matching_rfh = std::find_if(
+ render_frame_hosts_.begin(), render_frame_hosts_.end(),
+ [navigation_entry_id](std::unique_ptr<RenderFrameHostImpl>& rfh) {
+ return rfh->nav_entry_id() == navigation_entry_id;
+ });
+
+ // Not found.
+ if (matching_rfh == render_frame_hosts_.end())
+ return nullptr;
+
+ std::unique_ptr<RenderFrameHostImpl> rfh = std::move(*matching_rfh);
+ render_frame_hosts_.erase(matching_rfh);
+ rfh->LeaveBackForwardCache();
+ return rfh;
+}
+
+// Remove all entries from the BackForwardCache.
+void BackForwardCache::Flush() {
+ render_frame_hosts_.clear();
+}
+
+} // namespace content
diff --git a/content/browser/frame_host/back_forward_cache.h b/content/browser/frame_host/back_forward_cache.h
new file mode 100644
index 0000000..0617523
--- /dev/null
+++ b/content/browser/frame_host/back_forward_cache.h
@@ -0,0 +1,58 @@
+// 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.
+
+#ifndef CONTENT_BROWSER_FRAME_HOST_BACK_FORWARD_CACHE_H_
+#define CONTENT_BROWSER_FRAME_HOST_BACK_FORWARD_CACHE_H_
+
+#include <list>
+#include <memory>
+
+#include "base/macros.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+class RenderFrameHostImpl;
+
+// BackForwardCache:
+//
+// After the user navigates away from a document, the old one goes into the
+// frozen state and is kept in this object. They can potentially be reused
+// after an history navigation. Reusing a document means swapping it back with
+// the current_frame_host.
+class CONTENT_EXPORT BackForwardCache {
+ public:
+ BackForwardCache();
+ ~BackForwardCache();
+
+ // Returns true when a RenderFrameHost can be stored into the
+ // BackForwardCache. Depends on the |render_frame_host| and its children's
+ // state.
+ bool CanStoreDocument(RenderFrameHostImpl* render_frame_host);
+
+ // Moves |render_frame_host| into the BackForwardCache. It can be reused in
+ // a future history navigation by using RestoreDocument(). When the
+ // BackForwardCache is full, the least recently used document is evicted.
+ // Precondition: CanStoreDocument(render_frame_host).
+ void StoreDocument(std::unique_ptr<RenderFrameHostImpl>);
+
+ // During a history navigation, move a document out of the BackForwardCache
+ // knowing its navigation entry ID. Returns nullptr when none is found.
+ std::unique_ptr<RenderFrameHostImpl> RestoreDocument(int navigation_entry_id);
+
+ // Remove all entries from the BackForwardCache.
+ void Flush();
+
+ private:
+ // Contains the set of stored RenderFrameHost.
+ // Invariant:
+ // - Ordered from the most recently used to the last recently used.
+ // - Once the list is full, the least recently used document is evicted.
+ std::list<std::unique_ptr<RenderFrameHostImpl>> render_frame_hosts_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackForwardCache);
+};
+} // namespace content
+
+#endif // CONTENT_BROWSER_FRAME_HOST_BACK_FORWARD_CACHE_H_
diff --git a/content/browser/frame_host/navigation_controller_impl.cc b/content/browser/frame_host/navigation_controller_impl.cc
index 4bfa4500..f3512e10 100644
--- a/content/browser/frame_host/navigation_controller_impl.cc
+++ b/content/browser/frame_host/navigation_controller_impl.cc
@@ -81,6 +81,7 @@
#include "content/public/common/url_utils.h"
#include "media/base/mime_util.h"
#include "net/base/escape.h"
+#include "net/http/http_status_code.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/blink/public/common/blob/blob_utils.h"
#include "third_party/blink/public/common/mime_util/mime_util.h"
@@ -2380,6 +2381,16 @@
DCHECK(IsInitialNavigation() || pending_entry_index_ != -1);
DCHECK(!IsRendererDebugURL(pending_entry_->GetURL()));
needs_reload_ = false;
+ FrameTreeNode* root = delegate_->GetFrameTree()->root();
+ int nav_entry_id = pending_entry_->GetUniqueID();
+
+ // BackForwardCache:
+ // Try to restore a document from the BackForwardCache.
+ if (auto rfh = back_forward_cache_.RestoreDocument(nav_entry_id)) {
+ root->render_manager()->RestoreFromBackForwardCache(std::move(rfh));
+ CommitRestoreFromBackForwardCache();
+ return;
+ }
// If we were navigating to a slow-to-commit page, and the user performs
// a session history navigation to the last committed page, RenderViewHost
@@ -2409,8 +2420,6 @@
return;
}
- FrameTreeNode* root = delegate_->GetFrameTree()->root();
-
// Compare FrameNavigationEntries to see which frames in the tree need to be
// navigated.
std::vector<std::unique_ptr<NavigationRequest>> same_document_loads;
@@ -3152,4 +3161,25 @@
get_timestamp_callback_ = get_timestamp_callback;
}
+// BackForwardCache:
+void NavigationControllerImpl::CommitRestoreFromBackForwardCache() {
+ // TODO(arthursonzogni): Extract the missing parts from RendererDidNavigate()
+ // and reuse them.
+ LoadCommittedDetails details;
+ details.previous_entry_index = GetCurrentEntryIndex();
+ details.entry = pending_entry_;
+ details.type = NAVIGATION_TYPE_EXISTING_PAGE;
+ details.is_main_frame = true;
+ details.http_status_code = net::HTTP_OK;
+ details.did_replace_entry = false;
+ details.is_same_document = false;
+
+ last_committed_entry_index_ = pending_entry_index_;
+ DiscardPendingEntry(false);
+
+ // Notify content/ embedder of the history update.
+ delegate_->NotifyNavigationStateChanged(INVALIDATE_TYPE_ALL);
+ delegate_->NotifyNavigationEntryCommitted(details);
+}
+
} // namespace content
diff --git a/content/browser/frame_host/navigation_controller_impl.h b/content/browser/frame_host/navigation_controller_impl.h
index 3e4713f5..130f2b9 100644
--- a/content/browser/frame_host/navigation_controller_impl.h
+++ b/content/browser/frame_host/navigation_controller_impl.h
@@ -16,6 +16,7 @@
#include "base/macros.h"
#include "base/time/time.h"
#include "build/build_config.h"
+#include "content/browser/frame_host/back_forward_cache.h"
#include "content/browser/frame_host/navigation_controller_delegate.h"
#include "content/browser/frame_host/navigation_entry_impl.h"
#include "content/browser/ssl/ssl_manager.h"
@@ -238,6 +239,8 @@
// navigation failed due to an SSL error.
void SetPendingNavigationSSLError(bool error);
+ BackForwardCache& back_forward_cache() { return back_forward_cache_; }
+
// Returns true if the string corresponds to a valid data URL, false
// otherwise.
#if defined(OS_ANDROID)
@@ -442,6 +445,11 @@
// specified |offset|. The index returned is not guaranteed to be valid.
int GetIndexForOffset(int offset) const;
+ // BackForwardCache:
+ // Notify observers a document was restored from the bfcache.
+ // This updates the URL bar and the history buttons.
+ void CommitRestoreFromBackForwardCache();
+
// ---------------------------------------------------------------------------
// The user browser context associated with this controller.
@@ -539,6 +547,12 @@
ReloadType last_committed_reload_type_;
base::Time last_committed_reload_time_;
+ // BackForwardCache:
+ //
+ // Stores frozen RenderFrameHost. Restores them on history navigation.
+ // See BackForwardCache class documentation.
+ BackForwardCache back_forward_cache_;
+
DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
};
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index d7d59b6..e14848a6 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1047,6 +1047,24 @@
delegate_->AudioContextPlaybackStopped(this, audio_context_id);
}
+// The current frame went into the BackForwardCache.
+void RenderFrameHostImpl::EnterBackForwardCache() {
+ DCHECK(IsBackForwardCacheEnabled());
+ DCHECK(!is_in_back_forward_cache_);
+ is_in_back_forward_cache_ = true;
+ for (auto& child : children_)
+ child->current_frame_host()->EnterBackForwardCache();
+}
+
+// The frame as been restored from the BackForwardCache.
+void RenderFrameHostImpl::LeaveBackForwardCache() {
+ DCHECK(IsBackForwardCacheEnabled());
+ DCHECK(is_in_back_forward_cache_);
+ is_in_back_forward_cache_ = false;
+ for (auto& child : children_)
+ child->current_frame_host()->LeaveBackForwardCache();
+}
+
SiteInstanceImpl* RenderFrameHostImpl::GetSiteInstance() {
return site_instance_.get();
}
@@ -2326,7 +2344,7 @@
// If this RenderFrameHost is already pending deletion, it must have already
// gone through this, therefore just return.
- if (!is_active()) {
+ if (unload_state_ != UnloadState::NotRun) {
NOTREACHED() << "RFH should be in default state when calling SwapOut.";
return;
}
@@ -2576,7 +2594,7 @@
// be sent again.
sudden_termination_disabler_types_enabled_ = 0;
- if (!is_active()) {
+ if (unload_state_ != UnloadState::NotRun) {
// If the process has died, we don't need to wait for the ACK. Complete the
// deletion immediately.
unload_state_ = UnloadState::Completed;
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index ffcef57..902644df 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -477,7 +477,10 @@
// This method returns true from the time this RenderFrameHost is created
// until it is pending deletion. Pending deletion starts when SwapOut is
// called on the frame or one of its ancestors.
- bool is_active() { return unload_state_ == UnloadState::NotRun; }
+ // BackForwardCache: Returns false when the frame is in the BackForwardCache.
+ bool is_active() {
+ return unload_state_ == UnloadState::NotRun && !is_in_back_forward_cache_;
+ }
// Navigates to an interstitial page represented by the provided data URL.
void NavigateToInterstitialURL(const GURL& data_url);
@@ -819,6 +822,14 @@
return is_attaching_inner_delegate_;
}
+ // BackForwardCache:
+ //
+ // When a RenderFrameHostImpl enters the BackForwardCache, the document enters
+ // in a "Frozen" state where no Javascript can run.
+ void EnterBackForwardCache(); // The document enters the BackForwardCache.
+ void LeaveBackForwardCache(); // The document leaves the BackForwardCache.
+ bool is_in_back_forward_cache() { return is_in_back_forward_cache_; }
+
protected:
friend class RenderFrameHostFactory;
@@ -1812,6 +1823,9 @@
};
UnloadState unload_state_ = UnloadState::NotRun;
+ // BackForwardCache:
+ bool is_in_back_forward_cache_ = false;
+
// NOTE: This must be the last member.
base::WeakPtrFactory<RenderFrameHostImpl> weak_ptr_factory_;
diff --git a/content/browser/frame_host/render_frame_host_manager.cc b/content/browser/frame_host/render_frame_host_manager.cc
index 18f96df..c15662eb 100644
--- a/content/browser/frame_host/render_frame_host_manager.cc
+++ b/content/browser/frame_host/render_frame_host_manager.cc
@@ -262,7 +262,7 @@
// same-process navigation is also ongoing, it will be canceled when the
// speculative RenderFrameHost replaces the current one in the commit call
// below.
- CommitPending();
+ CommitPending(std::move(speculative_render_frame_host_));
frame_tree_node_->ResetNavigationRequest(false, true);
} else if (render_frame_host == render_frame_host_.get()) {
// A same-process navigation committed while a simultaneous cross-process
@@ -368,6 +368,8 @@
// Tell the renderer to suppress any further modal dialogs so that we can swap
// it out. This must be done before canceling any current dialog, in case
// there is a loop creating additional dialogs.
+ // TODO(arthursonzogni): Undo this for documents restored from the
+ // BackForwardCache.
old_render_frame_host->SuppressFurtherDialogs();
// Now close any modal dialogs that would prevent us from swapping out. This
@@ -380,6 +382,22 @@
if (!old_render_frame_host->IsRenderFrameLive())
return;
+ // Reset any NavigationRequest in the RenderFrameHost. A swapped out
+ // RenderFrameHost should not be trying to commit a navigation.
+ old_render_frame_host->ResetNavigationRequests();
+
+ // BackForwardCache:
+ //
+ // If the old RenderFrameHost can be stored in the BackForwardCache, return
+ // early without swapping out and running unload handlers, as the document may
+ // be restored later.
+ BackForwardCache& back_forward_cache =
+ delegate_->GetControllerForRenderManager().back_forward_cache();
+ if (back_forward_cache.CanStoreDocument(old_render_frame_host.get())) {
+ back_forward_cache.StoreDocument(std::move(old_render_frame_host));
+ return;
+ }
+
// Create a replacement proxy for the old RenderFrameHost. (There should not
// be one yet.) This is done even if there are no active frames besides this
// one to simplify cleanup logic on the renderer side (see
@@ -388,10 +406,6 @@
CreateRenderFrameProxyHost(old_render_frame_host->GetSiteInstance(),
old_render_frame_host->render_view_host());
- // Reset any NavigationRequest in the RenderFrameHost. A swapped out
- // RenderFrameHost should not be trying to commit a navigation.
- old_render_frame_host->ResetNavigationRequests();
-
// Tell the old RenderFrameHost to swap out and be replaced by the proxy.
old_render_frame_host->SwapOut(proxy, true);
@@ -455,6 +469,12 @@
return false;
}
+void RenderFrameHostManager::RestoreFromBackForwardCache(
+ std::unique_ptr<RenderFrameHostImpl> rfh) {
+ rfh->GetProcess()->AddPendingView(); // Matched in CommitPending().
+ CommitPending(std::move(rfh));
+}
+
void RenderFrameHostManager::ResetProxyHosts() {
for (const auto& pair : proxy_hosts_) {
static_cast<SiteInstanceImpl*>(pair.second->GetSiteInstance())
@@ -614,7 +634,7 @@
navigation_rfh->Send(
new FrameMsg_SwapIn(navigation_rfh->GetRoutingID()));
}
- CommitPending();
+ CommitPending(std::move(speculative_render_frame_host_));
// Notify the WebUI about the new RenderFrame if needed (the newly
// created WebUI has just been committed by CommitPending, so
@@ -2116,10 +2136,11 @@
delegate_->SetFocusToLocationBar(false);
}
-void RenderFrameHostManager::CommitPending() {
+void RenderFrameHostManager::CommitPending(
+ std::unique_ptr<RenderFrameHostImpl> pending_rfh) {
TRACE_EVENT1("navigation", "RenderFrameHostManager::CommitPending",
"FrameTreeNode id", frame_tree_node_->frame_tree_node_id());
- DCHECK(speculative_render_frame_host_);
+ DCHECK(pending_rfh);
#if defined(OS_MACOSX)
// The old RenderWidgetHostView will be hidden before the new
@@ -2171,12 +2192,11 @@
navigation_entry);
}
- // Swap in the pending or speculative frame and make it active. Also ensure
- // the FrameTree stays in sync.
+ // Swap in the pending frame and make it active. Also ensure the FrameTree
+ // stays in sync.
+ DCHECK(pending_rfh);
std::unique_ptr<RenderFrameHostImpl> old_render_frame_host;
- DCHECK(speculative_render_frame_host_);
- old_render_frame_host =
- SetRenderFrameHost(std::move(speculative_render_frame_host_));
+ old_render_frame_host = SetRenderFrameHost(std::move(pending_rfh));
// For top-level frames, the RenderWidget{Host} will not be destroyed when the
// local frame is detached. https://crbug.com/419087
diff --git a/content/browser/frame_host/render_frame_host_manager.h b/content/browser/frame_host/render_frame_host_manager.h
index 0ce014d..7b7879bc 100644
--- a/content/browser/frame_host/render_frame_host_manager.h
+++ b/content/browser/frame_host/render_frame_host_manager.h
@@ -317,6 +317,12 @@
// Returns whether it was deleted.
bool DeleteFromPendingList(RenderFrameHostImpl* render_frame_host);
+ // BackForwardCache:
+ // During an history navigation, try to swap back a document from the
+ // BackForwardCache. The document is referenced from its navigation entry ID.
+ // Returns true when it succeed.
+ void RestoreFromBackForwardCache(std::unique_ptr<RenderFrameHostImpl>);
+
// Deletes any proxy hosts associated with this node. Used during destruction
// of WebContentsImpl.
void ResetProxyHosts();
@@ -682,9 +688,10 @@
// when the current RenderFrameHost commits and it has a pending WebUI.
void CommitPendingWebUI();
- // Sets the speculative RenderFrameHost to be the active one. Called when the
- // pending RenderFrameHost commits.
- void CommitPending();
+ // Sets the |pending_rfh| to be the active one. Called when the pending
+ // RenderFrameHost commits.
+ // BackForwardCache: Called to restore a RenderFrameHost.
+ void CommitPending(std::unique_ptr<RenderFrameHostImpl> pending_rfh);
// Helper to call CommitPending() in all necessary cases.
void CommitPendingIfNecessary(RenderFrameHostImpl* render_frame_host,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 9723917..72a0b42 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -676,6 +676,7 @@
// destroyed.
RenderFrameHostManager* root = GetRenderManager();
+ GetController().back_forward_cache().Flush();
root->current_frame_host()->SetRenderFrameCreated(false);
root->current_frame_host()->ResetNavigationRequests();
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 951dc1e..f9d8e96 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -65,6 +65,10 @@
const base::Feature kBackgroundFetchUploads{"BackgroundFetchUploads",
base::FEATURE_DISABLED_BY_DEFAULT};
+// Enable using the BackForwardCache.
+const base::Feature kBackForwardCache{"BackForwardCache",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
// Enable incremental marking for Blink's heap managed by the Oilpan garbage
// collector.
const base::Feature kBlinkHeapIncrementalMarking{
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 65f82bd..0157bc0 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -29,6 +29,7 @@
CONTENT_EXPORT extern const base::Feature kBackgroundFetch;
CONTENT_EXPORT extern const base::Feature kBackgroundFetchAccessActiveFetches;
CONTENT_EXPORT extern const base::Feature kBackgroundFetchUploads;
+CONTENT_EXPORT extern const base::Feature kBackForwardCache;
CONTENT_EXPORT extern const base::Feature kBlinkHeapIncrementalMarking;
CONTENT_EXPORT extern const base::Feature kBlinkHeapUnifiedGarbageCollection;
CONTENT_EXPORT extern const base::Feature kBloatedRendererDetection;
diff --git a/content/public/common/navigation_policy.cc b/content/public/common/navigation_policy.cc
index d02de4e..a8b4e7c 100644
--- a/content/public/common/navigation_policy.cc
+++ b/content/public/common/navigation_policy.cc
@@ -19,4 +19,8 @@
return base::FeatureList::IsEnabled(features::kPerNavigationMojoInterface);
}
+bool IsBackForwardCacheEnabled() {
+ return base::FeatureList::IsEnabled(features::kBackForwardCache);
+}
+
} // namespace content
diff --git a/content/public/common/navigation_policy.h b/content/public/common/navigation_policy.h
index 684ab8e3..9795775 100644
--- a/content/public/common/navigation_policy.h
+++ b/content/public/common/navigation_policy.h
@@ -14,6 +14,7 @@
CONTENT_EXPORT bool IsBrowserSideNavigationEnabled();
CONTENT_EXPORT bool IsPerNavigationMojoInterfaceEnabled();
+CONTENT_EXPORT bool IsBackForwardCacheEnabled();
} // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 93f7de3..e3d1c15 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -737,6 +737,7 @@
"../browser/accessibility/snapshot_ax_tree_browsertest.cc",
"../browser/accessibility/touch_accessibility_aura_browsertest.cc",
"../browser/appcache/appcache_browsertest.cc",
+ "../browser/back_forward_cache_browsertest.cc",
"../browser/background_sync/background_sync_browsertest.cc",
"../browser/battery_monitor_browsertest.cc",
"../browser/blob_storage/blob_storage_browsertest.cc",
diff --git a/content/test/web_contents_observer_sanity_checker.cc b/content/test/web_contents_observer_sanity_checker.cc
index 8c10620..a81e24de 100644
--- a/content/test/web_contents_observer_sanity_checker.cc
+++ b/content/test/web_contents_observer_sanity_checker.cc
@@ -16,6 +16,7 @@
#include "content/public/browser/site_instance.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/navigation_policy.h"
#include "net/base/net_errors.h"
namespace content {
@@ -140,8 +141,14 @@
<< "RenderFrameHostChanged called more than once for routing pair:"
<< Format(new_host);
}
- CHECK(!HasAnyChildren(new_host))
- << "A frame should not have children before it is committed.";
+
+ // If |new_host| is restored from the BackForwardCache, it can contain
+ // iframes, otherwise it has just been created and can't contain iframes for
+ // the moment.
+ if (!IsBackForwardCacheEnabled()) {
+ CHECK(!HasAnyChildren(new_host))
+ << "A frame should not have children before it is committed.";
+ }
}
void WebContentsObserverSanityChecker::FrameDeleted(