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