blob: bbc42610f93a3f00e7af6ae795367f54a6d7e788 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/feature_list.h"
#include "base/memory/ptr_util.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/frame_host/render_frame_host_manager.h"
#include "content/browser/frame_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/referrer_type_converters.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "services/service_manager/public/mojom/interface_provider.mojom.h"
#include "third_party/blink/public/common/features.h"
namespace content {
namespace {
using PortalTokenMap = std::
unordered_map<base::UnguessableToken, Portal*, base::UnguessableTokenHash>;
base::LazyInstance<PortalTokenMap>::Leaky g_portal_token_map =
LAZY_INSTANCE_INITIALIZER;
} // namespace
Portal::Portal(RenderFrameHostImpl* owner_render_frame_host)
: WebContentsObserver(
WebContents::FromRenderFrameHost(owner_render_frame_host)),
owner_render_frame_host_(owner_render_frame_host),
portal_token_(base::UnguessableToken::Create()),
portal_host_receiver_(this) {
auto pair = g_portal_token_map.Get().emplace(portal_token_, this);
DCHECK(pair.second);
}
Portal::~Portal() {
WebContentsImpl* outer_contents_impl = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(owner_render_frame_host_));
devtools_instrumentation::PortalDetached(outer_contents_impl->GetMainFrame());
FrameTreeNode* outer_node = FrameTreeNode::GloballyFindByID(
portal_contents_impl_->GetOuterDelegateFrameTreeNodeId());
if (outer_node)
outer_node->RemoveObserver(this);
portal_contents_impl_->set_portal(nullptr);
g_portal_token_map.Get().erase(portal_token_);
}
// static
bool Portal::IsEnabled() {
return base::FeatureList::IsEnabled(blink::features::kPortals) ||
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
// static
Portal* Portal::FromToken(const base::UnguessableToken& portal_token) {
PortalTokenMap& portals = g_portal_token_map.Get();
auto it = portals.find(portal_token);
return it == portals.end() ? nullptr : it->second;
}
// static
Portal* Portal::Create(
RenderFrameHostImpl* owner_render_frame_host,
mojo::PendingAssociatedReceiver<blink::mojom::Portal> receiver,
mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client) {
if (!IsEnabled()) {
mojo::ReportBadMessage(
"blink.mojom.Portal can only be used if the Portals feature is "
"enabled.");
return nullptr;
}
auto portal_ptr = base::WrapUnique(new Portal(owner_render_frame_host));
Portal* portal = portal_ptr.get();
portal->binding_ = mojo::MakeStrongAssociatedBinding<blink::mojom::Portal>(
std::move(portal_ptr), std::move(receiver));
portal->client_.Bind(std::move(client));
return portal;
}
// static
std::unique_ptr<Portal> Portal::CreateForTesting(
RenderFrameHostImpl* owner_render_frame_host) {
return base::WrapUnique(new Portal(owner_render_frame_host));
}
// 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->frame_tree_node()->IsMainFrame()) {
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));
}
RenderFrameProxyHost* Portal::CreateProxyAndAttachPortal() {
WebContentsImpl* outer_contents_impl = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(owner_render_frame_host_));
service_manager::mojom::InterfaceProviderPtr interface_provider;
auto interface_provider_request(mojo::MakeRequest(&interface_provider));
// 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->GetFrameTree()->AddFrame(
owner_render_frame_host_->frame_tree_node(),
owner_render_frame_host_->GetProcess()->GetID(),
owner_render_frame_host_->GetProcess()->GetNextRoutingID(),
std::move(interface_provider_request),
mojo::PendingRemote<blink::mojom::DocumentInterfaceBroker>()
.InitWithNewPipeAndPassReceiver(),
mojo::PendingRemote<blink::mojom::DocumentInterfaceBroker>()
.InitWithNewPipeAndPassReceiver(),
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
.InitWithNewPipeAndPassReceiver(),
blink::WebTreeScopeType::kDocument, "", "", true,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, blink::FrameOwnerElementType::kPortal);
outer_node->AddObserver(this);
bool web_contents_created = false;
if (!portal_contents_) {
// Create the Portal WebContents.
WebContents::CreateParams params(outer_contents_impl->GetBrowserContext());
SetPortalContents(WebContents::Create(params));
web_contents_created = true;
}
DCHECK_EQ(portal_contents_.get(), portal_contents_impl_);
DCHECK_EQ(portal_contents_impl_->portal(), this);
DCHECK_EQ(portal_contents_impl_->GetDelegate(), this);
outer_contents_impl->AttachInnerWebContents(std::move(portal_contents_),
outer_node->current_frame_host());
FrameTreeNode* frame_tree_node =
portal_contents_impl_->GetMainFrame()->frame_tree_node();
RenderFrameProxyHost* proxy_host =
frame_tree_node->render_manager()->GetProxyToOuterDelegate();
proxy_host->SetRenderFrameProxyCreated(true);
portal_contents_impl_->ReattachToOuterWebContentsFrame();
if (web_contents_created)
PortalWebContentsCreated(portal_contents_impl_);
devtools_instrumentation::PortalAttached(outer_contents_impl->GetMainFrame());
return proxy_host;
}
void Portal::Navigate(const GURL& url,
blink::mojom::ReferrerPtr referrer,
NavigateCallback callback) {
if (!url.SchemeIsHTTPOrHTTPS()) {
mojo::ReportBadMessage("Portal::Navigate tried to use non-HTTP protocol.");
binding_->Close(); // 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_impl_->GetFrameTree()->root();
RenderFrameHostImpl* portal_frame = portal_root->current_frame_host();
// TODO(lfg): Figure out download policies for portals.
// https://github.com/WICG/portals/issues/150
NavigationDownloadPolicy download_policy;
portal_root->navigator()->NavigateFromFrameProxy(
portal_frame, url, owner_render_frame_host_->GetLastCommittedOrigin(),
owner_render_frame_host_->GetSiteInstance(),
mojo::ConvertTo<Referrer>(referrer), ui::PAGE_TRANSITION_LINK, false,
download_policy, "GET", nullptr, "", nullptr, false);
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));
}
} // namespace
void Portal::Activate(blink::TransferableMessage data,
ActivateCallback callback) {
WebContentsImpl* outer_contents = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(owner_render_frame_host_));
if (outer_contents->portal()) {
mojo::ReportBadMessage("Portal::Activate called on nested portal");
binding_->Close(); // Also deletes |this|.
return;
}
WebContentsDelegate* delegate = outer_contents->GetDelegate();
bool is_loading = portal_contents_impl_->IsLoading();
std::unique_ptr<WebContents> portal_contents;
if (portal_contents_impl_->GetOuterWebContents()) {
FrameTreeNode* outer_frame_tree_node = FrameTreeNode::GloballyFindByID(
portal_contents_impl_->GetOuterDelegateFrameTreeNodeId());
outer_frame_tree_node->RemoveObserver(this);
portal_contents = portal_contents_impl_->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.
if (RenderWidgetHostViewBase* view = static_cast<RenderWidgetHostViewBase*>(
portal_contents_impl_->GetMainFrame()->GetView())) {
view->Destroy();
}
portal_contents_impl_->CreateRenderWidgetHostViewForRenderManager(
portal_contents_impl_->GetRenderViewHost());
portal_contents = std::move(portal_contents_);
}
auto* outer_contents_main_frame_view = static_cast<RenderWidgetHostViewBase*>(
outer_contents->GetMainFrame()->GetView());
auto* portal_contents_main_frame_view =
static_cast<RenderWidgetHostViewBase*>(
portal_contents_impl_->GetMainFrame()->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());
}
std::unique_ptr<WebContents> predecessor_web_contents =
delegate->SwapWebContents(outer_contents, std::move(portal_contents),
true, is_loading);
CHECK_EQ(predecessor_web_contents.get(), outer_contents);
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();
}
portal_contents_impl_->set_portal(nullptr);
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();
Portal* portal =
Create(portal_contents_impl_->GetMainFrame(), std::move(portal_receiver),
std::move(pending_client));
portal->SetPortalContents(std::move(predecessor_web_contents));
portal_contents_impl_->GetMainFrame()->OnPortalActivated(
portal->portal_token_, std::move(pending_portal),
std::move(client_receiver), std::move(data), std::move(callback));
devtools_instrumentation::PortalActivated(outer_contents->GetMainFrame());
}
void Portal::PostMessageToGuest(
blink::TransferableMessage message,
const base::Optional<url::Origin>& target_origin) {
portal_contents_impl_->GetMainFrame()->ForwardMessageFromHost(
std::move(message), owner_render_frame_host_->GetLastCommittedOrigin(),
target_origin);
}
void Portal::PostMessageToHost(
blink::TransferableMessage message,
const base::Optional<url::Origin>& target_origin) {
DCHECK(GetPortalContents());
if (target_origin) {
if (target_origin != owner_render_frame_host_->GetLastCommittedOrigin())
return;
}
client().ForwardMessageFromGuest(
std::move(message),
GetPortalContents()->GetMainFrame()->GetLastCommittedOrigin(),
target_origin);
}
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.
binding_->Close(); // Also deletes |this|.
}
void Portal::RenderFrameDeleted(RenderFrameHost* render_frame_host) {
// When the portal is in an orphaned state, the RenderFrameDeleted callback is
// used to tie the portal's lifetime to its owner.
if (render_frame_host == owner_render_frame_host_)
binding_->Close(); // Also deletes |this|.
}
void Portal::WebContentsDestroyed() {
binding_->Close(); // Also deletes |this|.
}
void Portal::LoadingStateChanged(WebContents* source,
bool to_different_document) {
DCHECK_EQ(source, portal_contents_impl_);
if (!source->IsLoading())
client_->DispatchLoadEvent();
}
void Portal::PortalWebContentsCreated(WebContents* portal_web_contents) {
WebContentsImpl* outer_contents = static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(owner_render_frame_host_));
DCHECK(outer_contents->GetDelegate());
outer_contents->GetDelegate()->PortalWebContentsCreated(portal_web_contents);
}
base::UnguessableToken Portal::GetDevToolsFrameToken() const {
return portal_contents_impl_->GetMainFrame()->GetDevToolsFrameToken();
}
WebContentsImpl* Portal::GetPortalContents() {
return portal_contents_impl_;
}
void Portal::SetBindingForTesting(
mojo::StrongAssociatedBindingPtr<blink::mojom::Portal> binding) {
binding_ = binding;
}
void Portal::SetClientForTesting(
mojo::AssociatedRemote<blink::mojom::PortalClient> client) {
client_ = std::move(client);
}
void Portal::SetPortalContents(std::unique_ptr<WebContents> web_contents) {
portal_contents_ = std::move(web_contents);
portal_contents_impl_ = static_cast<WebContentsImpl*>(portal_contents_.get());
portal_contents_impl_->SetDelegate(this);
portal_contents_impl_->set_portal(this);
}
} // namespace content