Guard against pending entry deletion for bfcache activations.

A recent optimization in r1460465 removed a duplicate NavigationEntry
for bfcache activations, which accidentally made it possible for the
pending entry to be deleted during a call to Navigator::Navigate.

Most Navigator::Navigate calls that involve the pending entry already
create their own PendingEntryRef to protect against this. This CL uses a
new scoped object, ScopedPendingEntryReentrancyGuard, to more
consistently protect against this and re-entrant navigations in all
navigations to pending entries.

Bug: 40353566, 417251428
Change-Id: I6d15163f9b20a678c7011d2a41ba30f11d00b1b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6557445
Reviewed-by: Alex Moshchuk <[email protected]>
Commit-Queue: Charlie Reis <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1462545}
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index 6e0a9f8d..db53193 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -593,6 +593,33 @@
   controller_->PendingEntryRefDeleted(this);
 }
 
+// NavigationControllerImpl::ScopedPendingEntryReentrancyGuard------------------
+
+NavigationControllerImpl::ScopedPendingEntryReentrancyGuard::
+    ScopedPendingEntryReentrancyGuard(
+        base::SafeRef<NavigationControllerImpl> controller)
+    : controller_(controller) {
+  // Navigations that involve pending entries do not support re-entrancy.
+  // Encountering this CHECK failure means the caller is attempting to start
+  // another navigation while a navigation to an existing pending entry is on
+  // the stack. In most cases, the new navigation should be done from a posted
+  // task instead. See http://crbug.com/40353566 for context.
+  CHECK(!controller->in_navigate_to_pending_entry_);
+
+  controller->in_navigate_to_pending_entry_ = true;
+  controller->CheckPotentialNavigationReentrancy();
+
+  // It must not be possible to delete the pending NavigationEntry while
+  // navigating to it. Grab a reference to delay potential deletion until
+  // this scoped object is deleted at the end of the relevant function call.
+  pending_entry_ref_ = controller->ReferencePendingEntry();
+}
+
+NavigationControllerImpl::ScopedPendingEntryReentrancyGuard::
+    ~ScopedPendingEntryReentrancyGuard() {
+  controller_->in_navigate_to_pending_entry_ = false;
+}
+
 // NavigationControllerImpl ----------------------------------------------------
 
 const size_t kMaxEntryCountForTestingNotSet = static_cast<size_t>(-1);
@@ -3344,13 +3371,16 @@
       CHECK_EQ(different_document_loads.size(), 1u);
       CHECK(same_document_loads.empty());
       request = different_document_loads.at(0)->GetWeakPtr();
-      // Extend the life time of the pending NavigationEntry since the following
-      // Navigator::Navigate() might clear the pending NavigationEntry, and hit
-      // the CHECK_EQ() in the bottom of Navigator::Navigate().
-      std::unique_ptr<PendingEntryRef> pending_entry_ref =
-          ReferencePendingEntry();
+
+      // Ensure that no re-entrant calls or discards of the pending entry occur
+      // while calling `Navigator::Navigate` for a pending entry.
+      ScopedPendingEntryReentrancyGuard reentrancy_guard(
+          weak_factory_.GetSafeRef());
+
       root->navigator().Navigate(std::move(different_document_loads.at(0)),
                                  ReloadType::NONE);
+
+      // `reentrancy_guard` deleted here.
     } else {
       // The legacy approach creates a new NavigationRequest for the entry and
       // discards any previously created NavigationRequests, even though the new
@@ -3440,10 +3470,10 @@
   SCOPED_CRASH_KEY_NUMBER("nav_reentrancy", "pending_reload_type",
                           (int)pending_reload_);
 
-  // This call does not support re-entrancy.  See http://crbug.com/347742.
-  CHECK(!in_navigate_to_pending_entry_);
-  in_navigate_to_pending_entry_ = true;
-  CheckPotentialNavigationReentrancy();
+  // Ensure that no re-entrant calls or discards of the pending entry occur
+  // while calling `Navigator::Navigate` for a pending entry.
+  ScopedPendingEntryReentrancyGuard reentrancy_guard(
+      weak_factory_.GetSafeRef());
 
   // If the navigation-reentrancy is caused by calling
   // NavigateToExistingPendingEntry twice, this will note the previous call's
@@ -3451,11 +3481,6 @@
   SCOPED_CRASH_KEY_NUMBER("nav_reentrancy", "prev_pending_entry_id",
                           pending_entry_ ? pending_entry_->GetUniqueID() : -1);
 
-  // It is not possible to delete the pending NavigationEntry while navigating
-  // to it. Grab a reference to delay potential deletion until the end of this
-  // function.
-  std::unique_ptr<PendingEntryRef> pending_entry_ref = ReferencePendingEntry();
-
   // If there is a main-frame same-document history navigation, we may defer
   // the subframe history navigations in order to give JS in the main frame the
   // opportunity to cancel the entire traverse via the navigate event. In that
@@ -3520,9 +3545,8 @@
     }
   }
 
-  in_navigate_to_pending_entry_ = false;
-
   return all_requests;
+  // `reentrancy_guard` deleted here.
 }
 
 NavigationControllerImpl::HistoryNavigationAction
@@ -3853,22 +3877,17 @@
   ValidateRequestMatchesEntry(request.get(), pending_entry_);
 #endif
 
-  // This call does not support re-entrancy.  See http://crbug.com/347742.
-  CHECK(!in_navigate_to_pending_entry_);
-  in_navigate_to_pending_entry_ = true;
-  CheckPotentialNavigationReentrancy();
-
-  // It is not possible to delete the pending NavigationEntry while navigating
-  // to it. Grab a reference to delay potential deletion until the end of this
-  // function.
-  std::unique_ptr<PendingEntryRef> pending_entry_ref = ReferencePendingEntry();
+  // Ensure that no re-entrant calls or discards of the pending entry occur
+  // while calling `Navigator::Navigate` for a pending entry.
+  ScopedPendingEntryReentrancyGuard reentrancy_guard(
+      weak_factory_.GetSafeRef());
 
   base::WeakPtr<NavigationHandle> created_navigation_handle(
       request->GetWeakPtr());
   node->navigator().Navigate(std::move(request), reload_type);
 
-  in_navigate_to_pending_entry_ = false;
   return created_navigation_handle;
+  // `reentrancy_guard` deleted here.
 }
 
 FrameTreeNode* NavigationControllerImpl::GetTargetFrameTreeNodeForNavigation(