Prerender: Add first implementation of prerender-in-new-tab mode

This CL adds first end-to-end implementation of the prerender-in-new-tab
mode behind a new feature flag (kPrerender2InNewTab). The mode is
available via "target_hint" parameter specified on speculation rules
(this part was implemented by the previous CL).

Also, this CL adds browser tests to make sure the behavior in basic
cases. Follow-up CLs will add more tests (including WPTs) to cover edge
cases.

> Implementation Design

See the design document for details:
https://docs.google.com/document/d/1ZSwrsBtwgxO8xtgX0hA23YR92H62Y0o60ByVO352RL4/edit?usp=sharing

This CL can roughly be divided into 2 parts: triggering part and
activation part.

1) Triggering
When "target_hint: _blank" is specified on speculation rules,
PrerenderHostRegistry creates PrerenderNewTabHandle. The handle is
responsible for creating/owning a new WebContents (a.k.a. pre-created
WebContents) to prerender a page for a new tab. The handles asks another
PrerenderHostRegistry associated with the pre-created WebContents to
start prerendering. The pre-created WebContents will have a blank
primary main frame (i.e., sitting at an initial empty document), and the
prerendered page will be in a separate RenderFrameHost in kPrerendering
state. The handle is retained by PrerenderHostRegistry on the triggering
WebContents (not the pre-created WebContents) so that the registry can
handle cancellation requests, for example, initiated by removal of the
speculation rules in the trigger page.

A notable point in this part is that the pre-created WebContents has a
tentative WebContentsDelegate during prerendering. This will be swapped
with a proper one during activation. This CL implements only the basic
functions of the delegate as a first step.

2) Activation
When a request to open a new tab/window comes from the trigger page,
the browser checks if there is a pre-created WebContents for navigation.
If it exists, the browser reuses the pre-created WebContents to handle
the request instead of creating a new WebContents. Then, the browser
starts the navigation sequence in the pre-created WebContents and run
prerender activation as usual.

> TODOs for follow-up CLs

Following things will be addressed by follow-up CLs:

- Support Precog for the prerender-in-new-tab mode.
- Support Extensions for the prerender-in-new-tab mode.
- Flesh out PrerenderNewTabHandle::WebContentsDelegateImpl and add test
  cases.

Change-Id: I3f3c7111b1da7a08ef4493ef4eeb23479c257d87
Bug: 1350676
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3890670
Reviewed-by: Asami Doi <asamidoi@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Reviewed-by: Lingqi Chi <lingqi@chromium.org>
Commit-Queue: Hiroki Nakagawa <nhiroki@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1075500}
diff --git a/content/browser/preloading/prerender/prerender_host_registry.cc b/content/browser/preloading/prerender/prerender_host_registry.cc
index bf44d020..f10fc76a 100644
--- a/content/browser/preloading/prerender/prerender_host_registry.cc
+++ b/content/browser/preloading/prerender/prerender_host_registry.cc
@@ -24,6 +24,7 @@
 #include "content/browser/preloading/prerender/prerender_final_status.h"
 #include "content/browser/preloading/prerender/prerender_metrics.h"
 #include "content/browser/preloading/prerender/prerender_navigation_utils.h"
+#include "content/browser/preloading/prerender/prerender_new_tab_handle.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -305,6 +306,28 @@
   return frame_tree_node_id;
 }
 
