| // 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 |