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