Add notification for aborted popups (fixes #3776)

Pass a new |popup_id| parameter to OnBeforePopup and call a new
OnBeforePopupAborted callback if the popup is aborted before
OnAfterCreated is called for the popup browser. Add new
CefBrowserHost::GetBrowserByIdentifier and GetOpenerIdentifier
methods to assist with retrieval of associated browsers.

In cefclient, clean up state when a popup is aborted and close
any associated popup browsers when the opener browser is closed.
This also works when running with `--use-default-popup`.
This commit is contained in:
Marshall Greenblatt
2024-11-08 19:05:04 -05:00
parent b91be9fcc9
commit 1a99a3abc5
45 changed files with 773 additions and 109 deletions

View File

@@ -330,7 +330,7 @@ CefWindowHandle AlloyBrowserHostImpl::GetWindowHandle() {
}
CefWindowHandle AlloyBrowserHostImpl::GetOpenerWindowHandle() {
return opener_;
return opener_window_handle_;
}
void AlloyBrowserHostImpl::Find(const CefString& searchText,
@@ -1414,14 +1414,17 @@ AlloyBrowserHostImpl::AlloyBrowserHostImpl(
browser_info,
request_context),
content::WebContentsObserver(web_contents),
opener_(kNullWindowHandle),
is_windowless_(platform_delegate_->IsWindowless()) {
contents_delegate_.ObserveWebContents(web_contents);
if (opener.get() && !is_views_hosted_) {
// GetOpenerWindowHandle() only returns a value for non-views-hosted
// popup browsers.
opener_ = opener->GetWindowHandle();
if (opener.get()) {
opener_id_ = opener->GetIdentifier();
if (!is_views_hosted_) {
// GetOpenerWindowHandle() only returns a value for non-views-hosted
// popup browsers.
opener_window_handle_ = opener->GetWindowHandle();
}
}
// Associate the platform delegate with this browser.

View File

@@ -174,7 +174,8 @@ class AlloyBrowserHostImpl : public CefBrowserHostBase,
DestructionState destruction_state() const { return destruction_state_; }
// content::WebContentsDelegate methods.
void PrintCrossProcessSubframe(content::WebContents* web_contents,
void PrintCrossProcessSubframe(
content::WebContents* web_contents,
const gfx::Rect& rect,
int document_cookie,
content::RenderFrameHost* subframe_host) const override;
@@ -317,7 +318,7 @@ class AlloyBrowserHostImpl : public CefBrowserHostBase,
void StartAudioCapturer();
void OnRecentlyAudibleTimerFired();
CefWindowHandle opener_;
CefWindowHandle opener_window_handle_ = kNullWindowHandle;
const bool is_windowless_;
CefWindowHandle host_window_handle_ = kNullWindowHandle;

View File

@@ -480,6 +480,10 @@ void CefBrowserHostBase::SetFocus(bool focus) {
}
}
int CefBrowserHostBase::GetOpenerIdentifier() {
return opener_id_;
}
void CefBrowserHostBase::RunFileDialog(
FileDialogMode mode,
const CefString& title,
@@ -1445,6 +1449,11 @@ bool CefBrowserHostBase::IsVisible() const {
return false;
}
int CefBrowserHostBase::GetNextPopupId() {
CEF_REQUIRE_UIT();
return next_popup_id_++;
}
bool CefBrowserHostBase::EnsureDevToolsProtocolManager() {
CEF_REQUIRE_UIT();
if (!contents_delegate_.web_contents()) {

View File

@@ -220,6 +220,7 @@ class CefBrowserHostBase : public CefBrowserHost,
bool HasView() override;
bool IsReadyToBeClosed() override;
void SetFocus(bool focus) override;
int GetOpenerIdentifier() override;
void RunFileDialog(FileDialogMode mode,
const CefString& title,
const CefString& default_file_path,
@@ -418,6 +419,10 @@ class CefBrowserHostBase : public CefBrowserHost,
// Returns true if this browser is currently visible.
virtual bool IsVisible() const;
// Returns the next popup ID for use with OnBeforePopup. Must be called on
// the UI thread.
int GetNextPopupId();
protected:
bool EnsureDevToolsProtocolManager();
void InitializeDevToolsRegistrationOnUIThread(
@@ -439,6 +444,7 @@ class CefBrowserHostBase : public CefBrowserHost,
scoped_refptr<CefBrowserInfo> browser_info_;
CefRefPtr<CefRequestContextImpl> request_context_;
const bool is_views_hosted_;
int opener_id_ = 0;
// Only accessed on the UI thread.
CefBrowserContentsDelegate contents_delegate_;
@@ -473,6 +479,8 @@ class CefBrowserHostBase : public CefBrowserHost,
std::unique_ptr<CefMediaStreamRegistrar> media_stream_registrar_;
int next_popup_id_ = 1;
private:
IMPLEMENT_REFCOUNTING(CefBrowserHostBase);
};

View File

@@ -151,6 +151,21 @@ CefRefPtr<CefBrowser> CefBrowserHost::CreateBrowserSync(
return CefBrowserHostBase::Create(create_params);
}
// static
CefRefPtr<CefBrowser> CefBrowserHost::GetBrowserByIdentifier(int browser_id) {
// Verify that the context is in a valid state.
if (!CONTEXT_STATE_VALID()) {
DCHECK(false) << "context not valid";
return nullptr;
}
if (browser_id <= 0) {
return nullptr;
}
return CefBrowserHostBase::GetBrowserForBrowserId(browser_id).get();
}
// static
bool CefBrowserCreateParams::IsChromeStyle(const CefWindowInfo* window_info) {
if (!window_info) {

View File

@@ -45,6 +45,14 @@ CefBrowserInfoManager* g_info_manager = nullptr;
} // namespace
CefBrowserInfoManager::PendingPopup::~PendingPopup() {
CEF_REQUIRE_UIT();
if (step != CREATION_COMPLETE && !aborted_callback.is_null()) {
// Notify of pending popup abort.
std::move(aborted_callback).Run();
}
}
CefBrowserInfoManager::CefBrowserInfoManager() {
DCHECK(!g_info_manager);
g_info_manager = this;
@@ -167,14 +175,35 @@ bool CefBrowserInfoManager::CanCreateWindow(
window_info.bounds.height = cef_features.height;
}
const int popup_id = browser->GetNextPopupId();
allow = !handler->OnBeforePopup(
browser.get(), opener_frame, pending_popup->target_url.spec(),
pending_popup->target_frame_name,
browser.get(), opener_frame, popup_id,
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);
handled = true;
if (allow) {
// The parent browser may be destroyed during popup creation, so don't
// bind a direct reference.
pending_popup->aborted_callback = base::BindOnce(
[](int browser_id, int popup_id) {
LOG(WARNING) << "Pending popup " << popup_id
<< " aborted for browser " << browser_id;
if (auto browser =
CefBrowserHostBase::GetBrowserForBrowserId(browser_id)) {
if (auto client = browser->GetClient()) {
if (auto handler = client->GetLifeSpanHandler()) {
handler->OnBeforePopupAborted(browser.get(), popup_id);
}
}
}
},
browser->GetIdentifier(), popup_id);
}
}
}
@@ -217,12 +246,41 @@ bool CefBrowserInfoManager::CanCreateWindow(
// otherwise GetCustomWebContentsView will fail to retrieve the PopupInfo.
opener->GetProcess()->FilterURL(false, &pending_popup->target_url);
pending_create_popup_ = pending_popup.get();
// Need to Push here because WebContentsCreated may be called before
// CreateWindowResult.
PushPendingPopup(std::move(pending_popup));
}
return allow;
}
void CefBrowserInfoManager::CreateWindowResult(content::RenderFrameHost* opener,
bool success) {
// This method is called during RenderFrameHostImpl::CreateNewWindow execution
// (if CanCreateWindow returns true) with three possible states:
// 1. Before WebContentsCreated with |success=false|. This is the normal
// failure case where the pending popup will be canceled. For example, if a
// file select dialog is active.
// 2. After WebContentsCreated/AddWebContents with |success=true|. This is the
// normal success case where OnAfterCreated has already been called.
// 3. After WebContentsCreated/AddWebContents with |success=false|. This is
// the failure case where a WebContents won't have an opener from the
// renderer's perspective (for example, with JavaScript access disabled or
// no-referrer links). The WebContents is still valid, will navigate
// normally, and OnAfterCreated has already been called.
if (!success && pending_create_popup_) {
const auto* popup = pending_create_popup_.get();
pending_create_popup_ = nullptr;
// Cancel the pending popup.
std::erase_if(pending_popup_list_, [popup](const auto& popup_ptr) {
return popup_ptr.get() == popup;
});
}
}
void CefBrowserInfoManager::GetCustomWebContentsView(
const GURL& target_url,
const content::GlobalRenderFrameHostId& opener_global_id,
@@ -255,6 +313,8 @@ void CefBrowserInfoManager::WebContentsCreated(
content::WebContents* new_contents) {
CEF_REQUIRE_UIT();
pending_create_popup_ = nullptr;
// GET_CUSTOM_WEB_CONTENTS_VIEW is only used with Alloy style.
auto pending_popup = PopPendingPopup(
PendingPopup::GET_CUSTOM_WEB_CONTENTS_VIEW,
@@ -272,6 +332,8 @@ void CefBrowserInfoManager::WebContentsCreated(
pending_popup->step = PendingPopup::WEB_CONTENTS_CREATED;
pending_popup->new_contents = new_contents;
PushPendingPopup(std::move(pending_popup));
} else {
pending_popup->step = PendingPopup::CREATION_COMPLETE;
}
}
@@ -286,6 +348,7 @@ bool CefBrowserInfoManager::AddWebContents(content::WebContents* new_contents) {
PendingPopup::WEB_CONTENTS_CREATED, new_contents);
if (pending_popup) {
DCHECK(!pending_popup->alloy_style);
pending_popup->step = PendingPopup::CREATION_COMPLETE;
return !pending_popup->use_default_browser_creation;
}
@@ -589,17 +652,10 @@ void CefBrowserInfoManager::RenderProcessHostDestroyed(
}
// 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;
}
}
}
std::erase_if(
pending_popup_list_, [render_process_id](const auto& popup_ptr) {
return popup_ptr->opener_global_id.child_id == render_process_id;
});
}
void CefBrowserInfoManager::PushPendingPopup(

View File

@@ -82,6 +82,10 @@ class CefBrowserInfoManager : public content::RenderProcessHostObserver {
bool opener_suppressed,
bool* no_javascript_access);
// Called from ContentBrowserClient::CreateWindowResult if CanCreateWindow
// returns true. See comments on PendingPopup for more information.
void CreateWindowResult(content::RenderFrameHost* opener, bool success);
// Called from WebContentsDelegate::GetCustomWebContentsView (Alloy style
// only). See comments on PendingPopup for more information.
void GetCustomWebContentsView(
@@ -161,23 +165,33 @@ class CefBrowserInfoManager : public content::RenderProcessHostObserver {
// RenderProcessHostObserver methods:
void RenderProcessHostDestroyed(content::RenderProcessHost* host) override;
// Store state information about pending popups. Call order is:
// Store state information about pending popups. The UIT callbacks occur
// synchronously during RenderFrameHostImpl::CreateNewWindow execution. The
// result of CreateNewWindow execution will be passed to CreateWindowResult
// (may call OnBeforePopupAborted; see documentation in that method). Call
// order for successful popup creation is is:
// - CanCreateWindow (UIT):
// Provides an opportunity to cancel the popup (calls OnBeforePopup) and
// creates the new platform delegate for the popup. If the popup owner is
// an extension guest view (PDF viewer) then the popup is canceled and
// WebContentsDelegate::OpenURLFromTab is called via the
// CefBrowserHostBase::MaybeAllowNavigation implementation.
// And then the following calls may occur at the same time:
// And then the following UIT and IOT calls may occur at the same time:
// - GetCustomWebContentsView (UIT) (Alloy style only):
// Creates the OSR views for windowless popups.
// - WebContentsCreated (UIT):
// Creates the CefBrowserHost representation for the popup.
// Creates the CefBrowserHost representation for the popup (calls
// OnAfterCreated).
// - AddWebContents (UIT) (Chrome style only):
// Creates the Browser or tab representation for the popup.
// - CefBrowserManager::GetNewBrowserInfo (IOT)
// Passes information about the popup to the renderer process.
struct PendingPopup {
~PendingPopup();
// Used to notify if popup creation is aborted.
base::OnceClosure aborted_callback;
// Track the last method that modified this PendingPopup instance. There may
// be multiple pending popups with the same identifiers and this allows us
// to differentiate between them at different processing steps.
@@ -185,6 +199,7 @@ class CefBrowserInfoManager : public content::RenderProcessHostObserver {
CAN_CREATE_WINDOW,
GET_CUSTOM_WEB_CONTENTS_VIEW,
WEB_CONTENTS_CREATED,
CREATION_COMPLETE,
} step;
// True if this popup is Alloy style, otherwise Chrome style.
@@ -286,6 +301,11 @@ class CefBrowserInfoManager : public content::RenderProcessHostObserver {
using PendingPopupList = std::vector<std::unique_ptr<PendingPopup>>;
PendingPopupList pending_popup_list_;
// Current popup pending creation during RenderFrameHostImpl::CreateNewWindow
// execution (valid from CanCreateWindow returning true to WebContentsCreated
// or CreateWindowResult being called). Only accessed on the UI thread.
raw_ptr<PendingPopup> pending_create_popup_ = nullptr;
int next_timeout_id_ = 0;
};

View File

@@ -500,6 +500,8 @@ void ChromeBrowserHostImpl::Attach(content::WebContents* web_contents,
DCHECK(web_contents);
if (opener) {
opener_id_ = opener->GetIdentifier();
// Give the opener browser's platform delegate an opportunity to modify the
// new browser's platform delegate.
opener->platform_delegate()->PopupWebContentsCreated(

View File

@@ -369,6 +369,12 @@ bool ChromeContentBrowserClientCef::CanCreateWindow(
user_gesture, opener_suppressed, no_javascript_access);
}
void ChromeContentBrowserClientCef::CreateWindowResult(
content::RenderFrameHost* opener,
bool success) {
CefBrowserInfoManager::GetInstance()->CreateWindowResult(opener, success);
}
void ChromeContentBrowserClientCef::OverrideWebkitPrefs(
content::WebContents* web_contents,
blink::web_pref::WebPreferences* prefs) {

View File

@@ -63,6 +63,8 @@ class ChromeContentBrowserClientCef : public ChromeContentBrowserClient {
bool user_gesture,
bool opener_suppressed,
bool* no_javascript_access) override;
void CreateWindowResult(content::RenderFrameHost* opener,
bool success) override;
void OverrideWebkitPrefs(content::WebContents* web_contents,
blink::web_pref::WebPreferences* prefs) override;
void WillCreateURLLoaderFactory(