+int PrerenderHostRegistry::CreateAndStartHostForNewTab(
+    const PrerenderAttributes& attributes,
+    WebContents& web_contents) {
+  DCHECK(base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab));
+  std::string recorded_url =
+      attributes.initiator_origin.has_value()
+          ? attributes.initiator_origin.value().GetURL().spec()
+          : "(empty_url)";
+  TRACE_EVENT2("navigation",
+               "PrerenderHostRegistry::CreateAndStartHostForNewTab",
+               "attributes", attributes, "initiator_origin", recorded_url);
+
+  auto handle = std::make_unique<PrerenderNewTabHandle>(
+      attributes, *web_contents.GetBrowserContext());
+  int prerender_host_id = handle->StartPrerendering();
+  if (prerender_host_id == RenderFrameHost::kNoFrameTreeNodeId)
+    return RenderFrameHost::kNoFrameTreeNodeId;
+  prerender_new_tab_handle_by_frame_tree_node_id_[prerender_host_id] =
+      std::move(handle);
+  return prerender_host_id;
+}
+
 int PrerenderHostRegistry::StartPrerendering(int frame_tree_node_id) {
   if (frame_tree_node_id == RenderFrameHost::kNoFrameTreeNodeId) {
     DCHECK(base::FeatureList::IsEnabled(
@@ -387,21 +410,37 @@
 
   for (int host_id : frame_tree_node_ids) {
     // Look up the id in the non-reserved host map.
-    auto iter = prerender_host_by_frame_tree_node_id_.find(host_id);
-    if (iter == prerender_host_by_frame_tree_node_id_.end())
-      continue;
+    if (auto iter = prerender_host_by_frame_tree_node_id_.find(host_id);
+        iter != prerender_host_by_frame_tree_node_id_.end()) {
+      if (running_prerender_host_id_ == host_id)
+        running_prerender_host_id_ = RenderFrameHost::kNoFrameTreeNodeId;
 
-    if (running_prerender_host_id_ == host_id)
-      running_prerender_host_id_ = RenderFrameHost::kNoFrameTreeNodeId;
+      // Remove the prerender host from the host map so that it's not used for
+      // activation during asynchronous deletion.
+      std::unique_ptr<PrerenderHost> prerender_host = std::move(iter->second);
+      prerender_host_by_frame_tree_node_id_.erase(iter);
 
-    // Remove the prerender host from the host map so that it's not used for
-    // activation during asynchronous deletion.
-    std::unique_ptr<PrerenderHost> prerender_host = std::move(iter->second);
-    prerender_host_by_frame_tree_node_id_.erase(iter);
+      // Asynchronously delete the prerender host.
+      ScheduleToDeleteAbandonedHost(std::move(prerender_host),
+                                    PrerenderCancellationReason(final_status));
+    }
 
-    // Asynchronously delete the prerender host.
-    ScheduleToDeleteAbandonedHost(std::move(prerender_host),
-                                  PrerenderCancellationReason(final_status));
+    if (base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab)) {
+      // Look up the id in the prerender-in-new-tab handle map.
+      if (auto iter =
+              prerender_new_tab_handle_by_frame_tree_node_id_.find(host_id);
+          iter != prerender_new_tab_handle_by_frame_tree_node_id_.end()) {
+        // The host should be driven by PrerenderHostRegistry associated with
+        // the new tab.
+        DCHECK_NE(running_prerender_host_id_, host_id);
+
+        std::unique_ptr<PrerenderNewTabHandle> handle = std::move(iter->second);
+        prerender_new_tab_handle_by_frame_tree_node_id_.erase(iter);
+        handle->CancelPrerendering(final_status);
+      }
+    } else {
+      DCHECK(prerender_new_tab_handle_by_frame_tree_node_id_.empty());
+    }
   }
 
   // Start another prerender if the running prerender is cancelled.
@@ -429,29 +468,42 @@
 
   // Look up the id in the non-reserved host map, remove it from the map, and
   // record the cancellation reason.
-  auto iter = prerender_host_by_frame_tree_node_id_.find(frame_tree_node_id);
-  if (iter == prerender_host_by_frame_tree_node_id_.end())
-    return false;
+  if (auto iter =
+          prerender_host_by_frame_tree_node_id_.find(frame_tree_node_id);
+      iter != prerender_host_by_frame_tree_node_id_.end()) {
+    std::unique_ptr<PrerenderHost> prerender_host = std::move(iter->second);
+    prerender_host_by_frame_tree_node_id_.erase(iter);
 
-  reason.ReportMetrics(iter->second->trigger_type(),
-                       iter->second->embedder_histogram_suffix());
+    reason.ReportMetrics(prerender_host->trigger_type(),
+                         prerender_host->embedder_histogram_suffix());
 
-  // Remove the prerender host from the host map so that it's not used for
-  // activation during asynchronous deletion.
+    // Asynchronously delete the prerender host.
+    ScheduleToDeleteAbandonedHost(std::move(prerender_host), reason);
 
-  std::unique_ptr<PrerenderHost> prerender_host = std::move(iter->second);
-  prerender_host_by_frame_tree_node_id_.erase(iter);
+    // Start another prerender if the running prerender is cancelled.
+    if (running_prerender_host_id_ == frame_tree_node_id) {
+      running_prerender_host_id_ = RenderFrameHost::kNoFrameTreeNodeId;
+      StartPrerendering(RenderFrameHost::kNoFrameTreeNodeId);
+    }
 
-  // Asynchronously delete the prerender host.
-  ScheduleToDeleteAbandonedHost(std::move(prerender_host), reason);
-
-  // Start another prerender if the running prerender is cancelled.
-  if (running_prerender_host_id_ == frame_tree_node_id) {
-    running_prerender_host_id_ = RenderFrameHost::kNoFrameTreeNodeId;
-    StartPrerendering(RenderFrameHost::kNoFrameTreeNodeId);
+    return true;
   }
 
-  return true;
+  if (base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab)) {
+    // Look up the id in the prerender new-tab handle map, remove it from the
+    // map, and record the cancellation reason.
+    if (auto iter = prerender_new_tab_handle_by_frame_tree_node_id_.find(
+            frame_tree_node_id);
+        iter != prerender_new_tab_handle_by_frame_tree_node_id_.end()) {
+      iter->second->CancelPrerendering(reason.final_status());
+      prerender_new_tab_handle_by_frame_tree_node_id_.erase(iter);
+      return true;
+    }
+  } else {
+    DCHECK(prerender_new_tab_handle_by_frame_tree_node_id_.empty());
+  }
+
+  return false;
 }
 
 void PrerenderHostRegistry::CancelAllHosts(PrerenderFinalStatus final_status) {
@@ -465,6 +517,11 @@
                                   PrerenderCancellationReason(final_status));
   }
 
+  auto prerender_new_tab_handle_map =
+      std::move(prerender_new_tab_handle_by_frame_tree_node_id_);
+  for (auto& iter : prerender_new_tab_handle_map)
+    iter.second->CancelPrerendering(final_status);
+
   pending_prerenders_.clear();
 }
 
