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