| // Copyright 2019 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 "extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.h" |
| |
| #include "base/containers/contains.h" |
| #include "base/containers/flat_map.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/no_destructor.h" |
| #include "components/guest_view/browser/guest_view_manager.h" |
| #include "components/guest_view/browser/guest_view_manager_delegate.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "extensions/browser/api/extensions_api_client.h" |
| #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.h" |
| #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h" |
| #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h" |
| #include "extensions/common/mojom/guest_view.mojom.h" |
| #include "services/network/public/cpp/web_sandbox_flags.h" |
| #include "services/network/public/mojom/web_sandbox_flags.mojom-shared.h" |
| #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h" |
| #include "third_party/blink/public/common/frame/frame_owner_element_type.h" |
| |
| namespace extensions { |
| |
| namespace { |
| using EmbedderMap = |
| base::flat_map<int32_t, std::unique_ptr<MimeHandlerViewEmbedder>>; |
| |
| EmbedderMap* GetMimeHandlerViewEmbeddersMap() { |
| static base::NoDestructor<EmbedderMap> instance; |
| return instance.get(); |
| } |
| } // namespace |
| |
| // static |
| MimeHandlerViewEmbedder* MimeHandlerViewEmbedder::Get( |
| int32_t frame_tree_node_id) { |
| const auto& map = *GetMimeHandlerViewEmbeddersMap(); |
| auto it = map.find(frame_tree_node_id); |
| if (it == map.cend()) |
| return nullptr; |
| return it->second.get(); |
| } |
| |
| // static |
| void MimeHandlerViewEmbedder::Create(int32_t frame_tree_node_id, |
| const GURL& resource_url, |
| const std::string& stream_id, |
| const std::string& internal_id) { |
| DCHECK( |
| !base::Contains(*GetMimeHandlerViewEmbeddersMap(), frame_tree_node_id)); |
| GetMimeHandlerViewEmbeddersMap()->insert_or_assign( |
| frame_tree_node_id, |
| base::WrapUnique(new MimeHandlerViewEmbedder( |
| frame_tree_node_id, resource_url, stream_id, internal_id))); |
| } |
| |
| MimeHandlerViewEmbedder::MimeHandlerViewEmbedder(int32_t frame_tree_node_id, |
| const GURL& resource_url, |
| const std::string& stream_id, |
| const std::string& internal_id) |
| : content::WebContentsObserver( |
| content::WebContents::FromFrameTreeNodeId(frame_tree_node_id)), |
| frame_tree_node_id_(frame_tree_node_id), |
| resource_url_(resource_url), |
| stream_id_(stream_id), |
| internal_id_(internal_id) {} |
| |
| MimeHandlerViewEmbedder::~MimeHandlerViewEmbedder() {} |
| |
| void MimeHandlerViewEmbedder::DestroySelf() { |
| GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_); |
| } |
| |
| void MimeHandlerViewEmbedder::DidStartNavigation( |
| content::NavigationHandle* handle) { |
| // This observer is created after the observed |frame_tree_node_id_| started |
| // its navigation to the |resource_url|. If any new navigations start then |
| // we should stop now and do not create a MHVG. Same document navigations |
| // could occur for {replace,push}State among other reasons and should not |
| // lead to deleting the MVHE (e.g., the test |
| // PDFExtensionLinkClickTest.OpenPDFWithReplaceState reaches here). |
| if (handle->GetFrameTreeNodeId() == frame_tree_node_id_ && |
| !handle->IsSameDocument()) { |
| DestroySelf(); |
| } |
| } |
| |
| void MimeHandlerViewEmbedder::ReadyToCommitNavigation( |
| content::NavigationHandle* handle) { |
| if (handle->GetFrameTreeNodeId() != frame_tree_node_id_) |
| return; |
| // We should've deleted the MimeHandlerViewEmbedder at this point if the frame |
| // is sandboxed. |
| DCHECK_EQ(network::mojom::WebSandboxFlags::kNone, |
| handle->SandboxFlagsToCommit() & |
| network::mojom::WebSandboxFlags::kPlugins); |
| |
| if (!render_frame_host_) { |
| render_frame_host_ = handle->GetRenderFrameHost(); |
| GetContainerManager()->SetInternalId(internal_id_); |
| } |
| } |
| |
| void MimeHandlerViewEmbedder::DidFinishNavigation( |
| content::NavigationHandle* handle) { |
| if (!handle->HasCommitted() || |
| frame_tree_node_id_ != handle->GetFrameTreeNodeId()) { |
| return; |
| } |
| // We should've deleted the MimeHandlerViewEmbedder at this point if the frame |
| // is sandboxed. |
| DCHECK(render_frame_host_); |
| DCHECK(!render_frame_host_->IsSandboxed( |
| network::mojom::WebSandboxFlags::kPlugins)); |
| } |
| |
| void MimeHandlerViewEmbedder::RenderFrameCreated( |
| content::RenderFrameHost* render_frame_host) { |
| // If an extension injects a frame before we finish attaching the PDF's |
| // iframe, then we don't want to early out by mistake. We also put an |
| // unguessable name element on the <embed> to guard against the possibility |
| // that someone injects an <embed>, but that can be done in a separate CL. |
| if (!render_frame_host_ || |
| render_frame_host_ != render_frame_host->GetParent() || |
| render_frame_host_->GetLastCommittedURL() != resource_url_ || |
| render_frame_host->GetFrameOwnerElementType() != |
| blink::FrameOwnerElementType::kEmbed || |
| render_frame_host->GetFrameName() != internal_id_) { |
| return; |
| } |
| if (!ready_to_create_mime_handler_view_) { |
| // Renderer notifies the browser about creating MimeHandlerView right after |
| // HTMLPlugInElement::RequestObject, which is before the plugin element is |
| // navigated. |
| DestroySelf(); |
| return; |
| } |
| |
| outer_contents_rfh_ = render_frame_host; |
| |
| // This suggests that a same-origin child frame is created under the |
| // RFH associated with |frame_tree_node_id_|. This suggests that the HTML |
| // string is loaded in the observed frame's document and now the renderer |
| // can initiate the MimeHandlerViewFrameContainer creation process. |
| // TODO(ekaramad): We shouldn't have to wait for the response from the |
| // renderer; instead we should proceed with creating MHVG. Instead, the |
| // interface request in MHVG for beforeunload should wait until this response |
| // comes back. |
| GetContainerManager()->CreateBeforeUnloadControl( |
| base::BindOnce(&MimeHandlerViewEmbedder::CreateMimeHandlerViewGuest, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void MimeHandlerViewEmbedder::FrameDeleted(int frame_tree_node_id) { |
| // TODO(mcnee): RenderFrameDeleted seems like a better fit for the child frame |
| // case (i.e. |outer_contents_rfh_|), though we'd still need FrameDeleted for |
| // |frame_tree_node_id_|. |
| if (frame_tree_node_id == frame_tree_node_id_ || |
| (outer_contents_rfh_ && |
| outer_contents_rfh_->GetFrameTreeNodeId() == frame_tree_node_id)) { |
| DestroySelf(); |
| } |
| } |
| |
| void MimeHandlerViewEmbedder::CreateMimeHandlerViewGuest( |
| mojo::PendingRemote<mime_handler::BeforeUnloadControl> |
| before_unload_control_remote) { |
| auto* browser_context = web_contents()->GetBrowserContext(); |
| auto* manager = |
| guest_view::GuestViewManager::FromBrowserContext(browser_context); |
| if (!manager) { |
| manager = guest_view::GuestViewManager::CreateWithDelegate( |
| browser_context, |
| ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate( |
| browser_context)); |
| } |
| base::DictionaryValue create_params; |
| create_params.SetString(mime_handler_view::kViewId, stream_id_); |
| manager->CreateGuest( |
| MimeHandlerViewGuest::Type, web_contents(), create_params, |
| base::BindOnce(&MimeHandlerViewEmbedder::DidCreateMimeHandlerViewGuest, |
| weak_factory_.GetWeakPtr(), |
| std::move(before_unload_control_remote))); |
| } |
| |
| void MimeHandlerViewEmbedder::DidCreateMimeHandlerViewGuest( |
| mojo::PendingRemote<mime_handler::BeforeUnloadControl> |
| before_unload_control_remote, |
| content::WebContents* guest_web_contents) { |
| auto* guest_view = MimeHandlerViewGuest::FromWebContents(guest_web_contents); |
| if (!guest_view) |
| return; |
| guest_view->SetBeforeUnloadController( |
| std::move(before_unload_control_remote)); |
| |
| DCHECK(outer_contents_rfh_); |
| DCHECK(render_frame_host_); |
| DCHECK_EQ(outer_contents_rfh_->GetParent(), render_frame_host_); |
| guest_view->SetEmbedderFrame(render_frame_host_->GetGlobalId()); |
| |
| const int embedder_frame_process_id = |
| render_frame_host_->GetProcess()->GetID(); |
| const int element_instance_id = outer_contents_rfh_->GetRoutingID(); |
| const int guest_instance_id = guest_view->guest_instance_id(); |
| |
| // TODO(ekaramad): This URL is used to communicate with |
| // MimeHandlerViewFrameContainer which is only the case if the embedder frame |
| // is the content frame of a plugin element (https://crbug.com/957373). |
| guest_view->set_original_resource_url(resource_url_); |
| guest_view::GuestViewManager::FromBrowserContext( |
| web_contents()->GetBrowserContext()) |
| ->AttachGuest(embedder_frame_process_id, element_instance_id, |
| guest_instance_id, |
| base::DictionaryValue() /* unused attach_params */); |
| // Full page plugin refers to <iframe> or main frame navigations to a |
| // MimeHandlerView resource. In such cases MHVG does not have a frame |
| // container. |
| bool is_full_page = !guest_view->maybe_has_frame_container() && |
| !guest_view->GetEmbedderFrame()->GetParent(); |
| MimeHandlerViewAttachHelper::Get(embedder_frame_process_id) |
| ->AttachToOuterWebContents(guest_view, embedder_frame_process_id, |
| outer_contents_rfh_, element_instance_id, |
| is_full_page /* is_full_page_plugin */); |
| // MHVE is no longer required. |
| DestroySelf(); |
| } |
| |
| mojom::MimeHandlerViewContainerManager* |
| MimeHandlerViewEmbedder::GetContainerManager() { |
| if (!container_manager_) { |
| render_frame_host_->GetRemoteAssociatedInterfaces()->GetInterface( |
| &container_manager_); |
| } |
| return container_manager_.get(); |
| } |
| |
| void MimeHandlerViewEmbedder::ReadyToCreateMimeHandlerView( |
| bool ready_to_create_mime_handler_view) { |
| ready_to_create_mime_handler_view_ = ready_to_create_mime_handler_view; |
| if (!ready_to_create_mime_handler_view_) |
| DestroySelf(); |
| } |
| |
| void MimeHandlerViewEmbedder::OnFrameSandboxed() { |
| DCHECK(!render_frame_host_); |
| DCHECK(!container_manager_); |
| DestroySelf(); |
| } |
| |
| } // namespace extensions |