mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-02-10 09:00:42 +01:00
Popup windows will be created on the display that best matches the requested coordinates. The requested size will apply to the content area (as required by JS documentation) and window size will be reduced if necessary to fit within the target display. The requested origin will apply to the window (including frame) and will be modified if necessary so that the window is fully visible on the target display. This change does not implement popup positioning for cefclient which uses an application-created parent window. This change grants access to the getScreenDetails JS API without user prompt.
564 lines
19 KiB
C++
564 lines
19 KiB
C++
// 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_base.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_switches.h"
|
|
#include "libcef/common/extensions/extensions_util.h"
|
|
#include "libcef/common/frame_util.h"
|
|
#include "libcef/common/values_impl.h"
|
|
#include "libcef/features/runtime_checks.h"
|
|
|
|
#include "base/command_line.h"
|
|
#include "base/logging.h"
|
|
#include "base/threading/sequenced_task_runner_handle.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"
|
|
#include "content/public/common/url_constants.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();
|
|
|
|
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_host->GetGlobalId());
|
|
if (it != pending_new_browser_info_map_.end()) {
|
|
SendNewBrowserInfoResponse(browser_info, /*is_guest_view=*/false,
|
|
std::move(it->second->callback),
|
|
it->second->callback_runner);
|
|
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();
|
|
|
|
content::OpenURLParams params(target_url, referrer, disposition,
|
|
ui::PAGE_TRANSITION_LINK,
|
|
/*is_renderer_initiated=*/true);
|
|
params.user_gesture = user_gesture;
|
|
|
|
CefRefPtr<CefBrowserHostBase> browser;
|
|
if (!MaybeAllowNavigation(opener, params, browser) || !browser) {
|
|
// Cancel the popup.
|
|
return false;
|
|
}
|
|
|
|
CefRefPtr<CefClient> client = browser->GetClient();
|
|
bool allow = true;
|
|
|
|
CefWindowInfo window_info;
|
|
|
|
#if BUILDFLAG(IS_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_global_id = opener->GetGlobalId();
|
|
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);
|
|
|
|
// Default to the size from the popup features.
|
|
if (cef_features.xSet)
|
|
window_info.bounds.x = cef_features.x;
|
|
if (cef_features.ySet)
|
|
window_info.bounds.y = cef_features.y;
|
|
if (cef_features.widthSet)
|
|
window_info.bounds.width = cef_features.width;
|
|
if (cef_features.heightSet)
|
|
window_info.bounds.height = cef_features.height;
|
|
|
|
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) {
|
|
CefBrowserCreateParams create_params;
|
|
create_params.MaybeSetWindowInfo(window_info);
|
|
|
|
// In most cases, Views-hosted browsers should create Views-hosted popups
|
|
// and native browsers should use default popup handling. The one exception
|
|
// is with the Chrome runtime where a Views-hosted browser may have an
|
|
// external parent. In that case we want to use default popup handling even
|
|
// though the parent is (technically) Views-hosted.
|
|
create_params.popup_with_views_hosted_opener =
|
|
browser->HasView() &&
|
|
!browser->platform_delegate()->HasExternalParent();
|
|
|
|
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,
|
|
const content::GlobalRenderFrameHostId& opener_global_id,
|
|
content::WebContentsView** view,
|
|
content::RenderViewHostDelegateView** delegate_view) {
|
|
CEF_REQUIRE_UIT();
|
|
REQUIRE_ALLOY_RUNTIME();
|
|
|
|
std::unique_ptr<CefBrowserInfoManager::PendingPopup> pending_popup =
|
|
PopPendingPopup(CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW,
|
|
opener_global_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,
|
|
const content::GlobalRenderFrameHostId& opener_global_id,
|
|
CefBrowserSettings& settings,
|
|
CefRefPtr<CefClient>& client,
|
|
std::unique_ptr<CefBrowserPlatformDelegate>& platform_delegate,
|
|
CefRefPtr<CefDictionaryValue>& extra_info) {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
// GET_CUSTOM_WEB_CONTENTS_VIEW is only used with the alloy runtime.
|
|
const auto previous_step =
|
|
cef::IsAlloyRuntimeEnabled()
|
|
? CefBrowserInfoManager::PendingPopup::GET_CUSTOM_WEB_CONTENTS_VIEW
|
|
: CefBrowserInfoManager::PendingPopup::CAN_CREATE_WINDOW;
|
|
|
|
std::unique_ptr<CefBrowserInfoManager::PendingPopup> pending_popup =
|
|
PopPendingPopup(previous_step, opener_global_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(
|
|
const content::GlobalRenderFrameHostId& global_id,
|
|
cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback) {
|
|
DCHECK(frame_util::IsValidGlobalId(global_id));
|
|
DCHECK(callback);
|
|
|
|
auto callback_runner = base::SequencedTaskRunnerHandle::Get();
|
|
|
|
base::AutoLock lock_scope(browser_info_lock_);
|
|
|
|
bool is_guest_view = false;
|
|
|
|
scoped_refptr<CefBrowserInfo> browser_info =
|
|
GetBrowserInfoInternal(global_id, &is_guest_view);
|
|
|
|
if (browser_info) {
|
|
// Send the response immediately.
|
|
SendNewBrowserInfoResponse(browser_info, is_guest_view, std::move(callback),
|
|
callback_runner);
|
|
return;
|
|
}
|
|
|
|
// Verify that no request for the same route is currently queued.
|
|
DCHECK(pending_new_browser_info_map_.find(global_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->global_id = global_id;
|
|
pending->timeout_id = timeout_id;
|
|
pending->callback = std::move(callback);
|
|
pending->callback_runner = callback_runner;
|
|
pending_new_browser_info_map_.insert(
|
|
std::make_pair(global_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,
|
|
global_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<CefBrowserHostBase> 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::GetBrowserInfo(
|
|
const content::GlobalRenderFrameHostId& global_id,
|
|
bool* is_guest_view) {
|
|
base::AutoLock lock_scope(browser_info_lock_);
|
|
return GetBrowserInfoInternal(global_id, is_guest_view);
|
|
}
|
|
|
|
bool CefBrowserInfoManager::MaybeAllowNavigation(
|
|
content::RenderFrameHost* opener,
|
|
const content::OpenURLParams& params,
|
|
CefRefPtr<CefBrowserHostBase>& browser_out) const {
|
|
CEF_REQUIRE_UIT();
|
|
|
|
bool is_guest_view = false;
|
|
auto browser = extensions::GetOwnerBrowserForHost(opener, &is_guest_view);
|
|
if (!browser) {
|
|
// Print preview uses a modal dialog where we don't own the WebContents.
|
|
// Allow that navigation to proceed.
|
|
return true;
|
|
}
|
|
|
|
if (!browser->MaybeAllowNavigation(opener, is_guest_view, params)) {
|
|
return false;
|
|
}
|
|
|
|
browser_out = browser;
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
|
|
host->RemoveObserver(this);
|
|
|
|
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()) {
|
|
const auto& info = it->second;
|
|
if (info->global_id.child_id == render_process_id) {
|
|
CancelNewBrowserInfoResponse(info.get());
|
|
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_global_id.child_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,
|
|
const content::GlobalRenderFrameHostId& opener_global_id,
|
|
const GURL& target_url) {
|
|
CEF_REQUIRE_UIT();
|
|
DCHECK(frame_util::IsValidGlobalId(opener_global_id));
|
|
|
|
PendingPopupList::iterator it = pending_popup_list_.begin();
|
|
for (; it != pending_popup_list_.end(); ++it) {
|
|
PendingPopup* popup = it->get();
|
|
if (popup->step == step && popup->opener_global_id == opener_global_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::GetBrowserInfoInternal(
|
|
const content::GlobalRenderFrameHostId& global_id,
|
|
bool* is_guest_view) {
|
|
browser_info_lock_.AssertAcquired();
|
|
|
|
if (is_guest_view)
|
|
*is_guest_view = false;
|
|
|
|
if (!frame_util::IsValidGlobalId(global_id))
|
|
return nullptr;
|
|
|
|
for (const auto& browser_info : browser_info_list_) {
|
|
bool is_guest_view_tmp;
|
|
auto frame =
|
|
browser_info->GetFrameForGlobalId(global_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(
|
|
scoped_refptr<CefBrowserInfo> browser_info,
|
|
bool is_guest_view,
|
|
cef::mojom::BrowserManager::GetNewBrowserInfoCallback callback,
|
|
scoped_refptr<base::SequencedTaskRunner> callback_runner) {
|
|
if (!callback_runner->RunsTasksInCurrentSequence()) {
|
|
callback_runner->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&CefBrowserInfoManager::SendNewBrowserInfoResponse,
|
|
browser_info, is_guest_view, std::move(callback),
|
|
callback_runner));
|
|
return;
|
|
}
|
|
|
|
auto params = cef::mojom::NewBrowserInfo::New();
|
|
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 = base::WrapUnique(extra_info_impl->CopyValue());
|
|
params->extra_info = std::move(*extra_info_value);
|
|
}
|
|
} else {
|
|
// The new browser info response has timed out.
|
|
params->browser_id = -1;
|
|
}
|
|
|
|
std::move(callback).Run(std::move(params));
|
|
}
|
|
|
|
// static
|
|
void CefBrowserInfoManager::CancelNewBrowserInfoResponse(
|
|
PendingNewBrowserInfo* pending_info) {
|
|
SendNewBrowserInfoResponse(/*browser_info=*/nullptr, /*is_guest_view=*/false,
|
|
std::move(pending_info->callback),
|
|
pending_info->callback_runner);
|
|
}
|
|
|
|
// static
|
|
void CefBrowserInfoManager::TimeoutNewBrowserInfoResponse(
|
|
const content::GlobalRenderFrameHostId& global_id,
|
|
int timeout_id) {
|
|
CEF_REQUIRE_UIT();
|
|
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(global_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;
|
|
|
|
#if DCHECK_IS_ON()
|
|
// This method should never be called for a PDF renderer.
|
|
content::RenderProcessHost* process =
|
|
content::RenderProcessHost::FromID(global_id.child_id);
|
|
DCHECK(!process || !process->IsPdf());
|
|
#endif
|
|
|
|
LOG(ERROR) << "Timeout of new browser info response for frame "
|
|
<< frame_util::GetFrameDebugString(global_id);
|
|
|
|
CancelNewBrowserInfoResponse(pending_info.get());
|
|
g_info_manager->pending_new_browser_info_map_.erase(it);
|
|
}
|
|
}
|