1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_embedder.h"
6
7 #include "base/containers/flat_map.h"
8 #include "base/memory/ptr_util.h"
9 #include "base/no_destructor.h"
10 #include "components/guest_view/browser/guest_view_manager.h"
11 #include "components/guest_view/browser/guest_view_manager_delegate.h"
12 #include "content/public/browser/navigation_handle.h"
13 #include "content/public/browser/render_frame_host.h"
14 #include "content/public/browser/render_process_host.h"
15 #include "content/public/browser/web_contents.h"
16 #include "extensions/browser/api/extensions_api_client.h"
17 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.h"
18 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_constants.h"
19 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
20 #include "extensions/common/mojom/guest_view.mojom.h"
21 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
22 #include "third_party/blink/public/common/frame/frame_owner_element_type.h"
23 #include "third_party/blink/public/common/frame/sandbox_flags.h"
24
25 namespace extensions {
26
27 namespace {
28 using EmbedderMap =
29 base::flat_map<int32_t, std::unique_ptr<MimeHandlerViewEmbedder>>;
30
GetMimeHandlerViewEmbeddersMap()31 EmbedderMap* GetMimeHandlerViewEmbeddersMap() {
32 static base::NoDestructor<EmbedderMap> instance;
33 return instance.get();
34 }
35 } // namespace
36
37 // static
Get(int32_t frame_tree_node_id)38 MimeHandlerViewEmbedder* MimeHandlerViewEmbedder::Get(
39 int32_t frame_tree_node_id) {
40 const auto& map = *GetMimeHandlerViewEmbeddersMap();
41 auto it = map.find(frame_tree_node_id);
42 if (it == map.cend())
43 return nullptr;
44 return it->second.get();
45 }
46
47 // static
Create(int32_t frame_tree_node_id,const GURL & resource_url,const std::string & mime_type,const std::string & stream_id,const std::string & internal_id)48 void MimeHandlerViewEmbedder::Create(int32_t frame_tree_node_id,
49 const GURL& resource_url,
50 const std::string& mime_type,
51 const std::string& stream_id,
52 const std::string& internal_id) {
53 DCHECK(
54 !base::Contains(*GetMimeHandlerViewEmbeddersMap(), frame_tree_node_id));
55 GetMimeHandlerViewEmbeddersMap()->insert_or_assign(
56 frame_tree_node_id, base::WrapUnique(new MimeHandlerViewEmbedder(
57 frame_tree_node_id, resource_url, mime_type,
58 stream_id, internal_id)));
59 }
60
MimeHandlerViewEmbedder(int32_t frame_tree_node_id,const GURL & resource_url,const std::string & mime_type,const std::string & stream_id,const std::string & internal_id)61 MimeHandlerViewEmbedder::MimeHandlerViewEmbedder(int32_t frame_tree_node_id,
62 const GURL& resource_url,
63 const std::string& mime_type,
64 const std::string& stream_id,
65 const std::string& internal_id)
66 : content::WebContentsObserver(
67 content::WebContents::FromFrameTreeNodeId(frame_tree_node_id)),
68 frame_tree_node_id_(frame_tree_node_id),
69 resource_url_(resource_url),
70 mime_type_(mime_type),
71 stream_id_(stream_id),
72 internal_id_(internal_id) {}
73
~MimeHandlerViewEmbedder()74 MimeHandlerViewEmbedder::~MimeHandlerViewEmbedder() {}
75
DidStartNavigation(content::NavigationHandle * handle)76 void MimeHandlerViewEmbedder::DidStartNavigation(
77 content::NavigationHandle* handle) {
78 // This observer is created after the observed |frame_tree_node_id_| started
79 // its navigation to the |resource_url|. If any new navigations start then
80 // we should stop now and do not create a MHVG. Same document navigations
81 // could occur for {replace,push}State among other reasons and should not
82 // lead to deleting the MVHE (e.g., the test
83 // PDFExtensionLinkClickTest.OpenPDFWithReplaceState reaches here).
84 if (handle->GetFrameTreeNodeId() == frame_tree_node_id_ &&
85 !handle->IsSameDocument()) {
86 GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_);
87 }
88 }
89
ReadyToCommitNavigation(content::NavigationHandle * handle)90 void MimeHandlerViewEmbedder::ReadyToCommitNavigation(
91 content::NavigationHandle* handle) {
92 if (handle->GetFrameTreeNodeId() != frame_tree_node_id_)
93 return;
94 if (!render_frame_host_) {
95 render_frame_host_ = handle->GetRenderFrameHost();
96 GetContainerManager()->SetInternalId(internal_id_);
97 }
98 }
99
DidFinishNavigation(content::NavigationHandle * handle)100 void MimeHandlerViewEmbedder::DidFinishNavigation(
101 content::NavigationHandle* handle) {
102 if (frame_tree_node_id_ != handle->GetFrameTreeNodeId())
103 return;
104 CheckSandboxFlags();
105 }
106
RenderFrameCreated(content::RenderFrameHost * render_frame_host)107 void MimeHandlerViewEmbedder::RenderFrameCreated(
108 content::RenderFrameHost* render_frame_host) {
109 // If an extension injects a frame before we finish attaching the PDF's
110 // iframe, then we don't want to early out by mistake. We also put an
111 // unguessable name element on the <embed> to guard against the possibility
112 // that someone injects an <embed>, but that can be done in a separate CL.
113 if (!render_frame_host_ ||
114 render_frame_host_ != render_frame_host->GetParent() ||
115 render_frame_host_->GetLastCommittedURL() != resource_url_ ||
116 render_frame_host->GetFrameOwnerElementType() !=
117 blink::FrameOwnerElementType::kEmbed ||
118 render_frame_host->GetFrameName() != internal_id_) {
119 return;
120 }
121 if (!ready_to_create_mime_handler_view_) {
122 // Renderer notifies the browser about creating MimeHandlerView right after
123 // HTMLPlugInElement::RequestObject, which is before the plugin element is
124 // navigated.
125 GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_);
126 return;
127 }
128 outer_contents_frame_tree_node_id_ = render_frame_host->GetFrameTreeNodeId();
129 element_instance_id_ = render_frame_host->GetRoutingID();
130 // This suggests that a same-origin child frame is created under the
131 // RFH associated with |frame_tree_node_id_|. This suggests that the HTML
132 // string is loaded in the observed frame's document and now the renderer
133 // can initiate the MimeHandlerViewFrameContainer creation process.
134 // TODO(ekaramad): We shouldn't have to wait for the response from the
135 // renderer; instead we should proceed with creating MHVG. Instead, the
136 // interface request in MHVG for beforeunload should wait until this response
137 // comes back.
138 GetContainerManager()->CreateBeforeUnloadControl(
139 base::BindOnce(&MimeHandlerViewEmbedder::CreateMimeHandlerViewGuest,
140 weak_factory_.GetWeakPtr()));
141 }
142
FrameDeleted(content::RenderFrameHost * render_frame_host)143 void MimeHandlerViewEmbedder::FrameDeleted(
144 content::RenderFrameHost* render_frame_host) {
145 if (render_frame_host->GetFrameTreeNodeId() == frame_tree_node_id_ ||
146 render_frame_host->GetFrameTreeNodeId() ==
147 outer_contents_frame_tree_node_id_) {
148 GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_);
149 }
150 }
151
CreateMimeHandlerViewGuest(mojo::PendingRemote<mime_handler::BeforeUnloadControl> before_unload_control)152 void MimeHandlerViewEmbedder::CreateMimeHandlerViewGuest(
153 mojo::PendingRemote<mime_handler::BeforeUnloadControl>
154 before_unload_control) {
155 auto* browser_context = web_contents()->GetBrowserContext();
156 auto* manager =
157 guest_view::GuestViewManager::FromBrowserContext(browser_context);
158 if (!manager) {
159 manager = guest_view::GuestViewManager::CreateWithDelegate(
160 browser_context,
161 ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(
162 browser_context));
163 }
164 pending_before_unload_control_ = std::move(before_unload_control);
165 base::DictionaryValue create_params;
166 create_params.SetString(mime_handler_view::kViewId, stream_id_);
167 manager->CreateGuest(
168 MimeHandlerViewGuest::Type, web_contents(), create_params,
169 base::BindOnce(&MimeHandlerViewEmbedder::DidCreateMimeHandlerViewGuest,
170 weak_factory_.GetWeakPtr()));
171 }
172
DidCreateMimeHandlerViewGuest(content::WebContents * guest_web_contents)173 void MimeHandlerViewEmbedder::DidCreateMimeHandlerViewGuest(
174 content::WebContents* guest_web_contents) {
175 auto* guest_view = MimeHandlerViewGuest::FromWebContents(guest_web_contents);
176 if (!guest_view)
177 return;
178 // Manager was created earlier in the stack in CreateMimeHandlerViewGuest (if
179 // it had not existed before that).
180 guest_view->SetBeforeUnloadController(
181 std::move(pending_before_unload_control_));
182 int guest_instance_id = guest_view->guest_instance_id();
183 auto* outer_contents_rfh = web_contents()->UnsafeFindFrameByFrameTreeNodeId(
184 outer_contents_frame_tree_node_id_);
185 int32_t embedder_frame_process_id =
186 outer_contents_rfh->GetParent()->GetProcess()->GetID();
187 guest_view->SetEmbedderFrame(embedder_frame_process_id,
188 outer_contents_rfh->GetParent()->GetRoutingID());
189 // TODO(ekaramad): This URL is used to communicate with
190 // MimeHandlerViewFrameContainer which is only the case if the embedder frame
191 // is the content frame of a plugin element (https://crbug.com/957373).
192 guest_view->set_original_resource_url(resource_url_);
193 guest_view::GuestViewManager::FromBrowserContext(
194 web_contents()->GetBrowserContext())
195 ->AttachGuest(embedder_frame_process_id, element_instance_id_,
196 guest_instance_id,
197 base::DictionaryValue() /* unused attach_params */);
198 // Full page plugin refers to <iframe> or main frame navigations to a
199 // MimeHandlerView resource. In such cases MHVG does not have a frame
200 // container.
201 bool is_full_page = !guest_view->maybe_has_frame_container() &&
202 !guest_view->GetEmbedderFrame()->GetParent();
203 MimeHandlerViewAttachHelper::Get(embedder_frame_process_id)
204 ->AttachToOuterWebContents(guest_view, embedder_frame_process_id,
205 outer_contents_rfh, element_instance_id_,
206 is_full_page /* is_full_page_plugin */);
207 // MHVE is no longer required.
208 GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_);
209 }
210
211 mojom::MimeHandlerViewContainerManager*
GetContainerManager()212 MimeHandlerViewEmbedder::GetContainerManager() {
213 if (!container_manager_) {
214 render_frame_host_->GetRemoteAssociatedInterfaces()->GetInterface(
215 &container_manager_);
216 }
217 return container_manager_.get();
218 }
219
ReadyToCreateMimeHandlerView(bool ready_to_create_mime_handler_view)220 void MimeHandlerViewEmbedder::ReadyToCreateMimeHandlerView(
221 bool ready_to_create_mime_handler_view) {
222 ready_to_create_mime_handler_view_ = ready_to_create_mime_handler_view;
223 if (!ready_to_create_mime_handler_view_)
224 GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_);
225 }
226
CheckSandboxFlags()227 void MimeHandlerViewEmbedder::CheckSandboxFlags() {
228 // If the FrameTreeNode is deleted while it has ownership of the ongoing
229 // NavigationRequest, DidFinishNavigation is called before FrameDeleted (see
230 // https://crbug.com/969840).
231 if (render_frame_host_ && !render_frame_host_->IsSandboxed(
232 blink::mojom::WebSandboxFlags::kPlugins)) {
233 return;
234 }
235 if (render_frame_host_) {
236 // Notify the renderer to load an empty page instead.
237 GetContainerManager()->LoadEmptyPage(resource_url_);
238 }
239 GetMimeHandlerViewEmbeddersMap()->erase(frame_tree_node_id_);
240 }
241
242 } // namespace extensions
243