| // Copyright 2015 The Chromium Embedded Framework Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can |
| // be found in the LICENSE file. |
| |
| #include "libcef/browser/browser_info_manager.h" |
| |
| #include <utility> |
| |
| #include "libcef/browser/browser_host_impl.h" |
| #include "libcef/browser/browser_platform_delegate.h" |
| #include "libcef/browser/extensions/browser_extensions_util.h" |
| #include "libcef/browser/thread_util.h" |
| #include "libcef/common/cef_messages.h" |
| #include "libcef/common/cef_switches.h" |
| #include "libcef/common/extensions/extensions_util.h" |
| #include "libcef/common/values_impl.h" |
| |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #include "content/common/view_messages.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/child_process_host.h" |
| |
| namespace { |
| |
| // Timeout delay for new browser info responses. |
| const int64_t kNewBrowserInfoResponseTimeoutMs = 2000; |
| |
| void TranslatePopupFeatures(const blink::mojom::WindowFeatures& webKitFeatures, |
| CefPopupFeatures& features) { |
| features.x = static_cast<int>(webKitFeatures.x); |
| features.xSet = webKitFeatures.has_x; |
| features.y = static_cast<int>(webKitFeatures.y); |
| features.ySet = webKitFeatures.has_y; |
| features.width = static_cast<int>(webKitFeatures.width); |
| features.widthSet = webKitFeatures.has_width; |
| features.height = static_cast<int>(webKitFeatures.height); |
| features.heightSet = webKitFeatures.has_height; |
| |
| features.menuBarVisible = webKitFeatures.menu_bar_visible; |
| features.statusBarVisible = webKitFeatures.status_bar_visible; |
| features.toolBarVisible = webKitFeatures.tool_bar_visible; |
| features.scrollbarsVisible = webKitFeatures.scrollbars_visible; |
| } |
| |
| CefBrowserInfoManager* g_info_manager = nullptr; |
| |
| } // namespace |
| |
| CefBrowserInfoManager::CefBrowserInfoManager() { |
| DCHECK(!g_info_manager); |
| g_info_manager = this; |
| } |
| |
| CefBrowserInfoManager::~CefBrowserInfoManager() { |
| DCHECK(browser_info_list_.empty()); |
| g_info_manager = nullptr; |
| } |
| |
| // static |
| CefBrowserInfoManager* CefBrowserInfoManager::GetInstance() { |
| return g_info_manager; |
| } |
| |
| scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::CreateBrowserInfo( |
| bool is_popup, |
| bool is_windowless, |
| CefRefPtr<CefDictionaryValue> extra_info) { |
| base::AutoLock lock_scope(browser_info_lock_); |
| |
| scoped_refptr<CefBrowserInfo> browser_info = new CefBrowserInfo( |
| ++next_browser_id_, is_popup, is_windowless, extra_info); |
| browser_info_list_.push_back(browser_info); |
| |
| return browser_info; |
| } |
| |
| scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::CreatePopupBrowserInfo( |
| content::WebContents* new_contents, |
| bool is_windowless, |
| CefRefPtr<CefDictionaryValue> extra_info) { |
| base::AutoLock lock_scope(browser_info_lock_); |
| |
| auto frame_host = new_contents->GetMainFrame(); |
| const int render_process_id = frame_host->GetProcess()->GetID(); |
| const auto frame_id = CefFrameHostImpl::MakeFrameId(frame_host); |
| |
| scoped_refptr<CefBrowserInfo> browser_info = |
| new CefBrowserInfo(++next_browser_id_, true, is_windowless, extra_info); |
| browser_info_list_.push_back(browser_info); |
| |
| // Continue any pending NewBrowserInfo request. |
| auto it = pending_new_browser_info_map_.find(frame_id); |
| if (it != pending_new_browser_info_map_.end()) { |
| SendNewBrowserInfoResponse(render_process_id, browser_info, |
| false /* is_guest_view */, |
| it->second->reply_msg); |
| pending_new_browser_info_map_.erase(it); |
| } |
| |
| return browser_info; |
| } |
| |
| bool CefBrowserInfoManager::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) { |
| CEF_REQUIRE_UIT(); |
| |
| bool is_guest_view = false; |
| CefRefPtr<CefBrowserHostImpl> browser = |
| extensions::GetOwnerBrowserForHost(opener, &is_guest_view); |
| DCHECK(browser.get()); |
| if (!browser.get()) { |
| // Cancel the popup. |
| return false; |
| } |
| |
| if (is_guest_view) { |
| content::OpenURLParams params(target_url, referrer, disposition, |
| ui::PAGE_TRANSITION_LINK, true); |
| params.user_gesture = user_gesture; |
| |
| // Pass navigation to the owner browser. |
| CEF_POST_TASK( |
| CEF_UIT, |
| base::Bind(base::IgnoreResult(&CefBrowserHostImpl::OpenURLFromTab), |
| browser.get(), nullptr, params)); |
| |
| // Cancel the popup. |
| return false; |
| } |
| |
| CefRefPtr<CefClient> client = browser->GetClient(); |
| bool allow = true; |
| |
| std::unique_ptr<CefWindowInfo> window_info(new CefWindowInfo); |
| |
| #if defined(OS_WIN) |
| window_info->SetAsPopup(nullptr, CefString()); |
| #endif |
| |
| auto pending_popup = std::make_unique<CefBrowserInfoManager::PendingPopup>(); |
| pending_popup->step = CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW; |
| pending_popup->opener_render_process_id = opener->GetProcess()->GetID(); |
| pending_popup->opener_render_routing_id = opener->GetRoutingID(); |
| pending_popup->target_url = target_url; |
| pending_popup->target_frame_name = frame_name; |
| |
| // Start with the current browser's settings. |
| pending_popup->client = client; |
| pending_popup->settings = browser->settings(); |
| |
| if (client.get()) { |
| CefRefPtr<CefLifeSpanHandler> handler = client->GetLifeSpanHandler(); |
| if (handler.get()) { |
| CefRefPtr<CefFrame> opener_frame = browser->GetFrameForHost(opener); |
| DCHECK(opener_frame); |
| |
| CefPopupFeatures cef_features; |
| TranslatePopupFeatures(features, cef_features); |
| |
| #if (defined(OS_WIN) || defined(OS_MACOSX)) |
| // Default to the size from the popup features. |
| if (cef_features.xSet) |
| window_info->x = cef_features.x; |
| if (cef_features.ySet) |
| window_info->y = cef_features.y; |
| if (cef_features.widthSet) |
| window_info->width = cef_features.width; |
| if (cef_features.heightSet) |
| window_info->height = cef_features.height; |
| #endif |
| |
| allow = !handler->OnBeforePopup( |
| browser.get(), opener_frame, pending_popup->target_url.spec(), |
| pending_popup->target_frame_name, |
| static_cast<cef_window_open_disposition_t>(disposition), user_gesture, |
| cef_features, *window_info, pending_popup->client, |
| pending_popup->settings, pending_popup->extra_info, |
| no_javascript_access); |
| } |
| } |
| |
| if (allow) { |
| CefBrowserHostImpl::CreateParams create_params; |
| |
| if (!browser->IsViewsHosted()) |
| create_params.window_info = std::move(window_info); |
| |
| create_params.settings = pending_popup->settings; |
| create_params.client = pending_popup->client; |
| create_params.extra_info = pending_popup->extra_info; |
| |
| pending_popup->platform_delegate = |
| CefBrowserPlatformDelegate::Create(create_params); |
| CHECK(pending_popup->platform_delegate.get()); |
| |
| // Between the calls to CanCreateWindow and GetCustomWebContentsView |
| // RenderViewHostImpl::CreateNewWindow() will call |
| // RenderProcessHostImpl::FilterURL() which, in the case of "javascript:" |
| // URIs, rewrites the URL to "about:blank". We need to apply the same filter |
| // otherwise GetCustomWebContentsView will fail to retrieve the PopupInfo. |
| opener->GetProcess()->FilterURL(false, &pending_popup->target_url); |
| |
| PushPendingPopup(std::move(pending_popup)); |
| } |
| |
| return allow; |
| } |
| |
| void CefBrowserInfoManager::GetCustomWebContentsView( |
| const GURL& target_url, |
| int opener_render_process_id, |
| int opener_render_routing_id, |
| content::WebContentsView** view, |
| content::RenderViewHostDelegateView** delegate_view) { |
| CEF_REQUIRE_UIT(); |
| |
| std::unique_ptr<CefBrowserInfoManager::PendingPopup> pending_popup = |
| PopPendingPopup(CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW, |
| opener_render_process_id, opener_render_routing_id, |
| target_url); |
| DCHECK(pending_popup.get()); |
| DCHECK(pending_popup->platform_delegate.get()); |
| |
| if (pending_popup->platform_delegate->IsWindowless()) { |
| pending_popup->platform_delegate->CreateViewForWebContents(view, |
| delegate_view); |
| } |
| |
| pending_popup->step = |
| CefBrowserInfoManager::PendingPopup::GET_CUSTOM_WEB_CONTENTS_VIEW; |
| PushPendingPopup(std::move(pending_popup)); |
| } |
| |
| void CefBrowserInfoManager::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) { |
| CEF_REQUIRE_UIT(); |
| |
| std::unique_ptr<CefBrowserInfoManager::PendingPopup> pending_popup = |
| PopPendingPopup( |
| CefBrowserInfoManager::PendingPopup::GET_CUSTOM_WEB_CONTENTS_VIEW, |
| opener_render_process_id, opener_render_routing_id, target_url); |
| DCHECK(pending_popup.get()); |
| DCHECK(pending_popup->platform_delegate.get()); |
| |
| settings = pending_popup->settings; |
| client = pending_popup->client; |
| platform_delegate = std::move(pending_popup->platform_delegate); |
| extra_info = pending_popup->extra_info; |
| } |
| |
| void CefBrowserInfoManager::OnGetNewBrowserInfo(int render_process_id, |
| int render_routing_id, |
| IPC::Message* reply_msg) { |
| DCHECK_NE(render_process_id, content::ChildProcessHost::kInvalidUniqueID); |
| DCHECK_GT(render_routing_id, 0); |
| DCHECK(reply_msg); |
| |
| base::AutoLock lock_scope(browser_info_lock_); |
| |
| bool is_guest_view = false; |
| |
| scoped_refptr<CefBrowserInfo> browser_info = |
| GetBrowserInfo(render_process_id, render_routing_id, &is_guest_view); |
| |
| if (browser_info.get()) { |
| // Send the response immediately. |
| SendNewBrowserInfoResponse(render_process_id, browser_info, is_guest_view, |
| reply_msg); |
| return; |
| } |
| |
| const auto frame_id = |
| CefFrameHostImpl::MakeFrameId(render_process_id, render_routing_id); |
| |
| // Verify that no request for the same route is currently queued. |
| DCHECK(pending_new_browser_info_map_.find(frame_id) == |
| pending_new_browser_info_map_.end()); |
| |
| const int timeout_id = ++next_timeout_id_; |
| |
| // Queue the request. |
| std::unique_ptr<PendingNewBrowserInfo> pending(new PendingNewBrowserInfo()); |
| pending->render_process_id = render_process_id; |
| pending->render_routing_id = render_routing_id; |
| pending->timeout_id = timeout_id; |
| pending->reply_msg = reply_msg; |
| pending_new_browser_info_map_.insert( |
| std::make_pair(frame_id, std::move(pending))); |
| |
| // Register a timeout for the pending response so that the renderer process |
| // doesn't hang forever. |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableNewBrowserInfoTimeout)) { |
| CEF_POST_DELAYED_TASK( |
| CEF_UIT, |
| base::BindOnce(&CefBrowserInfoManager::TimeoutNewBrowserInfoResponse, |
| frame_id, timeout_id), |
| kNewBrowserInfoResponseTimeoutMs); |
| } |
| } |
| |
| void CefBrowserInfoManager::RemoveBrowserInfo( |
| scoped_refptr<CefBrowserInfo> browser_info) { |
| base::AutoLock lock_scope(browser_info_lock_); |
| |
| BrowserInfoList::iterator it = browser_info_list_.begin(); |
| for (; it != browser_info_list_.end(); ++it) { |
| if (*it == browser_info) { |
| browser_info_list_.erase(it); |
| return; |
| } |
| } |
| |
| NOTREACHED(); |
| } |
| |
| void CefBrowserInfoManager::DestroyAllBrowsers() { |
| BrowserInfoList list; |
| |
| { |
| base::AutoLock lock_scope(browser_info_lock_); |
| list = browser_info_list_; |
| } |
| |
| // Destroy any remaining browser windows. |
| if (!list.empty()) { |
| BrowserInfoList::iterator it = list.begin(); |
| for (; it != list.end(); ++it) { |
| CefRefPtr<CefBrowserHostImpl> browser = (*it)->browser(); |
| DCHECK(browser.get()); |
| if (browser.get()) { |
| // DestroyBrowser will call RemoveBrowserInfo. |
| browser->DestroyBrowser(); |
| } |
| } |
| } |
| |
| #if DCHECK_IS_ON() |
| { |
| // Verify that all browser windows have been destroyed. |
| base::AutoLock lock_scope(browser_info_lock_); |
| DCHECK(browser_info_list_.empty()); |
| } |
| #endif |
| } |
| |
| scoped_refptr<CefBrowserInfo> |
| CefBrowserInfoManager::GetBrowserInfoForFrameRoute(int render_process_id, |
| int render_routing_id, |
| bool* is_guest_view) { |
| base::AutoLock lock_scope(browser_info_lock_); |
| return GetBrowserInfo(render_process_id, render_routing_id, is_guest_view); |
| } |
| |
| scoped_refptr<CefBrowserInfo> |
| CefBrowserInfoManager::GetBrowserInfoForFrameTreeNode(int frame_tree_node_id, |
| bool* is_guest_view) { |
| if (is_guest_view) |
| *is_guest_view = false; |
| |
| if (frame_tree_node_id < 0) |
| return nullptr; |
| |
| base::AutoLock lock_scope(browser_info_lock_); |
| |
| for (const auto& browser_info : browser_info_list_) { |
| bool is_guest_view_tmp; |
| auto frame = browser_info->GetFrameForFrameTreeNode(frame_tree_node_id, |
| &is_guest_view_tmp); |
| if (frame || is_guest_view_tmp) { |
| if (is_guest_view) |
| *is_guest_view = is_guest_view_tmp; |
| return browser_info; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| CefBrowserInfoManager::BrowserInfoList |
| CefBrowserInfoManager::GetBrowserInfoList() { |
| base::AutoLock lock_scope(browser_info_lock_); |
| BrowserInfoList copy; |
| copy.assign(browser_info_list_.begin(), browser_info_list_.end()); |
| return copy; |
| } |
| |
| void CefBrowserInfoManager::RenderProcessHostDestroyed( |
| content::RenderProcessHost* host) { |
| CEF_REQUIRE_UIT(); |
| |
| const int render_process_id = host->GetID(); |
| DCHECK_GT(render_process_id, 0); |
| |
| // Remove all pending requests that reference the destroyed host. |
| { |
| base::AutoLock lock_scope(browser_info_lock_); |
| |
| PendingNewBrowserInfoMap::iterator it = |
| pending_new_browser_info_map_.begin(); |
| while (it != pending_new_browser_info_map_.end()) { |
| auto info = it->second.get(); |
| if (info->render_process_id == render_process_id) |
| it = pending_new_browser_info_map_.erase(it); |
| else |
| ++it; |
| } |
| } |
| |
| // Remove all pending popups that reference the destroyed host as the opener. |
| { |
| PendingPopupList::iterator it = pending_popup_list_.begin(); |
| while (it != pending_popup_list_.end()) { |
| PendingPopup* popup = it->get(); |
| if (popup->opener_render_process_id == render_process_id) { |
| it = pending_popup_list_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| } |
| |
| void CefBrowserInfoManager::PushPendingPopup( |
| std::unique_ptr<PendingPopup> popup) { |
| CEF_REQUIRE_UIT(); |
| pending_popup_list_.push_back(std::move(popup)); |
| } |
| |
| std::unique_ptr<CefBrowserInfoManager::PendingPopup> |
| CefBrowserInfoManager::PopPendingPopup(PendingPopup::Step step, |
| int opener_render_process_id, |
| int opener_render_routing_id, |
| const GURL& target_url) { |
| CEF_REQUIRE_UIT(); |
| DCHECK_GT(opener_render_process_id, 0); |
| DCHECK_GT(opener_render_routing_id, 0); |
| |
| PendingPopupList::iterator it = pending_popup_list_.begin(); |
| for (; it != pending_popup_list_.end(); ++it) { |
| PendingPopup* popup = it->get(); |
| if (popup->step == step && |
| popup->opener_render_process_id == opener_render_process_id && |
| popup->opener_render_routing_id == opener_render_routing_id && |
| popup->target_url == target_url) { |
| // Transfer ownership of the pointer. |
| it->release(); |
| pending_popup_list_.erase(it); |
| return base::WrapUnique(popup); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| scoped_refptr<CefBrowserInfo> CefBrowserInfoManager::GetBrowserInfo( |
| int render_process_id, |
| int render_routing_id, |
| bool* is_guest_view) { |
| browser_info_lock_.AssertAcquired(); |
| |
| if (is_guest_view) |
| *is_guest_view = false; |
| |
| if (render_process_id < 0 || render_routing_id < 0) |
| return nullptr; |
| |
| for (const auto& browser_info : browser_info_list_) { |
| bool is_guest_view_tmp; |
| auto frame = browser_info->GetFrameForRoute( |
| render_process_id, render_routing_id, &is_guest_view_tmp); |
| if (frame || is_guest_view_tmp) { |
| if (is_guest_view) |
| *is_guest_view = is_guest_view_tmp; |
| return browser_info; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| // static |
| void CefBrowserInfoManager::SendNewBrowserInfoResponse( |
| int render_process_id, |
| scoped_refptr<CefBrowserInfo> browser_info, |
| bool is_guest_view, |
| IPC::Message* reply_msg) { |
| if (!CEF_CURRENTLY_ON_UIT()) { |
| CEF_POST_TASK( |
| CEF_UIT, |
| base::Bind(&CefBrowserInfoManager::SendNewBrowserInfoResponse, |
| render_process_id, browser_info, is_guest_view, reply_msg)); |
| return; |
| } |
| |
| content::RenderProcessHost* host = |
| content::RenderProcessHost::FromID(render_process_id); |
| if (!host) { |
| delete reply_msg; |
| return; |
| } |
| |
| CefProcessHostMsg_GetNewBrowserInfo_Params params; |
| params.is_guest_view = is_guest_view; |
| |
| if (browser_info) { |
| params.browser_id = browser_info->browser_id(); |
| params.is_windowless = browser_info->is_windowless(); |
| params.is_popup = browser_info->is_popup(); |
| |
| auto extra_info = browser_info->extra_info(); |
| if (extra_info) { |
| auto extra_info_impl = |
| static_cast<CefDictionaryValueImpl*>(extra_info.get()); |
| auto extra_info_value = extra_info_impl->CopyValue(); |
| extra_info_value->Swap(¶ms.extra_info); |
| } |
| } else { |
| // The new browser info response has timed out. |
| params.browser_id = -1; |
| } |
| |
| CefProcessHostMsg_GetNewBrowserInfo::WriteReplyParams(reply_msg, params); |
| host->Send(reply_msg); |
| } |
| |
| // static |
| void CefBrowserInfoManager::TimeoutNewBrowserInfoResponse(int64_t frame_id, |
| int timeout_id) { |
| if (!g_info_manager) |
| return; |
| |
| base::AutoLock lock_scope(g_info_manager->browser_info_lock_); |
| |
| // Continue the NewBrowserInfo request if it's still pending. |
| auto it = g_info_manager->pending_new_browser_info_map_.find(frame_id); |
| if (it != g_info_manager->pending_new_browser_info_map_.end()) { |
| const auto& pending_info = it->second; |
| // Don't accidentally timeout a new request for the same frame. |
| if (pending_info->timeout_id != timeout_id) |
| return; |
| |
| LOG(ERROR) << "Timeout of new browser info response for frame process id " |
| << pending_info->render_process_id << " and routing id " |
| << pending_info->render_routing_id; |
| |
| SendNewBrowserInfoResponse(pending_info->render_process_id, nullptr, |
| false /* is_guest_view */, |
| pending_info->reply_msg); |
| g_info_manager->pending_new_browser_info_map_.erase(it); |
| } |
| } |