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(