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/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/containers/flat_map.h"
11 #include "base/metrics/histogram_functions.h"
12 #include "base/no_destructor.h"
13 #include "base/stl_util.h"
14 #include "base/strings/string_util.h"
15 #include "content/public/common/webplugininfo.h"
16 #include "content/public/renderer/render_frame.h"
17 #include "extensions/common/mojom/guest_view.mojom.h"
18 #include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container.h"
19 #include "extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.h"
20 #include "third_party/blink/public/web/web_document.h"
21 #include "third_party/blink/public/web/web_frame.h"
22 #include "third_party/blink/public/web/web_local_frame.h"
23 #include "url/url_constants.h"
24
25 namespace extensions {
26
27 namespace {
28
29 using UMAType = MimeHandlerViewUMATypes::Type;
30
31 using RenderFrameMap =
32 base::flat_map<int32_t, std::unique_ptr<MimeHandlerViewContainerManager>>;
33
GetRenderFrameMap()34 RenderFrameMap* GetRenderFrameMap() {
35 static base::NoDestructor<RenderFrameMap> instance;
36 return instance.get();
37 }
38
39 } // namespace
40
41 // static
BindReceiver(int32_t routing_id,mojo::PendingAssociatedReceiver<mojom::MimeHandlerViewContainerManager> receiver)42 void MimeHandlerViewContainerManager::BindReceiver(
43 int32_t routing_id,
44 mojo::PendingAssociatedReceiver<mojom::MimeHandlerViewContainerManager>
45 receiver) {
46 auto* render_frame = content::RenderFrame::FromRoutingID(routing_id);
47 if (!render_frame)
48 return;
49 auto* manager = Get(render_frame, true /* create_if_does_not_exist */);
50 manager->receivers_.Add(manager, std::move(receiver));
51 }
52
53 // static
Get(content::RenderFrame * render_frame,bool create_if_does_not_exits)54 MimeHandlerViewContainerManager* MimeHandlerViewContainerManager::Get(
55 content::RenderFrame* render_frame,
56 bool create_if_does_not_exits) {
57 if (!render_frame) {
58 // Through some |adoptNode| magic, blink could still call this method for
59 // a plugin element which does not have a frame (https://crbug.com/966371).
60 return nullptr;
61 }
62 int32_t routing_id = render_frame->GetRoutingID();
63 auto& map = *GetRenderFrameMap();
64 if (base::Contains(map, routing_id))
65 return map[routing_id].get();
66 if (create_if_does_not_exits) {
67 map[routing_id] =
68 std::make_unique<MimeHandlerViewContainerManager>(render_frame);
69 return map[routing_id].get();
70 }
71 return nullptr;
72 }
73
CreateFrameContainer(const blink::WebElement & plugin_element,const GURL & resource_url,const std::string & mime_type,const content::WebPluginInfo & plugin_info)74 bool MimeHandlerViewContainerManager::CreateFrameContainer(
75 const blink::WebElement& plugin_element,
76 const GURL& resource_url,
77 const std::string& mime_type,
78 const content::WebPluginInfo& plugin_info) {
79 if (plugin_info.type != content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN) {
80 // TODO(ekaramad): Rename this plugin type once https://crbug.com/659750 is
81 // fixed. We only create a MHVFC for the plugin types of BrowserPlugin
82 // (which used to create a MimeHandlerViewContainer).
83 return false;
84 }
85 if (IsManagedByContainerManager(plugin_element)) {
86 // This is the one injected by HTML string. Return true so that the
87 // HTMLPlugInElement creates a child frame to be used as the outer
88 // WebContents frame.
89 return true;
90 }
91 if (auto* old_frame_container = GetFrameContainer(plugin_element)) {
92 if (old_frame_container->resource_url().EqualsIgnoringRef(resource_url) &&
93 old_frame_container->mime_type() == mime_type) {
94 RecordInteraction(UMAType::kReuseFrameContaienr);
95 // TODO(ekaramad): Fix page transitions using the 'ref' in GURL (see
96 // https://crbug.com/318458 for context).
97 // This should translate into a same document navigation.
98 return true;
99 }
100 // If there is already a MHVFC for this |plugin_element|, destroy it.
101 RemoveFrameContainerForReason(old_frame_container,
102 UMAType::kRemoveFrameContainerUpdatePlugin,
103 true /* retain_manager */);
104 }
105 RecordInteraction(UMAType::kCreateFrameContainer);
106 auto frame_container = std::make_unique<MimeHandlerViewFrameContainer>(
107 this, plugin_element, resource_url, mime_type);
108 frame_containers_.push_back(std::move(frame_container));
109 return true;
110 }
111
112 void MimeHandlerViewContainerManager::
DidBlockMimeHandlerViewForDisallowedPlugin(const blink::WebElement & plugin_element)113 DidBlockMimeHandlerViewForDisallowedPlugin(
114 const blink::WebElement& plugin_element) {
115 if (IsManagedByContainerManager(plugin_element)) {
116 // This is the one injected by HTML string. Return true so that the
117 // HTMLPlugInElement creates a child frame to be used as the outer
118 // WebContents frame.
119 MimeHandlerViewContainer::GuestView()->ReadyToCreateMimeHandlerView(
120 render_frame()->GetRoutingID(), false);
121 }
122 }
123
GetScriptableObject(const blink::WebElement & plugin_element,v8::Isolate * isolate)124 v8::Local<v8::Object> MimeHandlerViewContainerManager::GetScriptableObject(
125 const blink::WebElement& plugin_element,
126 v8::Isolate* isolate) {
127 if (IsManagedByContainerManager(plugin_element)) {
128 return GetPostMessageSupport()->GetScriptableObject(isolate);
129 }
130 if (auto* frame_container = GetFrameContainer(plugin_element)) {
131 return frame_container->post_message_support()->GetScriptableObject(
132 isolate);
133 }
134 return v8::Local<v8::Object>();
135 }
136
MimeHandlerViewContainerManager(content::RenderFrame * render_frame)137 MimeHandlerViewContainerManager::MimeHandlerViewContainerManager(
138 content::RenderFrame* render_frame)
139 : content::RenderFrameObserver(render_frame) {}
140
~MimeHandlerViewContainerManager()141 MimeHandlerViewContainerManager::~MimeHandlerViewContainerManager() {}
142
ReadyToCommitNavigation(blink::WebDocumentLoader * document_loader)143 void MimeHandlerViewContainerManager::ReadyToCommitNavigation(
144 blink::WebDocumentLoader* document_loader) {
145 // At this point the <embed> element is not yet attached to the DOM and the
146 // |post_message_support_|, if any, had been created for a previous page. Note
147 // that this scenario comes up when a same-process navigation between two PDF
148 // files happens in the same RenderFrame (e.g., some of
149 // PDFExtensionLoadTest tests).
150 post_message_support_.reset();
151 plugin_element_ = blink::WebElement();
152 }
153
OnDestruct()154 void MimeHandlerViewContainerManager::OnDestruct() {
155 receivers_.Clear();
156 // This will delete the class.
157 GetRenderFrameMap()->erase(routing_id());
158 }
159
SetInternalId(const std::string & token_id)160 void MimeHandlerViewContainerManager::SetInternalId(
161 const std::string& token_id) {
162 internal_id_ = token_id;
163 }
164
LoadEmptyPage(const GURL & resource_url)165 void MimeHandlerViewContainerManager::LoadEmptyPage(const GURL& resource_url) {
166 // TODO(ekaramad): Add test coverage to ensure document ends up empty (see
167 // https://crbug.com/968350).
168 render_frame()->LoadHTMLString("<!doctype html>", resource_url, "UTF-8",
169 resource_url, true /* replace_current_item */);
170 GetRenderFrameMap()->erase(render_frame()->GetRoutingID());
171 }
172
CreateBeforeUnloadControl(CreateBeforeUnloadControlCallback callback)173 void MimeHandlerViewContainerManager::CreateBeforeUnloadControl(
174 CreateBeforeUnloadControlCallback callback) {
175 if (!post_message_support_)
176 post_message_support_ = std::make_unique<PostMessageSupport>(this);
177 // It might be bound when reloading the same page.
178 before_unload_control_receiver_.reset();
179 std::move(callback).Run(
180 before_unload_control_receiver_.BindNewPipeAndPassRemote());
181 }
182
SelfDeleteIfNecessary()183 void MimeHandlerViewContainerManager::SelfDeleteIfNecessary() {
184 // |internal_id| is only populated when |render_frame()| is embedder of a
185 // MimeHandlerViewGuest. Full-page PDF is one such case. In these cases
186 // we don't want to self-delete.
187 if (!frame_containers_.empty() || !internal_id_.empty())
188 return;
189
190 // There are no frame containers left, and we're not serving a full-page
191 // MimeHandlerView, so we remove ourselves from the map.
192 GetRenderFrameMap()->erase(routing_id());
193 }
194
DestroyFrameContainer(int32_t element_instance_id)195 void MimeHandlerViewContainerManager::DestroyFrameContainer(
196 int32_t element_instance_id) {
197 if (auto* frame_container = GetFrameContainer(element_instance_id))
198 RemoveFrameContainer(frame_container, false /* retain manager */);
199 else
200 SelfDeleteIfNecessary();
201 }
202
DidLoad(int32_t element_instance_id,const GURL & resource_url)203 void MimeHandlerViewContainerManager::DidLoad(int32_t element_instance_id,
204 const GURL& resource_url) {
205 RecordInteraction(UMAType::kDidLoadExtension);
206 if (post_message_support_ && !post_message_support_->is_active()) {
207 // We don't need verification here, if any MHV has loaded inside this
208 // |render_frame()| then the one corresponding to full-page must have done
209 // so first.
210 post_message_support_->SetActive();
211 return;
212 }
213 for (auto& frame_container : frame_containers_) {
214 if (frame_container->post_message_support()->is_active())
215 continue;
216 if (frame_container->resource_url() != resource_url)
217 continue;
218 // To ensure the postMessages will be sent to the right target frame, we
219 // double check with the browser if the target frame's Routing ID makes
220 // sense.
221 if (!frame_container->AreFramesAlive()) {
222 // At this point we expect a content frame inside the |plugin_element|
223 // as well as a first child corresponding to the <iframe> used to attach
224 // MimeHandlerViewGuest.
225 return;
226 }
227 frame_container->set_element_instance_id(element_instance_id);
228 auto* content_frame = frame_container->GetContentFrame();
229 int32_t content_frame_routing_id =
230 content::RenderFrame::GetRoutingIdForWebFrame(content_frame);
231 int32_t guest_frame_routing_id =
232 content::RenderFrame::GetRoutingIdForWebFrame(
233 content_frame->FirstChild());
234 // TODO(ekaramad); The routing IDs heere might have changed since the plugin
235 // has been navigated to load MimeHandlerView. We should double check these
236 // with the browser first (https://crbug.com/957373).
237 // This will end up activating the post_message_support(). The routing IDs
238 // are double checked in every upcoming call to GetTargetFrame() to ensure
239 // postMessages are sent to the intended WebFrame only.
240 frame_container->SetRoutingIds(content_frame_routing_id,
241 guest_frame_routing_id);
242
243 return;
244 }
245 }
246
247 MimeHandlerViewFrameContainer*
GetFrameContainer(int32_t mime_handler_view_instance_id)248 MimeHandlerViewContainerManager::GetFrameContainer(
249 int32_t mime_handler_view_instance_id) {
250 for (auto& frame_container : frame_containers_) {
251 if (frame_container->element_instance_id() == mime_handler_view_instance_id)
252 return frame_container.get();
253 }
254 return nullptr;
255 }
256
257 MimeHandlerViewFrameContainer*
GetFrameContainer(const blink::WebElement & plugin_element)258 MimeHandlerViewContainerManager::GetFrameContainer(
259 const blink::WebElement& plugin_element) {
260 for (auto& frame_container : frame_containers_) {
261 if (frame_container->plugin_element() == plugin_element)
262 return frame_container.get();
263 }
264 return nullptr;
265 }
266
RemoveFrameContainerForReason(MimeHandlerViewFrameContainer * frame_container,MimeHandlerViewUMATypes::Type event,bool retain_manager)267 void MimeHandlerViewContainerManager::RemoveFrameContainerForReason(
268 MimeHandlerViewFrameContainer* frame_container,
269 MimeHandlerViewUMATypes::Type event,
270 bool retain_manager) {
271 if (!RemoveFrameContainer(frame_container, retain_manager))
272 return;
273 RecordInteraction(event);
274 }
275
RemoveFrameContainer(MimeHandlerViewFrameContainer * frame_container,bool retain_manager)276 bool MimeHandlerViewContainerManager::RemoveFrameContainer(
277 MimeHandlerViewFrameContainer* frame_container,
278 bool retain_manager) {
279 auto it = std::find_if(frame_containers_.cbegin(), frame_containers_.cend(),
280 [&frame_container](const auto& iter) {
281 return iter.get() == frame_container;
282 });
283 if (it == frame_containers_.cend())
284 return false;
285 frame_containers_.erase(it);
286
287 if (!retain_manager)
288 SelfDeleteIfNecessary();
289
290 return true;
291 }
292
SetShowBeforeUnloadDialog(bool show_dialog,SetShowBeforeUnloadDialogCallback callback)293 void MimeHandlerViewContainerManager::SetShowBeforeUnloadDialog(
294 bool show_dialog,
295 SetShowBeforeUnloadDialogCallback callback) {
296 render_frame()->GetWebFrame()->GetDocument().SetShowBeforeUnloadDialog(
297 show_dialog);
298 std::move(callback).Run();
299 }
300
RecordInteraction(UMAType type)301 void MimeHandlerViewContainerManager::RecordInteraction(UMAType type) {
302 base::UmaHistogramEnumeration(MimeHandlerViewUMATypes::kUMAName, type);
303 }
304
GetPostMessageSupport()305 PostMessageSupport* MimeHandlerViewContainerManager::GetPostMessageSupport() {
306 if (!post_message_support_)
307 post_message_support_ = std::make_unique<PostMessageSupport>(this);
308 return post_message_support_.get();
309 }
310
GetSourceFrame()311 blink::WebLocalFrame* MimeHandlerViewContainerManager::GetSourceFrame() {
312 return render_frame()->GetWebFrame();
313 }
314
GetTargetFrame()315 blink::WebFrame* MimeHandlerViewContainerManager::GetTargetFrame() {
316 // Search for the frame using the 'name' attribute, since if an extension
317 // injects other frames into the embedder, there will be more than one frame.
318 return GetSourceFrame()->FindFrameByName(
319 blink::WebString::FromUTF8(internal_id_));
320 }
321
IsEmbedded() const322 bool MimeHandlerViewContainerManager::IsEmbedded() const {
323 return false;
324 }
325
IsResourceAccessibleBySource() const326 bool MimeHandlerViewContainerManager::IsResourceAccessibleBySource() const {
327 return true;
328 }
329
IsManagedByContainerManager(const blink::WebElement & plugin_element)330 bool MimeHandlerViewContainerManager::IsManagedByContainerManager(
331 const blink::WebElement& plugin_element) {
332 if (plugin_element_.IsNull() && plugin_element.HasAttribute("internalid") &&
333 base::ToUpperASCII(plugin_element.GetAttribute("internalid").Utf8()) ==
334 internal_id_) {
335 plugin_element_ = plugin_element;
336 MimeHandlerViewContainer::GuestView()->ReadyToCreateMimeHandlerView(
337 render_frame()->GetRoutingID(), true);
338 }
339 return plugin_element_ == plugin_element;
340 }
341
342 } // namespace extensions
343