blob: 8c763631ed63ee723d506bc17e9be9f2c65a584d [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/preloading/prerender/prerender_page_holder.h"
#include "base/run_loop.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_entry_restore_context_impl.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/site_instance_impl.h"
namespace content {
PrerenderPageHolder::PrerenderPageHolder(WebContentsImpl& web_contents)
: web_contents_(web_contents),
frame_tree_(
std::make_unique<FrameTree>(web_contents.GetBrowserContext(),
this,
this,
&web_contents,
&web_contents,
&web_contents,
&web_contents,
&web_contents,
&web_contents,
FrameTree::Type::kPrerender,
base::UnguessableToken::Create())) {
scoped_refptr<SiteInstance> site_instance =
SiteInstance::Create(web_contents.GetBrowserContext());
frame_tree_->Init(site_instance.get(),
/*renderer_initiated_creation=*/false,
/*main_frame_name=*/"", /*opener_for_origin=*/nullptr,
/*frame_policy=*/blink::FramePolicy());
// Use the same SessionStorageNamespace as the primary page for the
// prerendering page.
frame_tree_->controller().SetSessionStorageNamespace(
site_instance->GetStoragePartitionConfig(),
web_contents_.GetPrimaryFrameTree()
.controller()
.GetSessionStorageNamespace(
site_instance->GetStoragePartitionConfig()));
// TODO(https://crbug.com/1199679): This should be moved to FrameTree::Init
web_contents_.NotifySwappedFromRenderManager(
/*old_frame=*/nullptr,
frame_tree_->root()->render_manager()->current_frame_host());
}
PrerenderPageHolder::~PrerenderPageHolder() {
// If we are still waiting on test loop, we can assume the page loading step
// has been cancelled and the PageHolder is being discarded without
// completing loading the page.
if (on_wait_loading_finished_)
std::move(on_wait_loading_finished_)
.Run(PrerenderHost::LoadingOutcome::kPrerenderingCancelled);
if (frame_tree_)
frame_tree_->Shutdown();
}
void PrerenderPageHolder::DidStopLoading() {
if (on_wait_loading_finished_) {
std::move(on_wait_loading_finished_)
.Run(PrerenderHost::LoadingOutcome::kLoadingCompleted);
}
}
bool PrerenderPageHolder::IsHidden() {
return true;
}
FrameTree* PrerenderPageHolder::LoadingTree() {
// For prerendering loading tree is the same as its frame tree as loading is
// done at a frame tree level in the background, unlike the loading visible
// to the user where we account for nested frame tree loading state.
return frame_tree_.get();
}
int PrerenderPageHolder::GetOuterDelegateFrameTreeNodeId() {
// A prerendered FrameTree is not "inner to" or "nested inside" another
// FrameTree; it exists in parallel to the primary FrameTree of the current
// WebContents. Therefore, it must not attempt to access the primary
// FrameTree in the sense of an "outer delegate" relationship, so we return
// the invalid ID here.
return FrameTreeNode::kFrameTreeNodeInvalidId;
}
bool PrerenderPageHolder::IsPortal() {
return false;
}
void PrerenderPageHolder::ActivateAndShowRepostFormWarningDialog() {
// Not supported, cancel pending reload.
GetNavigationController().CancelPendingReload();
}
bool PrerenderPageHolder::ShouldPreserveAbortedURLs() {
return false;
}
WebContents* PrerenderPageHolder::DeprecatedGetWebContents() {
return GetWebContents();
}
std::unique_ptr<StoredPage> PrerenderPageHolder::Activate(
NavigationRequest& navigation_request) {
// There should be no ongoing main-frame navigation during activation.
// TODO(https://crbug.com/1190644): Make sure sub-frame navigations are
// fine.
DCHECK(!frame_tree_->root()->HasNavigation());
// Before the root's current_frame_host is cleared, collect the subframes of
// `frame_tree_` whose FrameTree will need to be updated.
FrameTree::NodeRange node_range = frame_tree_->Nodes();
std::vector<FrameTreeNode*> subframe_nodes(std::next(node_range.begin()),
node_range.end());
// Before the root's current_frame_host is cleared, collect the replication
// state so that it can be used for post-activation validation.
blink::mojom::FrameReplicationState prior_replication_state =
frame_tree_->root()->current_replication_state();
// Update FrameReplicationState::has_received_user_gesture_before_nav of the
// prerendered page.
//
// On regular navigation, it is updated via a renderer => browser IPC
// (RenderFrameHostImpl::HadStickyUserActivationBeforeNavigationChanged),
// which is sent from blink::DocumentLoader::CommitNavigation. However,
// this doesn't happen on prerender page activation, so the value is not
// correctly updated without this treatment.
//
// The updated value will be sent to the renderer on
// blink::mojom::Page::ActivatePrerenderedPage.
prior_replication_state.has_received_user_gesture_before_nav =
navigation_request.frame_tree_node()
->has_received_user_gesture_before_nav();
// frame_tree_->root(). Do not add any code between here and
// frame_tree_.reset() that calls into observer functions to minimize the
// duration of current_frame_host being null.
//
// TODO(https://crbug.com/1176148): Investigate how to combine taking the
// prerendered page and frame_tree_ destruction.
std::unique_ptr<StoredPage> page =
frame_tree_->root()->render_manager()->TakePrerenderedPage();
std::unique_ptr<NavigationEntryRestoreContextImpl> context =
std::make_unique<NavigationEntryRestoreContextImpl>();
std::unique_ptr<NavigationEntryImpl> nav_entry =
GetNavigationController()
.GetEntryWithUniqueID(page->render_frame_host->nav_entry_id())
->CloneWithoutSharing(context.get());
navigation_request.SetPrerenderActivationNavigationState(
std::move(nav_entry), prior_replication_state);
FrameTree& target_frame_tree = GetPrimaryFrameTree();
DCHECK_EQ(&target_frame_tree,
navigation_request.frame_tree_node()->frame_tree());
// We support activating the prerenderd page only to the topmost
// RenderFrameHost.
CHECK(!page->render_frame_host->GetParentOrOuterDocumentOrEmbedder());
page->render_frame_host->SetFrameTreeNode(*(target_frame_tree.root()));
// Copy frame name into the replication state of the primary main frame to
// ensure that the replication state of the primary main frame after
// activation matches the replication state stored in the renderer.
// TODO(https://crbug.com/1237091): Copying frame name here is suboptimal
// and ideally we'd do this at the same time when transferring the proxies
// from the StoredPage into RenderFrameHostManager. However, this is a
// temporary solution until we move this into BrowsingContextState,
// along with RenderFrameProxyHost.
page->render_frame_host->frame_tree_node()->set_frame_name_for_activation(
prior_replication_state.unique_name, prior_replication_state.name);
for (auto& it : page->proxy_hosts) {
it.second->set_frame_tree_node(*(target_frame_tree.root()));
}
// Iterate over the root RenderFrameHost's subframes and update the
// associated frame tree. Note that subframe proxies don't need their
// FrameTrees independently updated, since their FrameTreeNodes don't
// change, and FrameTree references in those FrameTreeNodes will be updated
// through RenderFrameHosts.
//
// TODO(https://crbug.com/1199693): Need to investigate if and how
// pending delete RenderFrameHost objects should be handled if prerendering
// runs all of the unload handlers; they are not currently handled here.
// This is because pending delete RenderFrameHosts can still receive and
// process some messages while the RenderFrameHost FrameTree and
// FrameTreeNode are stale.
for (FrameTreeNode* subframe_node : subframe_nodes) {
subframe_node->SetFrameTree(target_frame_tree);
}
page->render_frame_host->ForEachRenderFrameHostIncludingSpeculative(
[this](RenderFrameHostImpl* rfh) {
// The visibility state of the prerendering page has not been
// updated by
// WebContentsImpl::UpdateVisibilityAndNotifyPageAndView(). So
// updates the visibility state using the PageVisibilityState of
// |web_contents|.
rfh->render_view_host()->SetFrameTreeVisibility(
web_contents_.GetPageVisibilityState());
});
frame_tree_->Shutdown();
frame_tree_.reset();
return page;
}
PrerenderHost::LoadingOutcome
PrerenderPageHolder::WaitForLoadCompletionForTesting() {
PrerenderHost::LoadingOutcome status =
PrerenderHost::LoadingOutcome::kLoadingCompleted;
if (!frame_tree_->IsLoadingIncludingInnerFrameTrees())
return status;
base::RunLoop loop;
on_wait_loading_finished_ =
base::BindOnce(&PrerenderPageHolder::FinishWaitingForTesting,
loop.QuitClosure(), &status);
loop.Run();
return status;
}
} // namespace content