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