blob: ada87cadd7e9a243e236d55988195434da1e7648 [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/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/web_contents/web_contents_impl.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_switches.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_binding_(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());
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,
blink::mojom::PortalAssociatedRequest request,
blink::mojom::PortalClientAssociatedPtrInfo client) {
auto portal_ptr = base::WrapUnique(new Portal(owner_render_frame_host));
Portal* portal = portal_ptr.get();
portal->binding_ = mojo::MakeStrongAssociatedBinding(std::move(portal_ptr),
std::move(request));
portal->client_ = blink::mojom::PortalClientAssociatedPtr(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::BindPortalHostRequest(
RenderFrameHostImpl* frame,
blink::mojom::PortalHostAssociatedRequest request) {
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& binding = web_contents->portal()->portal_host_binding_;
if (binding.is_bound())
binding.Close();
binding.Bind(std::move(request));
}
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));
blink::mojom::DocumentInterfaceBrokerPtrInfo
document_interface_broker_content;
blink::mojom::DocumentInterfaceBrokerPtrInfo document_interface_broker_blink;
// 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::MakeRequest(&document_interface_broker_content),
mojo::MakeRequest(&document_interface_broker_blink),
blink::WebTreeScopeType::kDocument, "", "", true,
base::UnguessableToken::Create(), blink::FramePolicy(),
FrameOwnerProperties(), false, blink::FrameOwnerElementType::kPortal);
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->set_render_frame_proxy_created(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) {
NavigationController::LoadURLParams load_url_params(url);
portal_contents_impl_->GetController().LoadURLWithParams(load_url_params);
}
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 =
portal_contents_impl_->DetachFromOuterWebContents();
auto* outer_contents_main_frame_view = static_cast<RenderWidgetHostViewBase*>(
outer_contents->GetMainFrame()->GetView());
if (outer_contents_main_frame_view) {
// Take fallback contents from previous WebContents so that the activation
// is smooth without flashes.
auto* portal_contents_main_frame_view =
static_cast<RenderWidgetHostViewBase*>(
portal_contents_impl_->GetMainFrame()->GetView());
portal_contents_main_frame_view->TakeFallbackContentFrom(
outer_contents_main_frame_view);
outer_contents_main_frame_view->Destroy();
}
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);
portal_contents_impl_->set_portal(nullptr);
blink::mojom::PortalAssociatedPtr portal_ptr;
blink::mojom::PortalClientAssociatedPtr portal_client_ptr;
auto portal_client_request = mojo::MakeRequest(&portal_client_ptr);
Portal* portal =
Create(portal_contents_impl_->GetMainFrame(),
mojo::MakeRequest(&portal_ptr), portal_client_ptr.PassInterface());
portal->SetPortalContents(std::move(predecessor_web_contents));
portal_contents_impl_->GetMainFrame()->OnPortalActivated(
portal->portal_token_, portal_ptr.PassInterface(),
std::move(portal_client_request), 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::RenderFrameDeleted(RenderFrameHost* render_frame_host) {
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(
blink::mojom::PortalClientAssociatedPtr 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