• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2015 The Chromium Embedded Framework Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4 
5 #include "libcef/browser/browser_info_manager.h"
6 
7 #include <utility>
8 
9 #include "libcef/browser/browser_host_base.h"
10 #include "libcef/browser/browser_platform_delegate.h"
11 #include "libcef/browser/extensions/browser_extensions_util.h"
12 #include "libcef/browser/thread_util.h"
13 #include "libcef/common/cef_switches.h"
14 #include "libcef/common/extensions/extensions_util.h"
15 #include "libcef/common/frame_util.h"
16 #include "libcef/common/values_impl.h"
17 #include "libcef/features/runtime_checks.h"
18 
19 #include "base/command_line.h"
20 #include "base/logging.h"
21 #include "base/threading/sequenced_task_runner_handle.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/child_process_host.h"
26 #include "content/public/common/url_constants.h"
27 
28 namespace {
29 
30 // Timeout delay for new browser info responses.
31 const int64_t kNewBrowserInfoResponseTimeoutMs = 2000;
32 
TranslatePopupFeatures(const blink::mojom::WindowFeatures & webKitFeatures,CefPopupFeatures & features)33 void TranslatePopupFeatures(const blink::mojom::WindowFeatures& webKitFeatures,
34                             CefPopupFeatures& features) {
35   features.x = static_cast<int>(webKitFeatures.x);
36   features.xSet = webKitFeatures.has_x;
37   features.y = static_cast<int>(webKitFeatures.y);
38   features.ySet = webKitFeatures.has_y;
39   features.width = static_cast<int>(webKitFeatures.width);
40   features.widthSet = webKitFeatures.has_width;
41   features.height = static_cast<int>(webKitFeatures.height);
42   features.heightSet = webKitFeatures.has_height;
43 
44   features.menuBarVisible = webKitFeatures.menu_bar_visible;
45   features.statusBarVisible = webKitFeatures.status_bar_visible;
46   features.toolBarVisible = webKitFeatures.tool_bar_visible;
47   features.scrollbarsVisible = webKitFeatures.scrollbars_visible;
48 }
49 
50 CefBrowserInfoManager* g_info_manager = nullptr;
51 
52 }  // namespace
53 
CefBrowserInfoManager()54 CefBrowserInfoManager::CefBrowserInfoManager() {
55   DCHECK(!g_info_manager);
56   g_info_manager = this;
57 }
58 
~CefBrowserInfoManager()59 CefBrowserInfoManager::~CefBrowserInfoManager() {
60   DCHECK(browser_info_list_.empty());
61   g_info_manager = nullptr;
62 }
63 
64 // static
GetInstance()65 CefBrowserInfoManager* CefBrowserInfoManager::GetInstance() {
66   return g_info_manager;
67 }
68 
CreateBrowserInfo(bool is_popup,bool is_windowless,CefRefPtr<CefDictionaryValue> extra_info)69 scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::CreateBrowserInfo(
70     bool is_popup,
71     bool is_windowless,
72     CefRefPtr<CefDictionaryValue> extra_info) {
73   base::AutoLock lock_scope(browser_info_lock_);
74 
75   scoped_refptr<CefBrowserInfo> browser_info = new CefBrowserInfo(
76       ++next_browser_id_, is_popup, is_windowless, extra_info);
77   browser_info_list_.push_back(browser_info);
78 
79   return browser_info;
80 }
81 
CreatePopupBrowserInfo(content::WebContents * new_contents,bool is_windowless,CefRefPtr<CefDictionaryValue> extra_info)82 scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::CreatePopupBrowserInfo(
83     content::WebContents* new_contents,
84     bool is_windowless,
85     CefRefPtr<CefDictionaryValue> extra_info) {
86   base::AutoLock lock_scope(browser_info_lock_);
87 
88   auto frame_host = new_contents->GetMainFrame();
89 
90   scoped_refptr<CefBrowserInfo> browser_info =
91       new CefBrowserInfo(++next_browser_id_, true, is_windowless, extra_info);
92   browser_info_list_.push_back(browser_info);
93 
94   // Continue any pending NewBrowserInfo request.
95   auto it = pending_new_browser_info_map_.find(frame_host->GetGlobalId());
96   if (it != pending_new_browser_info_map_.end()) {
97     SendNewBrowserInfoResponse(browser_info, /*is_guest_view=*/false,
98                                std::move(it->second->callback),
99                                it->second->callback_runner);
100     pending_new_browser_info_map_.erase(it);
101   }
102 
103   return browser_info;
104 }
105 
CanCreateWindow(content::RenderFrameHost * opener,const GURL & target_url,const content::Referrer & referrer,const std::string & frame_name,WindowOpenDisposition disposition,const blink::mojom::WindowFeatures & features,bool user_gesture,bool opener_suppressed,bool * no_javascript_access)106 bool CefBrowserInfoManager::CanCreateWindow(
107     content::RenderFrameHost* opener,
108     const GURL& target_url,
109     const content::Referrer& referrer,
110     const std::string& frame_name,
111     WindowOpenDisposition disposition,
112     const blink::mojom::WindowFeatures& features,
113     bool user_gesture,
114     bool opener_suppressed,
115     bool* no_javascript_access) {
116   CEF_REQUIRE_UIT();
117 
118   content::OpenURLParams params(target_url, referrer, disposition,
119                                 ui::PAGE_TRANSITION_LINK,
120                                 /*is_renderer_initiated=*/true);
121   params.user_gesture = user_gesture;
122 
123   CefRefPtr<CefBrowserHostBase> browser;
124   if (!MaybeAllowNavigation(opener, params, browser) || !browser) {
125     // Cancel the popup.
126     return false;
127   }
128 
129   CefRefPtr<CefClient> client = browser->GetClient();
130   bool allow = true;
131 
132   std::unique_ptr<CefWindowInfo> window_info(new CefWindowInfo);
133 
134 #if BUILDFLAG(IS_WIN)
135   window_info->SetAsPopup(nullptr, CefString());
136 #endif
137 
138   auto pending_popup = std::make_unique<CefBrowserInfoManager::PendingPopup>();
139   pending_popup->step = CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW;
140   pending_popup->opener_global_id = opener->GetGlobalId();
141   pending_popup->target_url = target_url;
142   pending_popup->target_frame_name = frame_name;
143 
144   // Start with the current browser's settings.
145   pending_popup->client = client;
146   pending_popup->settings = browser->settings();
147 
148   if (client.get()) {
149     CefRefPtr<CefLifeSpanHandler> handler = client->GetLifeSpanHandler();
150     if (handler.get()) {
151       CefRefPtr<CefFrame> opener_frame = browser->GetFrameForHost(opener);
152       DCHECK(opener_frame);
153 
154       CefPopupFeatures cef_features;
155       TranslatePopupFeatures(features, cef_features);
156 
157 #if (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC))
158       // Default to the size from the popup features.
159       if (cef_features.xSet)
160         window_info->bounds.x = cef_features.x;
161       if (cef_features.ySet)
162         window_info->bounds.y = cef_features.y;
163       if (cef_features.widthSet)
164         window_info->bounds.width = cef_features.width;
165       if (cef_features.heightSet)
166         window_info->bounds.height = cef_features.height;
167 #endif
168 
169       allow = !handler->OnBeforePopup(
170           browser.get(), opener_frame, pending_popup->target_url.spec(),
171           pending_popup->target_frame_name,
172           static_cast<cef_window_open_disposition_t>(disposition), user_gesture,
173           cef_features, *window_info, pending_popup->client,
174           pending_popup->settings, pending_popup->extra_info,
175           no_javascript_access);
176     }
177   }
178 
179   if (allow) {
180     CefBrowserCreateParams create_params;
181 
182     if (browser->HasView()) {
183       create_params.popup_with_views_hosted_opener = true;
184     } else {
185       create_params.window_info = std::move(window_info);
186     }
187 
188     create_params.settings = pending_popup->settings;
189     create_params.client = pending_popup->client;
190     create_params.extra_info = pending_popup->extra_info;
191 
192     pending_popup->platform_delegate =
193         CefBrowserPlatformDelegate::Create(create_params);
194     CHECK(pending_popup->platform_delegate.get());
195 
196     // Between the calls to CanCreateWindow and GetCustomWebContentsView
197     // RenderViewHostImpl::CreateNewWindow() will call
198     // RenderProcessHostImpl::FilterURL() which, in the case of "javascript:"
199     // URIs, rewrites the URL to "about:blank". We need to apply the same filter
200     // otherwise GetCustomWebContentsView will fail to retrieve the PopupInfo.
201     opener->GetProcess()->FilterURL(false, &pending_popup->target_url);
202 
203     PushPendingPopup(std::move(pending_popup));
204   }
205 
206   return allow;
207 }
208 
GetCustomWebContentsView(const GURL & target_url,const content::GlobalRenderFrameHostId & opener_global_id,content::WebContentsView ** view,content::RenderViewHostDelegateView ** delegate_view)209 void CefBrowserInfoManager::GetCustomWebContentsView(
210     const GURL& target_url,
211     const content::GlobalRenderFrameHostId& opener_global_id,
212     content::WebContentsView** view,
213     content::RenderViewHostDelegateView** delegate_view) {
214   CEF_REQUIRE_UIT();
215   REQUIRE_ALLOY_RUNTIME();
216 
217   std::unique_ptr<CefBrowserInfoManager::PendingPopup> pending_popup =
218       PopPendingPopup(CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW,
219                       opener_global_id, target_url);
220   DCHECK(pending_popup.get());
221   DCHECK(pending_popup->platform_delegate.get());
222 
223   if (pending_popup->platform_delegate->IsWindowless()) {
224     pending_popup->platform_delegate->CreateViewForWebContents(view,
225                                                                delegate_view);
226   }
227 
228   pending_popup->step =
229       CefBrowserInfoManager::PendingPopup::GET_CUSTOM_WEB_CONTENTS_VIEW;
230   PushPendingPopup(std::move(pending_popup));
231 }
232 
WebContentsCreated(const GURL & target_url,const content::GlobalRenderFrameHostId & opener_global_id,CefBrowserSettings & settings,CefRefPtr<CefClient> & client,std::unique_ptr<CefBrowserPlatformDelegate> & platform_delegate,CefRefPtr<CefDictionaryValue> & extra_info)233 void CefBrowserInfoManager::WebContentsCreated(
234     const GURL& target_url,
235     const content::GlobalRenderFrameHostId& opener_global_id,
236     CefBrowserSettings& settings,
237     CefRefPtr<CefClient>& client,
238     std::unique_ptr<CefBrowserPlatformDelegate>& platform_delegate,
239     CefRefPtr<CefDictionaryValue>& extra_info) {
240   CEF_REQUIRE_UIT();
241 
242   // GET_CUSTOM_WEB_CONTENTS_VIEW is only used with the alloy runtime.
243   const auto previous_step =
244       cef::IsAlloyRuntimeEnabled()
245           ? CefBrowserInfoManager::PendingPopup::GET_CUSTOM_WEB_CONTENTS_VIEW
246           : CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW;
247 
248   std::unique_ptr<CefBrowserInfoManager::PendingPopup> pending_popup =
249       PopPendingPopup(previous_step, opener_global_id, target_url);
250   DCHECK(pending_popup.get());
251   DCHECK(pending_popup->platform_delegate.get());
252 
253   settings = pending_popup->settings;
254   client = pending_popup->client;
255   platform_delegate = std::move(pending_popup->platform_delegate);
256   extra_info = pending_popup->extra_info;
257 }
258 
OnGetNewBrowserInfo(const content::GlobalRenderFrameHostId & global_id,cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback)259 void CefBrowserInfoManager::OnGetNewBrowserInfo(
260     const content::GlobalRenderFrameHostId& global_id,
261     cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback) {
262   DCHECK(frame_util::IsValidGlobalId(global_id));
263   DCHECK(callback);
264 
265   auto callback_runner = base::SequencedTaskRunnerHandle::Get();
266 
267   base::AutoLock lock_scope(browser_info_lock_);
268 
269   bool is_guest_view = false;
270 
271   scoped_refptr<CefBrowserInfo> browser_info =
272       GetBrowserInfoInternal(global_id, &is_guest_view);
273 
274   if (browser_info) {
275     // Send the response immediately.
276     SendNewBrowserInfoResponse(browser_info, is_guest_view, std::move(callback),
277                                callback_runner);
278     return;
279   }
280 
281   // Verify that no request for the same route is currently queued.
282   DCHECK(pending_new_browser_info_map_.find(global_id) ==
283          pending_new_browser_info_map_.end());
284 
285   const int timeout_id = ++next_timeout_id_;
286 
287   // Queue the request.
288   std::unique_ptr<PendingNewBrowserInfo> pending(new PendingNewBrowserInfo());
289   pending->global_id = global_id;
290   pending->timeout_id = timeout_id;
291   pending->callback = std::move(callback);
292   pending->callback_runner = callback_runner;
293   pending_new_browser_info_map_.insert(
294       std::make_pair(global_id, std::move(pending)));
295 
296   // Register a timeout for the pending response so that the renderer process
297   // doesn't hang forever.
298   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
299           switches::kDisableNewBrowserInfoTimeout)) {
300     CEF_POST_DELAYED_TASK(
301         CEF_UIT,
302         base::BindOnce(&CefBrowserInfoManager::TimeoutNewBrowserInfoResponse,
303                        global_id, timeout_id),
304         kNewBrowserInfoResponseTimeoutMs);
305   }
306 }
307 
RemoveBrowserInfo(scoped_refptr<CefBrowserInfo> browser_info)308 void CefBrowserInfoManager::RemoveBrowserInfo(
309     scoped_refptr<CefBrowserInfo> browser_info) {
310   base::AutoLock lock_scope(browser_info_lock_);
311 
312   BrowserInfoList::iterator it = browser_info_list_.begin();
313   for (; it != browser_info_list_.end(); ++it) {
314     if (*it == browser_info) {
315       browser_info_list_.erase(it);
316       return;
317     }
318   }
319 
320   NOTREACHED();
321 }
322 
DestroyAllBrowsers()323 void CefBrowserInfoManager::DestroyAllBrowsers() {
324   BrowserInfoList list;
325 
326   {
327     base::AutoLock lock_scope(browser_info_lock_);
328     list = browser_info_list_;
329   }
330 
331   // Destroy any remaining browser windows.
332   if (!list.empty()) {
333     BrowserInfoList::iterator it = list.begin();
334     for (; it != list.end(); ++it) {
335       CefRefPtr<CefBrowserHostBase> browser = (*it)->browser();
336       DCHECK(browser.get());
337       if (browser.get()) {
338         // DestroyBrowser will call RemoveBrowserInfo.
339         browser->DestroyBrowser();
340       }
341     }
342   }
343 
344 #if DCHECK_IS_ON()
345   {
346     // Verify that all browser windows have been destroyed.
347     base::AutoLock lock_scope(browser_info_lock_);
348     DCHECK(browser_info_list_.empty());
349   }
350 #endif
351 }
352 
GetBrowserInfo(const content::GlobalRenderFrameHostId & global_id,bool * is_guest_view)353 scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::GetBrowserInfo(
354     const content::GlobalRenderFrameHostId& global_id,
355     bool* is_guest_view) {
356   base::AutoLock lock_scope(browser_info_lock_);
357   return GetBrowserInfoInternal(global_id, is_guest_view);
358 }
359 
MaybeAllowNavigation(content::RenderFrameHost * opener,const content::OpenURLParams & params,CefRefPtr<CefBrowserHostBase> & browser_out) const360 bool CefBrowserInfoManager::MaybeAllowNavigation(
361     content::RenderFrameHost* opener,
362     const content::OpenURLParams& params,
363     CefRefPtr<CefBrowserHostBase>& browser_out) const {
364   CEF_REQUIRE_UIT();
365 
366   bool is_guest_view = false;
367   auto browser = extensions::GetOwnerBrowserForHost(opener, &is_guest_view);
368   if (!browser) {
369     // Print preview uses a modal dialog where we don't own the WebContents.
370     // Allow that navigation to proceed.
371     return true;
372   }
373 
374   if (!browser->MaybeAllowNavigation(opener, is_guest_view, params)) {
375     return false;
376   }
377 
378   browser_out = browser;
379   return true;
380 }
381 
382 CefBrowserInfoManager::BrowserInfoList
GetBrowserInfoList()383 CefBrowserInfoManager::GetBrowserInfoList() {
384   base::AutoLock lock_scope(browser_info_lock_);
385   BrowserInfoList copy;
386   copy.assign(browser_info_list_.begin(), browser_info_list_.end());
387   return copy;
388 }
389 
RenderProcessHostDestroyed(content::RenderProcessHost * host)390 void CefBrowserInfoManager::RenderProcessHostDestroyed(
391     content::RenderProcessHost* host) {
392   CEF_REQUIRE_UIT();
393 
394   host->RemoveObserver(this);
395 
396   const int render_process_id = host->GetID();
397   DCHECK_GT(render_process_id, 0);
398 
399   // Remove all pending requests that reference the destroyed host.
400   {
401     base::AutoLock lock_scope(browser_info_lock_);
402 
403     PendingNewBrowserInfoMap::iterator it =
404         pending_new_browser_info_map_.begin();
405     while (it != pending_new_browser_info_map_.end()) {
406       const auto& info = it->second;
407       if (info->global_id.child_id == render_process_id) {
408         CancelNewBrowserInfoResponse(info.get());
409         it = pending_new_browser_info_map_.erase(it);
410       } else {
411         ++it;
412       }
413     }
414   }
415 
416   // Remove all pending popups that reference the destroyed host as the opener.
417   {
418     PendingPopupList::iterator it = pending_popup_list_.begin();
419     while (it != pending_popup_list_.end()) {
420       PendingPopup* popup = it->get();
421       if (popup->opener_global_id.child_id == render_process_id) {
422         it = pending_popup_list_.erase(it);
423       } else {
424         ++it;
425       }
426     }
427   }
428 }
429 
PushPendingPopup(std::unique_ptr<PendingPopup> popup)430 void CefBrowserInfoManager::PushPendingPopup(
431     std::unique_ptr<PendingPopup> popup) {
432   CEF_REQUIRE_UIT();
433   pending_popup_list_.push_back(std::move(popup));
434 }
435 
436 std::unique_ptr<CefBrowserInfoManager::PendingPopup>
PopPendingPopup(PendingPopup::Step step,const content::GlobalRenderFrameHostId & opener_global_id,const GURL & target_url)437 CefBrowserInfoManager::PopPendingPopup(
438     PendingPopup::Step step,
439     const content::GlobalRenderFrameHostId& opener_global_id,
440     const GURL& target_url) {
441   CEF_REQUIRE_UIT();
442   DCHECK(frame_util::IsValidGlobalId(opener_global_id));
443 
444   PendingPopupList::iterator it = pending_popup_list_.begin();
445   for (; it != pending_popup_list_.end(); ++it) {
446     PendingPopup* popup = it->get();
447     if (popup->step == step && popup->opener_global_id == opener_global_id &&
448         popup->target_url == target_url) {
449       // Transfer ownership of the pointer.
450       it->release();
451       pending_popup_list_.erase(it);
452       return base::WrapUnique(popup);
453     }
454   }
455 
456   return nullptr;
457 }
458 
GetBrowserInfoInternal(const content::GlobalRenderFrameHostId & global_id,bool * is_guest_view)459 scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::GetBrowserInfoInternal(
460     const content::GlobalRenderFrameHostId& global_id,
461     bool* is_guest_view) {
462   browser_info_lock_.AssertAcquired();
463 
464   if (is_guest_view)
465     *is_guest_view = false;
466 
467   if (!frame_util::IsValidGlobalId(global_id))
468     return nullptr;
469 
470   for (const auto& browser_info : browser_info_list_) {
471     bool is_guest_view_tmp;
472     auto frame =
473         browser_info->GetFrameForGlobalId(global_id, &is_guest_view_tmp);
474     if (frame || is_guest_view_tmp) {
475       if (is_guest_view)
476         *is_guest_view = is_guest_view_tmp;
477       return browser_info;
478     }
479   }
480 
481   return nullptr;
482 }
483 
484 // static
SendNewBrowserInfoResponse(scoped_refptr<CefBrowserInfo> browser_info,bool is_guest_view,cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback,scoped_refptr<base::SequencedTaskRunner> callback_runner)485 void CefBrowserInfoManager::SendNewBrowserInfoResponse(
486     scoped_refptr<CefBrowserInfo> browser_info,
487     bool is_guest_view,
488     cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback,
489     scoped_refptr<base::SequencedTaskRunner> callback_runner) {
490   if (!callback_runner->RunsTasksInCurrentSequence()) {
491     callback_runner->PostTask(
492         FROM_HERE,
493         base::BindOnce(&CefBrowserInfoManager::SendNewBrowserInfoResponse,
494                        browser_info, is_guest_view, std::move(callback),
495                        callback_runner));
496     return;
497   }
498 
499   auto params = cef::mojom::NewBrowserInfo::New();
500   params->is_guest_view = is_guest_view;
501 
502   if (browser_info) {
503     params->browser_id = browser_info->browser_id();
504     params->is_windowless = browser_info->is_windowless();
505     params->is_popup = browser_info->is_popup();
506 
507     auto extra_info = browser_info->extra_info();
508     if (extra_info) {
509       auto extra_info_impl =
510           static_cast<CefDictionaryValueImpl*>(extra_info.get());
511       auto extra_info_value = base::WrapUnique(extra_info_impl->CopyValue());
512       params->extra_info = std::move(*extra_info_value);
513     }
514   } else {
515     // The new browser info response has timed out.
516     params->browser_id = -1;
517   }
518 
519   std::move(callback).Run(std::move(params));
520 }
521 
522 // static
CancelNewBrowserInfoResponse(PendingNewBrowserInfo * pending_info)523 void CefBrowserInfoManager::CancelNewBrowserInfoResponse(
524     PendingNewBrowserInfo* pending_info) {
525   SendNewBrowserInfoResponse(/*browser_info=*/nullptr, /*is_guest_view=*/false,
526                              std::move(pending_info->callback),
527                              pending_info->callback_runner);
528 }
529 
530 // static
TimeoutNewBrowserInfoResponse(const content::GlobalRenderFrameHostId & global_id,int timeout_id)531 void CefBrowserInfoManager::TimeoutNewBrowserInfoResponse(
532     const content::GlobalRenderFrameHostId& global_id,
533     int timeout_id) {
534   CEF_REQUIRE_UIT();
535   if (!g_info_manager)
536     return;
537 
538   base::AutoLock lock_scope(g_info_manager->browser_info_lock_);
539 
540   // Continue the NewBrowserInfo request if it's still pending.
541   auto it = g_info_manager->pending_new_browser_info_map_.find(global_id);
542   if (it != g_info_manager->pending_new_browser_info_map_.end()) {
543     const auto& pending_info = it->second;
544     // Don't accidentally timeout a new request for the same frame.
545     if (pending_info->timeout_id != timeout_id)
546       return;
547 
548 #if DCHECK_IS_ON()
549     // This method should never be called for a PDF renderer.
550     content::RenderProcessHost* process =
551         content::RenderProcessHost::FromID(global_id.child_id);
552     DCHECK(!process || !process->IsPdf());
553 #endif
554 
555     LOG(ERROR) << "Timeout of new browser info response for frame "
556                << frame_util::GetFrameDebugString(global_id);
557 
558     CancelNewBrowserInfoResponse(pending_info.get());
559     g_info_manager->pending_new_browser_info_map_.erase(it);
560   }
561 }
562