blob: 1435988f4573065c09008499ce8a039df84e9020 [file] [log] [blame]
// Copyright 2018 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/portal/portal.h"
#include <unordered_map>
#include <utility>
#include "base/callback_helpers.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_host_manager.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_widget_host_input_event_router.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/render_widget_host_iterator.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/page_type.h"
#include "content/public/common/referrer_type_converters.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "services/service_manager/public/mojom/interface_provider.mojom.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/frame/frame_owner_element_type.h"
#include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
namespace content {
namespace {
void CreatePortalRenderWidgetHostView(WebContentsImpl* web_contents,
RenderViewHostImpl* render_view_host) {
if (auto* view = render_view_host->GetWidget()->GetView())
view->Destroy();
web_contents->CreateRenderWidgetHostViewForRenderManager(render_view_host);
}
} // namespace
Portal::Portal(RenderFrameHostImpl* owner_render_frame_host)
: WebContentsObserver(
WebContents::FromRenderFrameHost(owner_render_frame_host)),
owner_render_frame_host_(owner_render_frame_host) {}
Portal::Portal(RenderFrameHostImpl* owner_render_frame_host,
std::unique_ptr<WebContents> existing_web_contents)
: Portal(owner_render_frame_host) {
portal_contents_.SetOwned(std::move(existing_web_contents));
portal_contents_->NotifyInsidePortal(true);
}
Portal::~Portal() {
devtools_instrumentation::PortalDetached(
GetPortalHostContents()->GetPrimaryMainFrame());
Observe(nullptr);
}
// static
bool Portal::IsEnabled() {
return base::FeatureList::IsEnabled(blink::features::kPortals);
}
// static
void Portal::BindPortalHostReceiver(
RenderFrameHostImpl* frame,
mojo::PendingAssociatedReceiver<blink::mojom::PortalHost>
pending_receiver) {
if (!IsEnabled()) {
mojo::ReportBadMessage(
"blink.mojom.PortalHost can only be used if the Portals feature is "
"enabled.");
return;
}
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(frame));
// This guards against the blink::mojom::PortalHost interface being used
// outside the main frame of a Portal's guest.
if (!web_contents || !web_contents->IsPortal() || !frame->is_main_frame()) {
mojo::ReportBadMessage(
"blink.mojom.PortalHost can only be used by the the main frame of a "
"Portal's guest.");
return;
}
// This binding may already be bound to another request, and in such cases,
// we rebind with the new request. An example scenario is a new document after
// a portal navigation trying to create a connection, but the old document
// hasn't been destroyed yet (and the pipe hasn't been closed).
auto& receiver = web_contents->portal()->portal_host_receiver_;
if (receiver.is_bound())
receiver.reset();
receiver.Bind(std::move(pending_receiver));
receiver.SetFilter(frame->CreateMessageFilterForAssociatedReceiver(
blink::mojom::PortalHost::Name_));
}
void Portal::Bind(
mojo::PendingAssociatedReceiver<blink::mojom::Portal> receiver,
mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client) {
DCHECK(!receiver_.is_bound());
DCHECK(!client_.is_bound());
receiver_.Bind(std::move(receiver));
receiver_.set_disconnect_handler(
base::BindOnce(&Portal::Close, base::Unretained(this)));
client_.Bind(std::move(client));
}
void Portal::DestroySelf() {
// Deletes |this|.
owner_render_frame_host_->DestroyPortal(this);
}
RenderFrameProxyHost* Portal::CreateProxyAndAttachPortal(
blink::mojom::RemoteFrameInterfacesFromRendererPtr
remote_frame_interfaces) {
DCHECK(remote_frame_interfaces);
WebContentsImpl* outer_contents_impl = GetPortalHostContents();
// Check if portal has already been attached.
if (portal_contents_ && portal_contents_->GetOuterWebContents()) {
mojo::ReportBadMessage(
"Trying to attach a portal that has already been attached.");
return nullptr;
}
// Create a FrameTreeNode in the outer WebContents to host the portal, in
// response to the creation of a portal in the renderer process.
FrameTreeNode* outer_node =
outer_contents_impl->GetPrimaryFrameTree().AddFrame(
owner_render_frame_host_,
owner_render_frame_host_->GetProcess()->GetID(),
owner_render_frame_host_->GetProcess()->GetNextRoutingID(),
// `outer_node` is just a dummy outer delegate node, which will never
// have a corresponding `RenderFrameImpl`, and therefore we pass null
// remotes/receivers for connections that it would normally have to a
// renderer process.
/*frame_remote=*/mojo::NullAssociatedRemote(),
/*browser_interface_broker_receiver=*/mojo::NullReceiver(),
// The PolicyContainerHost remote is sent to Blink in the
// CreateRenderView mojo message.
/*policy_container_bind_params=*/nullptr,
/*associated_interface_provider_receiver=*/
mojo::NullAssociatedReceiver(),
blink::mojom::TreeScopeType::kDocument, "", "", true,
blink::LocalFrameToken(), base::UnguessableToken::Create(),
blink::FramePolicy(), blink::mojom::FrameOwnerProperties(), false,
blink::FrameOwnerElementType::kPortal,
/*is_dummy_frame_for_inner_tree=*/true);
outer_node->AddObserver(this);
bool web_contents_created = false;
if (!portal_contents_) {
// Create the Portal WebContents.
WebContents::CreateParams params(outer_contents_impl->GetBrowserContext());
portal_contents_.SetOwned(base::WrapUnique(
static_cast<WebContentsImpl*>(WebContents::Create(params).release())));
outer_contents_impl->InnerWebContentsCreated(portal_contents_.get());
web_contents_created = true;
}
DCHECK(portal_contents_.OwnsContents());
DCHECK_EQ(portal_contents_->portal(), this);
DCHECK_EQ(portal_contents_->GetDelegate(), this);
DCHECK(!is_closing_) << "Portal should not be shutting down when contents "
"ownership is yielded";
outer_contents_impl->AttachInnerWebContents(
portal_contents_.ReleaseOwnership(), outer_node->current_frame_host(),
std::move(remote_frame_interfaces->frame),
std::move(remote_frame_interfaces->frame_host_receiver),
/*is_full_page=*/false);
// Create the view for all RenderViewHosts that don't have a
// RenderWidgetHostViewChildFrame view.
// TODO(https://crbug.com/1264031): With MPArch a WebContents might have
// multiple FrameTrees. Make sure this code really just needs the
// primary one.
for (auto& render_view_host :
portal_contents_->GetPrimaryFrameTree().render_view_hosts()) {
if (!render_view_host.second->GetWidget()->GetView() ||
!render_view_host.second->GetWidget()
->GetView()
->IsRenderWidgetHostViewChildFrame()) {
CreatePortalRenderWidgetHostView(portal_contents_.get(),
render_view_host.second);
}
}
RenderFrameProxyHost* proxy_host =
portal_contents_->GetPrimaryMainFrame()->GetProxyToOuterDelegate();
proxy_host->SetRenderFrameProxyCreated(true);
portal_contents_->ReattachToOuterWebContentsFrame();
if (web_contents_created)
PortalWebContentsCreated(portal_contents_.get());
outer_contents_impl->NotifyNavigationStateChanged(INVALIDATE_TYPE_TAB);
devtools_instrumentation::PortalAttached(
outer_contents_impl->GetPrimaryMainFrame());
return proxy_host;
}
void Portal::Close() {
if (is_closing_)
return;
is_closing_ = true;
receiver_.reset();
// If the contents is attached to its outer `WebContents`, and therefore not
// owned by `portal_contents_`, we can destroy ourself right now.
if (!portal_contents_.OwnsContents()) {
DestroySelf(); // Deletes this.
return;
}
// Otherwise if the portal contents is not attached to an outer `WebContents`,
// we have to manage the destruction process ourself. We start by calling
// `WebContentsImpl::ClosePage()`, which will go through the proper unload
// handler dance, and eventually come back and call `Portal::CloseContents()`,
// which for orphaned contents, will finally invoke `DestroySelf()`.
portal_contents_->ClosePage();
}
void Portal::Navigate(const GURL& url,
blink::mojom::ReferrerPtr referrer,
NavigateCallback callback) {
// |RenderFrameHostImpl::CreatePortal| doesn't allow portals to be created in
// a prerender page.
DCHECK_NE(RenderFrameHost::LifecycleState::kPrerendering,
owner_render_frame_host_->GetLifecycleState());
if (!url.SchemeIsHTTPOrHTTPS()) {
mojo::ReportBadMessage("Portal::Navigate tried to use non-HTTP protocol.");
DestroySelf(); // Also deletes |this|.
return;
}
GURL out_validated_url = url;
owner_render_frame_host_->GetSiteInstance()->GetProcess()->FilterURL(
false, &out_validated_url);
FrameTreeNode* portal_root = portal_contents_->GetPrimaryFrameTree().root();
RenderFrameHostImpl* portal_frame = portal_root->current_frame_host();
// TODO(crbug.com/1237547): Change our implementation to disallow downloads for
// portals.
blink::NavigationDownloadPolicy download_policy;
// Navigations in portals do not affect the host's session history. Upon
// activation, only the portal's last committed entry is merged with the
// host's session history. Hence, a portal maintaining multiple session
// history entries is not useful and would introduce unnecessary complexity.
// We therefore have portal navigations done with replacement, so that we only
// have one entry at a time.
// TODO(mcnee): There are still corner cases (e.g. using window.opener when
// it's remote) that could cause a portal to navigate without replacement.
// Fix this so that we can enforce this as an invariant.
constexpr bool should_replace_entry = true;
// TODO(crbug.com/1290239): Measure the start time in the renderer process.
const auto navigation_start_time = base::TimeTicks::Now();
// TODO(https://crbug.com/1074422): It is possible for a portal to be
// navigated by a frame other than the owning frame. Find a way to route the
// correct initiator of the portal navigation to this call.
const blink::LocalFrameToken frame_token =
owner_render_frame_host_->GetFrameToken();
portal_root->navigator().NavigateFromFrameProxy(
portal_frame, out_validated_url, &frame_token,
owner_render_frame_host_->GetProcess()->GetID(),
owner_render_frame_host_->GetLastCommittedOrigin(),
owner_render_frame_host_->GetSiteInstance(),
mojo::ConvertTo<Referrer>(referrer), ui::PAGE_TRANSITION_LINK,
should_replace_entry, download_policy, "GET", nullptr, "", nullptr,
network::mojom::SourceLocation::New(), false,
/*is_form_submission=*/false,
/*impression=*/absl::nullopt, navigation_start_time);
std::move(callback).Run();
}
namespace {
void FlushTouchEventQueues(RenderWidgetHostImpl* host) {
host->input_router()->FlushTouchEventQueue();
std::unique_ptr<RenderWidgetHostIterator> child_widgets =
host->GetEmbeddedRenderWidgetHosts();
while (RenderWidgetHost* child_widget = child_widgets->GetNextHost())
FlushTouchEventQueues(static_cast<RenderWidgetHostImpl*>(child_widget));
}
// Copies |predecessor_contents|'s navigation entries to
// |activated_contents|. |activated_contents| will have its last committed entry
// combined with the entries in |predecessor_contents|. |predecessor_contents|
// will only keep its last committed entry.
// TODO(914108): This currently only covers the basic cases for history
// traversal across portal activations. The design is still being discussed.
void TakeHistoryForActivation(WebContentsImpl* activated_contents,
WebContentsImpl* predecessor_contents) {
NavigationControllerImpl& activated_controller =
activated_contents->GetController();
NavigationControllerImpl& predecessor_controller =
predecessor_contents->GetController();
// Activation would have discarded any pending entry in the host contents.
DCHECK(!predecessor_controller.GetPendingEntry());
// TODO(mcnee): Once we enforce that a portal contents does not build up its
// own history, make this DCHECK that we only have a single committed entry,
// possibly with a new pending entry.
if (activated_controller.GetPendingEntryIndex() != -1) {
return;
}
DCHECK(activated_controller.GetLastCommittedEntry());
DCHECK(activated_controller.CanPruneAllButLastCommitted());
// TODO(mcnee): Allow for portal activations to replace history entries and to
// traverse existing history entries.
activated_controller.CopyStateFromAndPrune(&predecessor_controller,
false /* replace_entry */);
// The predecessor may be adopted as a portal, so it should now only have a
// single committed entry.
DCHECK(predecessor_controller.CanPruneAllButLastCommitted());
predecessor_controller.PruneAllButLastCommitted();
}
} // namespace
void Portal::Activate(blink::TransferableMessage data,
base::TimeTicks activation_time,
uint64_t trace_id,
ActivateCallback callback) {
if (GetPortalHostContents()->portal()) {
mojo::ReportBadMessage("Portal::Activate called on nested portal");
DestroySelf();
return;
}
if (is_activating_) {
mojo::ReportBadMessage("Portal::Activate called twice on the same portal");
DestroySelf();
return;
}
for (Portal* portal : owner_render_frame_host()->GetPortals()) {
if (portal != this && portal->is_activating_) {
mojo::ReportBadMessage(
"Portal::Activate called on portal whose owner RenderFrameHost has "
"another portal that is activating");
DestroySelf();
return;
}
}
is_activating_ = true;
WebContentsImpl* outer_contents = GetPortalHostContents();
outer_contents->GetDelegate()->UpdateInspectedWebContentsIfNecessary(
outer_contents, portal_contents_.get(),
base::BindOnce(&Portal::ActivateImpl, weak_factory_.GetWeakPtr(),
std::move(data), activation_time, trace_id,
std::move(callback)));
}
namespace {
const char* kCrossOriginPostMessageError =
"postMessage failed because portal is not same origin with its host";
}
void Portal::PostMessageToGuest(blink::TransferableMessage message) {
if (!IsSameOrigin()) {
owner_render_frame_host()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
kCrossOriginPostMessageError);
devtools_instrumentation::DidRejectCrossOriginPortalMessage(
owner_render_frame_host());
return;
}
portal_contents_->GetPrimaryMainFrame()->ForwardMessageFromHost(
std::move(message), owner_render_frame_host_->GetLastCommittedOrigin());
}
void Portal::PostMessageToHost(blink::TransferableMessage message) {
DCHECK(GetPortalContents());
if (!IsSameOrigin()) {
GetPortalContents()->GetPrimaryMainFrame()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
kCrossOriginPostMessageError);
devtools_instrumentation::DidRejectCrossOriginPortalMessage(
GetPortalContents()->GetPrimaryMainFrame());
return;
}
client().ForwardMessageFromGuest(
std::move(message),
GetPortalContents()->GetPrimaryMainFrame()->GetLastCommittedOrigin());
}
void Portal::OnFrameTreeNodeDestroyed(FrameTreeNode* frame_tree_node) {
// Listens for the deletion of the FrameTreeNode corresponding to this portal
// in the outer WebContents (not the FrameTreeNode of the document containing
// it). If that outer FrameTreeNode goes away, this Portal should stop
// accepting new messages and go away as well.
Close(); // May delete |this|.
}
void Portal::RenderFrameDeleted(RenderFrameHost* render_frame_host) {
// Even though this object is owned (via unique_ptr by the RenderFrameHost),
// explicitly observing RenderFrameDeleted is necessary because it happens
// earlier than the destructor, notably before Mojo teardown.
if (render_frame_host == owner_render_frame_host_)
DestroySelf(); // Deletes |this|.
}
void Portal::WebContentsDestroyed() {
DestroySelf(); // Deletes |this|.
}
void Portal::LoadingStateChanged(WebContents* source,
bool should_show_loading_ui) {
DCHECK_EQ(source, portal_contents_.get());
if (!source->IsLoading())
client_->DispatchLoadEvent();
}
void Portal::PortalWebContentsCreated(WebContents* portal_web_contents) {
WebContentsImpl* outer_contents = GetPortalHostContents();
DCHECK(outer_contents->GetDelegate());
outer_contents->GetDelegate()->PortalWebContentsCreated(portal_web_contents);
}
void Portal::CloseContents(WebContents* web_contents) {
DCHECK_EQ(web_contents, portal_contents_.get());
if (portal_contents_->GetOuterWebContents()) {
// This portal was still attached, we shouldn't have received a request to
// close it.
bad_message::ReceivedBadMessage(
web_contents->GetPrimaryMainFrame()->GetProcess(),
bad_message::RWH_CLOSE_PORTAL);
} else {
// Orphaned portal was closed.
DestroySelf(); // Deletes |this|.
}
}
WebContents* Portal::GetResponsibleWebContents(WebContents* web_contents) {
return GetPortalHostContents();
}
void Portal::NavigationStateChanged(WebContents* source,
InvalidateTypes changed_flags) {
WebContents* outer_contents = GetPortalHostContents();
// Can be null in tests.
if (!outer_contents->GetDelegate())
return;
outer_contents->GetDelegate()->NavigationStateChanged(source, changed_flags);
}
bool Portal::ShouldFocusPageAfterCrash() {
return false;
}
void Portal::CanDownload(const GURL& url,
const std::string& request_method,
base::OnceCallback<void(bool)> callback) {
// Downloads are not allowed in portals.
owner_render_frame_host()->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
base::StringPrintf("Download in a portal (from %s) was blocked.",
url.spec().c_str()));
std::move(callback).Run(false);
}
base::UnguessableToken Portal::GetDevToolsFrameToken() const {
return portal_contents_->GetPrimaryMainFrame()->GetDevToolsFrameToken();
}
WebContentsImpl* Portal::GetPortalContents() const {
return portal_contents_.get();
}
WebContentsImpl* Portal::GetPortalHostContents() const {
return static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(owner_render_frame_host_));
}
bool Portal::IsSameOrigin() const {
return owner_render_frame_host_->GetLastCommittedOrigin().IsSameOriginWith(
portal_contents_->GetPrimaryMainFrame()->GetLastCommittedOrigin());
}
std::pair<bool, blink::mojom::PortalActivateResult> Portal::CanActivate() {
WebContentsImpl* outer_contents = GetPortalHostContents();
if (outer_contents->GetOuterWebContents()) {
// TODO(crbug.com/942534): Support portals in guest views.
NOTIMPLEMENTED();
return std::make_pair(false,
blink::mojom::PortalActivateResult::kNotImplemented);
}
DCHECK(owner_render_frame_host_->IsActive())
<< "The binding should have been closed when the portal's outer "
"FrameTreeNode was deleted due to swap out.";
DCHECK(portal_contents_);
NavigationControllerImpl& portal_controller =
portal_contents_->GetController();
NavigationControllerImpl& predecessor_controller =
outer_contents->GetController();
// If no navigation has yet committed in the portal, it cannot be activated as
// this would lead to an empty tab contents (without even an about:blank).
if (!portal_controller.GetLastCommittedEntry() ||
portal_controller.GetLastCommittedEntry()->IsInitialEntry()) {
return std::make_pair(
false,
blink::mojom::PortalActivateResult::kRejectedDueToPortalNotReady);
}
DCHECK(predecessor_controller.GetLastCommittedEntry());
// Error pages and interstitials may not host portals due to the HTTP(S)
// restriction.
DCHECK_EQ(PAGE_TYPE_NORMAL,
predecessor_controller.GetLastCommittedEntry()->GetPageType());
// If the portal is crashed or is showing an error page, reject activation.
if (portal_contents_->IsCrashed() ||
portal_controller.GetLastCommittedEntry()->GetPageType() !=
PAGE_TYPE_NORMAL) {
return std::make_pair(
false, blink::mojom::PortalActivateResult::kRejectedDueToErrorInPortal);
}
// If a navigation in the main frame is occurring, stop it if possible and
// reject the activation if it's too late or if an ongoing navigation takes
// precedence. There are a few cases here:
// - a different RenderFrameHost has been assigned to the FrameTreeNode
// - the same RenderFrameHost is being used, but it is committing a navigation
// - the FrameTreeNode holds a navigation request that can't turn back but has
// not yet been handed off to a RenderFrameHost
FrameTreeNode* outer_root_node = owner_render_frame_host_->frame_tree_node();
NavigationRequest* outer_navigation = outer_root_node->navigation_request();
const bool has_user_gesture =
owner_render_frame_host_->HasTransientUserActivation();
// WILL_PROCESS_RESPONSE is slightly early: it happens
// immediately before READY_TO_COMMIT (unless it's deferred), but
// WILL_PROCESS_RESPONSE is easier to hook for tests using a
// NavigationThrottle.
if (owner_render_frame_host_->HasPendingCommitNavigation() ||
(outer_navigation &&
outer_navigation->state() >= NavigationRequest::WILL_PROCESS_RESPONSE) ||
Navigator::ShouldIgnoreIncomingRendererRequest(outer_navigation,
has_user_gesture)) {
return std::make_pair(false, blink::mojom::PortalActivateResult::
kRejectedDueToPredecessorNavigation);
}
return std::make_pair(true,
blink::mojom::PortalActivateResult::kAbortedDueToBug);
}
void Portal::ActivateImpl(blink::TransferableMessage data,
base::TimeTicks activation_time,
uint64_t trace_id,
ActivateCallback callback) {
WebContentsImpl* outer_contents = GetPortalHostContents();
WebContentsDelegate* delegate = outer_contents->GetDelegate();
is_activating_ = false;
bool can_activate;
blink::mojom::PortalActivateResult activate_error;
std::tie(can_activate, activate_error) = CanActivate();
if (!can_activate) {
outer_contents->GetDelegate()->UpdateInspectedWebContentsIfNecessary(
portal_contents_.get(), outer_contents, base::DoNothing());
std::move(callback).Run(activate_error);
return;
}
FrameTreeNode* outer_root_node = owner_render_frame_host_->frame_tree_node();
outer_root_node->navigator().CancelNavigation(outer_root_node);
DCHECK(!is_closing_) << "Portal should not be shutting down when contents "
"ownership is yielded";
std::unique_ptr<WebContents> successor_contents;
if (portal_contents_->GetOuterWebContents()) {
FrameTreeNode* outer_frame_tree_node = FrameTreeNode::GloballyFindByID(
portal_contents_->GetOuterDelegateFrameTreeNodeId());
outer_frame_tree_node->RemoveObserver(this);
successor_contents = portal_contents_->DetachFromOuterWebContents();
owner_render_frame_host_->RemoveChild(outer_frame_tree_node);
} else {
// Portals created for predecessor pages during activation may not be
// attached to an outer WebContents, and may not have an outer frame tree
// node created (i.e. CreateProxyAndAttachPortal isn't called). In this
// case, we can skip a few of the detachment steps above.
// TODO(https://crbug.com/1264031): With MPArch a WebContents might have
// multiple FrameTrees. Make sure this code really just needs the
// primary one.
for (auto& render_view_host :
portal_contents_->GetPrimaryFrameTree().render_view_hosts()) {
CreatePortalRenderWidgetHostView(portal_contents_.get(),
render_view_host.second);
}
successor_contents = portal_contents_.ReleaseOwnership();
}
DCHECK(!portal_contents_.OwnsContents());
// This assumes that the delegate keeps the new contents alive long enough to
// notify it of activation, at least.
WebContentsImpl* successor_contents_raw =
static_cast<WebContentsImpl*>(successor_contents.get());
auto* outer_contents_main_frame_view = static_cast<RenderWidgetHostViewBase*>(
outer_contents->GetPrimaryMainFrame()->GetView());
DCHECK(!outer_contents->GetPrimaryFrameTree()
.root()
->render_manager()
->speculative_frame_host());
auto* portal_contents_main_frame_view =
static_cast<RenderWidgetHostViewBase*>(
successor_contents_raw->GetPrimaryMainFrame()->GetView());
std::vector<std::unique_ptr<ui::TouchEvent>> touch_events;
if (outer_contents_main_frame_view) {
// Take fallback contents from previous WebContents so that the activation
// is smooth without flashes.
portal_contents_main_frame_view->TakeFallbackContentFrom(
outer_contents_main_frame_view);
touch_events =
outer_contents_main_frame_view->ExtractAndCancelActiveTouches();
FlushTouchEventQueues(outer_contents_main_frame_view->host());
}
TakeHistoryForActivation(successor_contents_raw, outer_contents);
successor_contents_raw->set_portal(nullptr);
// It's important we call this before destroying the outer contents'
// RenderWidgetHostView, otherwise the dialog may not be cleaned up correctly.
// See crbug.com/1292261 for more details.
outer_contents->CancelActiveAndPendingDialogs();
std::unique_ptr<WebContents> predecessor_web_contents =
delegate->ActivatePortalWebContents(outer_contents,
std::move(successor_contents));
DCHECK_EQ(predecessor_web_contents.get(), outer_contents);
devtools_instrumentation::PortalActivated(*this);
if (outer_contents_main_frame_view) {
portal_contents_main_frame_view->TransferTouches(touch_events);
// Takes ownership of SyntheticGestureController from the predecessor's
// RenderWidgetHost. This allows the controller to continue sending events
// to the new RenderWidgetHostView.
portal_contents_main_frame_view->host()->TakeSyntheticGestureController(
outer_contents_main_frame_view->host());
outer_contents_main_frame_view->Destroy();
}
// Remove page focus from the now orphaned predecessor.
outer_contents->GetPrimaryMainFrame()->GetRenderWidgetHost()->Blur();
// These pointers are cleared so that they don't dangle in the event this
// object isn't immediately deleted. It isn't done sooner because
// ActivatePortalWebContents misbehaves if the WebContents doesn't appear to
// be a portal at that time.
portal_contents_.Clear();
mojo::PendingAssociatedRemote<blink::mojom::Portal> pending_portal;
auto portal_receiver = pending_portal.InitWithNewEndpointAndPassReceiver();
mojo::PendingAssociatedRemote<blink::mojom::PortalClient> pending_client;
auto client_receiver = pending_client.InitWithNewEndpointAndPassReceiver();
RenderFrameHostImpl* successor_main_frame =
successor_contents_raw->GetPrimaryMainFrame();
auto predecessor = std::make_unique<Portal>(
successor_main_frame, std::move(predecessor_web_contents));
predecessor->Bind(std::move(portal_receiver), std::move(pending_client));
successor_main_frame->OnPortalActivated(
std::move(predecessor), std::move(pending_portal),
std::move(client_receiver), std::move(data), trace_id,
std::move(callback));
// Notifying of activation happens later than ActivatePortalWebContents so
// that it is observed after predecessor_web_contents has been moved into a
// portal.
DCHECK(outer_contents->IsPortal());
successor_contents_raw->DidActivatePortal(outer_contents, activation_time);
}
Portal::WebContentsHolder::WebContentsHolder(Portal* portal)
: portal_(portal) {}
Portal::WebContentsHolder::~WebContentsHolder() {
Clear();
}
bool Portal::WebContentsHolder::OwnsContents() const {
DCHECK(!owned_contents_ || contents_ == owned_contents_.get());
return owned_contents_ != nullptr;
}
void Portal::WebContentsHolder::SetUnowned(WebContentsImpl* web_contents) {
Clear();
contents_ = web_contents;
contents_->SetDelegate(portal_);
contents_->set_portal(portal_);
}
void Portal::WebContentsHolder::SetOwned(
std::unique_ptr<WebContents> web_contents) {
SetUnowned(static_cast<WebContentsImpl*>(web_contents.get()));
owned_contents_ = std::move(web_contents);
}
void Portal::WebContentsHolder::Clear() {
if (!contents_)
return;
FrameTreeNode* outer_node = FrameTreeNode::GloballyFindByID(
contents_->GetOuterDelegateFrameTreeNodeId());
if (outer_node)
outer_node->RemoveObserver(portal_);
if (contents_->GetDelegate() == portal_)
contents_->SetDelegate(nullptr);
contents_->set_portal(nullptr);
contents_ = nullptr;
owned_contents_ = nullptr;
}
} // namespace content