@@ -568,6 +625,32 @@
   return reserved_prerender_host_.get();
 }
 
+std::unique_ptr<WebContentsImpl>
+PrerenderHostRegistry::TakePreCreatedWebContentsForNewTabIfExists(
+    const mojom::CreateNewWindowParams& create_new_window_params,
+    const WebContents::CreateParams& web_contents_create_params) {
+  DCHECK(base::FeatureList::IsEnabled(blink::features::kPrerender2InNewTab));
+
+  // Don't serve a prerendered page if the window needs the opener or is created
+  // for non-regular navigations.
+  if (!create_new_window_params.opener_suppressed ||
+      create_new_window_params.is_form_submission ||
+      create_new_window_params.pip_options) {
+    return nullptr;
+  }
+
+  for (auto& iter : prerender_new_tab_handle_by_frame_tree_node_id_) {
+    std::unique_ptr<WebContentsImpl> web_contents =
+        iter.second->TakeWebContentsIfAvailable(create_new_window_params,
+                                                web_contents_create_params);
+    if (web_contents) {
+      prerender_new_tab_handle_by_frame_tree_node_id_.erase(iter);
+      return web_contents;
+    }
+  }
+  return nullptr;
+}
+
 std::vector<FrameTree*> PrerenderHostRegistry::GetPrerenderFrameTrees() {
   std::vector<FrameTree*> result;
   for (auto& i : prerender_host_by_frame_tree_node_id_) {
@@ -585,6 +668,11 @@
     if (iter.second->GetInitialUrl() == prerendering_url)
       return iter.second.get();
   }
+  for (auto& iter : prerender_new_tab_handle_by_frame_tree_node_id_) {
+    PrerenderHost* host = iter.second->GetPrerenderHostForTesting();  // IN-TEST
+    if (host && host->GetInitialUrl() == prerendering_url)
+      return host;
+  }
   return nullptr;
 }
 
@@ -778,6 +866,10 @@
       cancelled_prerenders.push_back(host_id);
     }
   }
+  for (const auto& [host_id, _] :
+       prerender_new_tab_handle_by_frame_tree_node_id_) {
+    cancelled_prerenders.push_back(host_id);
+  }
   CancelHosts(cancelled_prerenders, PrerenderFinalStatus::kTriggerDestroyed);
   pending_prerenders_.clear();
 
@@ -828,6 +920,8 @@
     if (host_by_id.second->trigger_type() == trigger_type)
       ++trigger_type_count;
   }
+  // TODO(crbug.com/1350676): Make this function care about
+  // `prerender_new_tab_handle_by_frame_tree_node_id_` as well.
 
   switch (trigger_type) {
     case PrerenderTriggerType::kSpeculationRule: