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: