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