From 1a99a3abc5b40b81c1886f040847e8a1425d605c Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Fri, 8 Nov 2024 19:05:04 -0500 Subject: [PATCH] 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`. --- include/capi/cef_browser_capi.h | 14 ++- include/capi/cef_life_span_handler_capi.h | 70 +++++++++----- include/cef_api_hash.h | 8 +- include/cef_browser.h | 13 +++ include/cef_life_span_handler.h | 55 ++++++++--- .../browser/alloy/alloy_browser_host_impl.cc | 15 +-- .../browser/alloy/alloy_browser_host_impl.h | 5 +- libcef/browser/browser_host_base.cc | 9 ++ libcef/browser/browser_host_base.h | 8 ++ libcef/browser/browser_host_create.cc | 15 +++ libcef/browser/browser_info_manager.cc | 82 ++++++++++++++--- libcef/browser/browser_info_manager.h | 26 +++++- .../chrome/chrome_browser_host_impl.cc | 2 + .../chrome_content_browser_client_cef.cc | 6 ++ .../chrome_content_browser_client_cef.h | 2 + libcef_dll/cpptoc/browser_host_cpptoc.cc | 35 ++++++- libcef_dll/cpptoc/life_span_handler_cpptoc.cc | 30 +++++- libcef_dll/ctocpp/browser_host_ctocpp.cc | 33 ++++++- libcef_dll/ctocpp/browser_host_ctocpp.h | 3 +- libcef_dll/ctocpp/life_span_handler_ctocpp.cc | 33 ++++++- libcef_dll/ctocpp/life_span_handler_ctocpp.h | 5 +- libcef_dll/wrapper/libcef_dll_dylib.cc | 12 ++- patch/patch.cfg | 6 ++ patch/patches/content_2015.patch | 21 ++++- patch/patches/rfh_navigation_4829483.patch | 21 ++++- .../cefclient/browser/base_client_handler.cc | 6 +- tests/cefclient/browser/client_handler.cc | 24 ++++- tests/cefclient/browser/client_handler.h | 4 + .../browser/default_client_handler.cc | 21 ++++- .../browser/default_client_handler.h | 4 + tests/cefclient/browser/root_window.cc | 24 +++++ tests/cefclient/browser/root_window.h | 22 +++++ tests/cefclient/browser/root_window_gtk.cc | 3 +- tests/cefclient/browser/root_window_gtk.h | 1 - tests/cefclient/browser/root_window_mac.mm | 11 ++- .../cefclient/browser/root_window_manager.cc | 91 ++++++++++++++++++- tests/cefclient/browser/root_window_manager.h | 26 +++++- tests/cefclient/browser/root_window_views.cc | 11 +++ tests/cefclient/browser/root_window_views.h | 1 - tests/cefclient/browser/root_window_win.cc | 2 + tests/cefclient/browser/root_window_win.h | 1 - tests/ceftests/frame_handler_unittest.cc | 6 ++ tests/ceftests/navigation_unittest.cc | 5 + tests/ceftests/osr_display_unittest.cc | 2 + tests/ceftests/request_context_unittest.cc | 88 +++++++++++++++++- 45 files changed, 773 insertions(+), 109 deletions(-) diff --git a/include/capi/cef_browser_capi.h b/include/capi/cef_browser_capi.h index b5dd342e1..cb2356837 100644 --- a/include/capi/cef_browser_capi.h +++ b/include/capi/cef_browser_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=e9f34d90eb4af614e35cbb29da0639b62acec7fd$ +// $hash=7b8b175b96f59916c09ac0dc9f307d07c1ad7e32$ // #ifndef CEF_INCLUDE_CAPI_CEF_BROWSER_CAPI_H_ @@ -380,6 +380,12 @@ typedef struct _cef_browser_host_t { cef_window_handle_t(CEF_CALLBACK* get_opener_window_handle)( struct _cef_browser_host_t* self); + /// + /// Retrieve the unique identifier of the browser that opened this browser. + /// Will return 0 for non-popup browsers. + /// + int(CEF_CALLBACK* get_opener_identifier)(struct _cef_browser_host_t* self); + /// /// Returns true (1) if this browser is wrapped in a cef_browser_view_t. /// @@ -1046,6 +1052,12 @@ CEF_EXPORT cef_browser_t* cef_browser_host_create_browser_sync( struct _cef_dictionary_value_t* extra_info, struct _cef_request_context_t* request_context); +/// +/// Returns the browser (if any) with the specified identifier. +/// +CEF_EXPORT cef_browser_t* cef_browser_host_get_browser_by_identifier( + int browser_id); + #ifdef __cplusplus } #endif diff --git a/include/capi/cef_life_span_handler_capi.h b/include/capi/cef_life_span_handler_capi.h index f67bce990..c129f658b 100644 --- a/include/capi/cef_life_span_handler_capi.h +++ b/include/capi/cef_life_span_handler_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=6aad2ccf30a6c519bbeee64d83866e82a41a48d8$ +// $hash=5a56a530920ced27e4adf06c8ff758c1a42bc6e7$ // #ifndef CEF_INCLUDE_CAPI_CEF_LIFE_SPAN_HANDLER_CAPI_H_ @@ -62,33 +62,40 @@ typedef struct _cef_life_span_handler_t { /// /// Called on the UI thread before a new popup browser is created. The - /// |browser| and |frame| values represent the source of the popup request. - /// The |target_url| and |target_frame_name| values indicate where the popup - /// browser should navigate and may be NULL if not specified with the request. - /// The |target_disposition| value indicates where the user intended to open - /// the popup (e.g. current tab, new tab, etc). The |user_gesture| value will - /// be true (1) if the popup was opened via explicit user gesture (e.g. - /// clicking a link) or false (0) if the popup opened automatically (e.g. via - /// the DomContentLoaded event). The |popupFeatures| structure contains - /// additional information about the requested popup window. To allow creation - /// of the popup browser optionally modify |windowInfo|, |client|, |settings| - /// and |no_javascript_access| and return false (0). To cancel creation of the + /// |browser| and |frame| values represent the source of the popup request + /// (opener browser and frame). The |popup_id| value uniquely identifies the + /// popup in the context of the opener browser. The |target_url| and + /// |target_frame_name| values indicate where the popup browser should + /// navigate and may be NULL if not specified with the request. The + /// |target_disposition| value indicates where the user intended to open the + /// popup (e.g. current tab, new tab, etc). The |user_gesture| value will be + /// true (1) if the popup was opened via explicit user gesture (e.g. clicking + /// a link) or false (0) if the popup opened automatically (e.g. via the + /// DomContentLoaded event). The |popupFeatures| structure contains additional + /// information about the requested popup window. To allow creation of the + /// popup browser optionally modify |windowInfo|, |client|, |settings| and + /// |no_javascript_access| and return false (0). To cancel creation of the /// popup browser return true (1). The |client| and |settings| values will /// default to the source browser's values. If the |no_javascript_access| /// value is set to false (0) the new browser will not be scriptable and may /// not be hosted in the same renderer process as the source browser. Any /// modifications to |windowInfo| will be ignored if the parent browser is - /// wrapped in a cef_browser_view_t. Popup browser creation will be canceled - /// if the parent browser is destroyed before the popup browser creation - /// completes (indicated by a call to OnAfterCreated for the popup browser). - /// The |extra_info| parameter provides an opportunity to specify extra - /// information specific to the created popup browser that will be passed to + /// wrapped in a cef_browser_view_t. The |extra_info| parameter provides an + /// opportunity to specify extra information specific to the created popup + /// browser that will be passed to /// cef_render_process_handler_t::on_browser_created() in the render process. /// + /// If popup browser creation succeeds then OnAfterCreated will be called for + /// the new popup browser. If popup browser creation fails, and if the opener + /// browser has not yet been destroyed, then OnBeforePopupAborted will be + /// called for the opener browser. See OnBeforePopupAborted documentation for + /// additional details. + /// int(CEF_CALLBACK* on_before_popup)( struct _cef_life_span_handler_t* self, struct _cef_browser_t* browser, struct _cef_frame_t* frame, + int popup_id, const cef_string_t* target_url, const cef_string_t* target_frame_name, cef_window_open_disposition_t target_disposition, @@ -100,6 +107,26 @@ typedef struct _cef_life_span_handler_t { struct _cef_dictionary_value_t** extra_info, int* no_javascript_access); + /// + /// Called on the UI thread if a new popup browser is aborted. This only + /// occurs if the popup is allowed in OnBeforePopup and creation fails before + /// OnAfterCreated is called for the new popup browser. The |browser| value is + /// the source of the popup request (opener browser). The |popup_id| value + /// uniquely identifies the popup in the context of the opener browser, and is + /// the same value that was passed to OnBeforePopup. + /// + /// Any client state associated with pending popups should be cleared in + /// OnBeforePopupAborted, OnAfterCreated of the popup browser, or + /// OnBeforeClose of the opener browser. OnBeforeClose of the opener browser + /// may be called before this function in cases where the opener is closing + /// during popup creation, in which case cef_browser_host_t::IsValid will + /// return false (0) in this function. + /// + void(CEF_CALLBACK* on_before_popup_aborted)( + struct _cef_life_span_handler_t* self, + struct _cef_browser_t* browser, + int popup_id); + /// /// Called on the UI thread before a new DevTools popup browser is created. /// The |browser| value represents the source of the popup request. Optionally @@ -252,10 +279,11 @@ typedef struct _cef_life_span_handler_t { /// browser object and do not attempt to execute any functions on the browser /// object (other than IsValid, GetIdentifier or IsSame) after this callback /// returns. cef_frame_handler_t callbacks related to final main frame - /// destruction will arrive after this callback and cef_browser_t::IsValid - /// will return false (0) at that time. Any in-progress network requests - /// associated with |browser| will be aborted when the browser is destroyed, - /// and cef_resource_request_handler_t callbacks related to those requests may + /// destruction, and OnBeforePopupAborted callbacks for any pending popups, + /// will arrive after this callback and cef_browser_t::IsValid will return + /// false (0) at that time. Any in-progress network requests associated with + /// |browser| will be aborted when the browser is destroyed, and + /// cef_resource_request_handler_t callbacks related to those requests may /// still arrive on the IO thread after this callback. See cef_frame_handler_t /// and do_close() documentation for additional usage information. /// diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index 6962b8203..48ea56b8b 100644 --- a/include/cef_api_hash.h +++ b/include/cef_api_hash.h @@ -42,13 +42,13 @@ // way that may cause binary incompatibility with other builds. The universal // hash value will change if any platform is affected whereas the platform hash // values will change only if that particular platform is affected. -#define CEF_API_HASH_UNIVERSAL "38565e673fbcfcd9e4494914bcea03609b41ec30" +#define CEF_API_HASH_UNIVERSAL "8d6d53c5732a12b4d663665e3745a93d1cfd742d" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "df2092177211214092ab77559596adbc37edf68d" +#define CEF_API_HASH_PLATFORM "3593cc6344b391d992421dd985c4ebcc46d8314f" #elif defined(OS_MAC) -#define CEF_API_HASH_PLATFORM "aaa5bde96ceffff3de2c6fab11142c9264f44a39" +#define CEF_API_HASH_PLATFORM "c045e75415a6abc2c29a3e1e05baea7528e2ec28" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "40de77e9ae3e071eda1f3bed7e900aedcdb354e5" +#define CEF_API_HASH_PLATFORM "b1058e8b167cfaa2f0feaccf4b4f23a813db515a" #endif #ifdef __cplusplus diff --git a/include/cef_browser.h b/include/cef_browser.h index 011e51271..f8749aeac 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -324,6 +324,12 @@ class CefBrowserHost : public virtual CefBaseRefCounted { CefRefPtr extra_info, CefRefPtr request_context); + /// + /// Returns the browser (if any) with the specified identifier. + /// + /*--cef()--*/ + static CefRefPtr GetBrowserByIdentifier(int browser_id); + /// /// Returns the hosted browser object. /// @@ -412,6 +418,13 @@ class CefBrowserHost : public virtual CefBaseRefCounted { /*--cef()--*/ virtual CefWindowHandle GetOpenerWindowHandle() = 0; + /// + /// Retrieve the unique identifier of the browser that opened this browser. + /// Will return 0 for non-popup browsers. + /// + /*--cef()--*/ + virtual int GetOpenerIdentifier() = 0; + /// /// Returns true if this browser is wrapped in a CefBrowserView. /// diff --git a/include/cef_life_span_handler.h b/include/cef_life_span_handler.h index 30e17e8a5..e10a42295 100644 --- a/include/cef_life_span_handler.h +++ b/include/cef_life_span_handler.h @@ -55,13 +55,15 @@ class CefLifeSpanHandler : public virtual CefBaseRefCounted { /// /// Called on the UI thread before a new popup browser is created. The - /// |browser| and |frame| values represent the source of the popup request. - /// The |target_url| and |target_frame_name| values indicate where the popup - /// browser should navigate and may be empty if not specified with the - /// request. The |target_disposition| value indicates where the user intended - /// to open the popup (e.g. current tab, new tab, etc). The |user_gesture| - /// value will be true if the popup was opened via explicit user gesture (e.g. - /// clicking a link) or false if the popup opened automatically (e.g. via the + /// |browser| and |frame| values represent the source of the popup request + /// (opener browser and frame). The |popup_id| value uniquely identifies the + /// popup in the context of the opener browser. The |target_url| and + /// |target_frame_name| values indicate where the popup browser should + /// navigate and may be empty if not specified with the request. The + /// |target_disposition| value indicates where the user intended to open the + /// popup (e.g. current tab, new tab, etc). The |user_gesture| value will be + /// true if the popup was opened via explicit user gesture (e.g. clicking a + /// link) or false if the popup opened automatically (e.g. via the /// DomContentLoaded event). The |popupFeatures| structure contains additional /// information about the requested popup window. To allow creation of the /// popup browser optionally modify |windowInfo|, |client|, |settings| and @@ -71,16 +73,21 @@ class CefLifeSpanHandler : public virtual CefBaseRefCounted { /// false the new browser will not be scriptable and may not be hosted in the /// same renderer process as the source browser. Any modifications to /// |windowInfo| will be ignored if the parent browser is wrapped in a - /// CefBrowserView. Popup browser creation will be canceled if the parent - /// browser is destroyed before the popup browser creation completes - /// (indicated by a call to OnAfterCreated for the popup browser). The - /// |extra_info| parameter provides an opportunity to specify extra - /// information specific to the created popup browser that will be passed to - /// CefRenderProcessHandler::OnBrowserCreated() in the render process. + /// CefBrowserView. The |extra_info| parameter provides an opportunity to + /// specify extra information specific to the created popup browser that will + /// be passed to CefRenderProcessHandler::OnBrowserCreated() in the render + /// process. + /// + /// If popup browser creation succeeds then OnAfterCreated will be called for + /// the new popup browser. If popup browser creation fails, and if the opener + /// browser has not yet been destroyed, then OnBeforePopupAborted will be + /// called for the opener browser. See OnBeforePopupAborted documentation for + /// additional details. /// /*--cef(optional_param=target_url,optional_param=target_frame_name)--*/ virtual bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, WindowOpenDisposition target_disposition, @@ -94,6 +101,25 @@ class CefLifeSpanHandler : public virtual CefBaseRefCounted { return false; } + /// + /// Called on the UI thread if a new popup browser is aborted. This only + /// occurs if the popup is allowed in OnBeforePopup and creation fails before + /// OnAfterCreated is called for the new popup browser. The |browser| value is + /// the source of the popup request (opener browser). The |popup_id| value + /// uniquely identifies the popup in the context of the opener browser, and is + /// the same value that was passed to OnBeforePopup. + /// + /// Any client state associated with pending popups should be cleared in + /// OnBeforePopupAborted, OnAfterCreated of the popup browser, or + /// OnBeforeClose of the opener browser. OnBeforeClose of the opener browser + /// may be called before this method in cases where the opener is closing + /// during popup creation, in which case CefBrowserHost::IsValid will return + /// false in this method. + /// + /*--cef()--*/ + virtual void OnBeforePopupAborted(CefRefPtr browser, + int popup_id) {} + /// /// Called on the UI thread before a new DevTools popup browser is created. /// The |browser| value represents the source of the popup request. Optionally @@ -242,7 +268,8 @@ class CefLifeSpanHandler : public virtual CefBaseRefCounted { /// Called just before a browser is destroyed. Release all references to the /// browser object and do not attempt to execute any methods on the browser /// object (other than IsValid, GetIdentifier or IsSame) after this callback - /// returns. CefFrameHandler callbacks related to final main frame destruction + /// returns. CefFrameHandler callbacks related to final main frame + /// destruction, and OnBeforePopupAborted callbacks for any pending popups, /// will arrive after this callback and CefBrowser::IsValid will return false /// at that time. Any in-progress network requests associated with |browser| /// will be aborted when the browser is destroyed, and diff --git a/libcef/browser/alloy/alloy_browser_host_impl.cc b/libcef/browser/alloy/alloy_browser_host_impl.cc index 6adfd2ecf..f1e2f0079 100644 --- a/libcef/browser/alloy/alloy_browser_host_impl.cc +++ b/libcef/browser/alloy/alloy_browser_host_impl.cc @@ -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. diff --git a/libcef/browser/alloy/alloy_browser_host_impl.h b/libcef/browser/alloy/alloy_browser_host_impl.h index de15ae05d..dd24b4f3b 100644 --- a/libcef/browser/alloy/alloy_browser_host_impl.h +++ b/libcef/browser/alloy/alloy_browser_host_impl.h @@ -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; diff --git a/libcef/browser/browser_host_base.cc b/libcef/browser/browser_host_base.cc index 907092c84..3f58c9ac8 100644 --- a/libcef/browser/browser_host_base.cc +++ b/libcef/browser/browser_host_base.cc @@ -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()) { diff --git a/libcef/browser/browser_host_base.h b/libcef/browser/browser_host_base.h index ee7147812..c46e1972f 100644 --- a/libcef/browser/browser_host_base.h +++ b/libcef/browser/browser_host_base.h @@ -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 browser_info_; CefRefPtr 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 media_stream_registrar_; + int next_popup_id_ = 1; + private: IMPLEMENT_REFCOUNTING(CefBrowserHostBase); }; diff --git a/libcef/browser/browser_host_create.cc b/libcef/browser/browser_host_create.cc index 8f7020d9b..deec03524 100644 --- a/libcef/browser/browser_host_create.cc +++ b/libcef/browser/browser_host_create.cc @@ -151,6 +151,21 @@ CefRefPtr CefBrowserHost::CreateBrowserSync( return CefBrowserHostBase::Create(create_params); } +// static +CefRefPtr 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) { diff --git a/libcef/browser/browser_info_manager.cc b/libcef/browser/browser_info_manager.cc index cf26dc9c6..6cdffd768 100644 --- a/libcef/browser/browser_info_manager.cc +++ b/libcef/browser/browser_info_manager.cc @@ -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(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( diff --git a/libcef/browser/browser_info_manager.h b/libcef/browser/browser_info_manager.h index f2e10835c..486605e9b 100644 --- a/libcef/browser/browser_info_manager.h +++ b/libcef/browser/browser_info_manager.h @@ -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>; 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 pending_create_popup_ = nullptr; + int next_timeout_id_ = 0; }; diff --git a/libcef/browser/chrome/chrome_browser_host_impl.cc b/libcef/browser/chrome/chrome_browser_host_impl.cc index c85549675..38f6ed1cc 100644 --- a/libcef/browser/chrome/chrome_browser_host_impl.cc +++ b/libcef/browser/chrome/chrome_browser_host_impl.cc @@ -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( diff --git a/libcef/browser/chrome/chrome_content_browser_client_cef.cc b/libcef/browser/chrome/chrome_content_browser_client_cef.cc index cf28ac9d6..d3acfd64f 100644 --- a/libcef/browser/chrome/chrome_content_browser_client_cef.cc +++ b/libcef/browser/chrome/chrome_content_browser_client_cef.cc @@ -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) { diff --git a/libcef/browser/chrome/chrome_content_browser_client_cef.h b/libcef/browser/chrome/chrome_content_browser_client_cef.h index b5517d017..5cd736999 100644 --- a/libcef/browser/chrome/chrome_content_browser_client_cef.h +++ b/libcef/browser/chrome/chrome_content_browser_client_cef.h @@ -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( diff --git a/libcef_dll/cpptoc/browser_host_cpptoc.cc b/libcef_dll/cpptoc/browser_host_cpptoc.cc index ffa1ba749..402d1f875 100644 --- a/libcef_dll/cpptoc/browser_host_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_host_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=8a2a8a4853c3869876ffad3e6c175945ac1c5021$ +// $hash=c7458f64a0d7c8537dbdbcf34d11e62bf781428c$ // #include "libcef_dll/cpptoc/browser_host_cpptoc.h" @@ -136,6 +136,20 @@ CEF_EXPORT cef_browser_t* cef_browser_host_create_browser_sync( return CefBrowserCppToC::Wrap(_retval); } +CEF_EXPORT cef_browser_t* cef_browser_host_get_browser_by_identifier( + int browser_id) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + CefRefPtr _retval = + CefBrowserHost::GetBrowserByIdentifier(browser_id); + + // Return type: refptr_same + return CefBrowserCppToC::Wrap(_retval); +} + namespace { // MEMBER FUNCTIONS - Body may be edited by hand. @@ -262,6 +276,24 @@ browser_host_get_opener_window_handle(struct _cef_browser_host_t* self) { return _retval; } +int CEF_CALLBACK +browser_host_get_opener_identifier(struct _cef_browser_host_t* self) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) { + return 0; + } + + // Execute + int _retval = CefBrowserHostCppToC::Get(self)->GetOpenerIdentifier(); + + // Return type: simple + return _retval; +} + int CEF_CALLBACK browser_host_has_view(struct _cef_browser_host_t* self) { shutdown_checker::AssertNotShutdown(); @@ -1536,6 +1568,7 @@ CefBrowserHostCppToC::CefBrowserHostCppToC() { GetStruct()->set_focus = browser_host_set_focus; GetStruct()->get_window_handle = browser_host_get_window_handle; GetStruct()->get_opener_window_handle = browser_host_get_opener_window_handle; + GetStruct()->get_opener_identifier = browser_host_get_opener_identifier; GetStruct()->has_view = browser_host_has_view; GetStruct()->get_client = browser_host_get_client; GetStruct()->get_request_context = browser_host_get_request_context; diff --git a/libcef_dll/cpptoc/life_span_handler_cpptoc.cc b/libcef_dll/cpptoc/life_span_handler_cpptoc.cc index 5bb7578d7..6b118de22 100644 --- a/libcef_dll/cpptoc/life_span_handler_cpptoc.cc +++ b/libcef_dll/cpptoc/life_span_handler_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=3883fd2aca10df181da30759fcce351fed62d43d$ +// $hash=b3844320e2d708a582a4bc2e3d5f2f143de8f514$ // #include "libcef_dll/cpptoc/life_span_handler_cpptoc.h" @@ -29,6 +29,7 @@ int CEF_CALLBACK life_span_handler_on_before_popup( struct _cef_life_span_handler_t* self, cef_browser_t* browser, cef_frame_t* frame, + int popup_id, const cef_string_t* target_url, const cef_string_t* target_frame_name, cef_window_open_disposition_t target_disposition, @@ -128,7 +129,7 @@ int CEF_CALLBACK life_span_handler_on_before_popup( // Execute bool _retval = CefLifeSpanHandlerCppToC::Get(self)->OnBeforePopup( - CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame), + CefBrowserCToCpp::Wrap(browser), CefFrameCToCpp::Wrap(frame), popup_id, CefString(target_url), CefString(target_frame_name), target_disposition, user_gesture ? true : false, popupFeaturesVal, windowInfoObj, clientPtr, settingsObj, extra_infoPtr, &no_javascript_accessBool); @@ -170,6 +171,29 @@ int CEF_CALLBACK life_span_handler_on_before_popup( return _retval; } +void CEF_CALLBACK +life_span_handler_on_before_popup_aborted(struct _cef_life_span_handler_t* self, + cef_browser_t* browser, + int popup_id) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) { + return; + } + // Verify param: browser; type: refptr_diff + DCHECK(browser); + if (!browser) { + return; + } + + // Execute + CefLifeSpanHandlerCppToC::Get(self)->OnBeforePopupAborted( + CefBrowserCToCpp::Wrap(browser), popup_id); +} + void CEF_CALLBACK life_span_handler_on_before_dev_tools_popup( struct _cef_life_span_handler_t* self, cef_browser_t* browser, @@ -365,6 +389,8 @@ life_span_handler_on_before_close(struct _cef_life_span_handler_t* self, CefLifeSpanHandlerCppToC::CefLifeSpanHandlerCppToC() { GetStruct()->on_before_popup = life_span_handler_on_before_popup; + GetStruct()->on_before_popup_aborted = + life_span_handler_on_before_popup_aborted; GetStruct()->on_before_dev_tools_popup = life_span_handler_on_before_dev_tools_popup; GetStruct()->on_after_created = life_span_handler_on_after_created; diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.cc b/libcef_dll/ctocpp/browser_host_ctocpp.cc index 33159c2a6..be166fcf6 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_host_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=2319d794dd3a38c448908114d1b4ea37b34f89dd$ +// $hash=02fc65eec44894d136e5c5ed139927c675a84c6e$ // #include "libcef_dll/ctocpp/browser_host_ctocpp.h" @@ -79,6 +79,20 @@ CefRefPtr CefBrowserHost::CreateBrowserSync( return CefBrowserCToCpp::Wrap(_retval); } +NO_SANITIZE("cfi-icall") +CefRefPtr CefBrowserHost::GetBrowserByIdentifier(int browser_id) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + cef_browser_t* _retval = + cef_browser_host_get_browser_by_identifier(browser_id); + + // Return type: refptr_same + return CefBrowserCToCpp::Wrap(_retval); +} + // VIRTUAL METHODS - Body may be edited by hand. NO_SANITIZE("cfi-icall") @@ -198,6 +212,23 @@ CefWindowHandle CefBrowserHostCToCpp::GetOpenerWindowHandle() { return _retval; } +NO_SANITIZE("cfi-icall") int CefBrowserHostCToCpp::GetOpenerIdentifier() { + shutdown_checker::AssertNotShutdown(); + + cef_browser_host_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, get_opener_identifier)) { + return 0; + } + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Execute + int _retval = _struct->get_opener_identifier(_struct); + + // Return type: simple + return _retval; +} + NO_SANITIZE("cfi-icall") bool CefBrowserHostCToCpp::HasView() { shutdown_checker::AssertNotShutdown(); diff --git a/libcef_dll/ctocpp/browser_host_ctocpp.h b/libcef_dll/ctocpp/browser_host_ctocpp.h index 51068d572..701577d0e 100644 --- a/libcef_dll/ctocpp/browser_host_ctocpp.h +++ b/libcef_dll/ctocpp/browser_host_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=73d8659f17a4ae3319b5bf20807d5c69a1759c04$ +// $hash=f3902d95b54a26c99f532f6d04ce7e8027c86830$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_BROWSER_HOST_CTOCPP_H_ @@ -45,6 +45,7 @@ class CefBrowserHostCToCpp : public CefCToCppRefCounted GetClient() override; CefRefPtr GetRequestContext() override; diff --git a/libcef_dll/ctocpp/life_span_handler_ctocpp.cc b/libcef_dll/ctocpp/life_span_handler_ctocpp.cc index 49a64030b..ff56e7963 100644 --- a/libcef_dll/ctocpp/life_span_handler_ctocpp.cc +++ b/libcef_dll/ctocpp/life_span_handler_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=f8686b23625810a3c7ceab1097eced10acec16ce$ +// $hash=fb1397bef33af6e40385469551b2037eeed8c390$ // #include "libcef_dll/ctocpp/life_span_handler_ctocpp.h" @@ -26,6 +26,7 @@ NO_SANITIZE("cfi-icall") bool CefLifeSpanHandlerCToCpp::OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, WindowOpenDisposition target_disposition, @@ -81,9 +82,9 @@ bool CefLifeSpanHandlerCToCpp::OnBeforePopup( // Execute int _retval = _struct->on_before_popup( _struct, CefBrowserCppToC::Wrap(browser), CefFrameCppToC::Wrap(frame), - target_url.GetStruct(), target_frame_name.GetStruct(), target_disposition, - user_gesture, &popupFeatures, &windowInfo, &clientStruct, &settings, - &extra_infoStruct, &no_javascript_accessInt); + popup_id, target_url.GetStruct(), target_frame_name.GetStruct(), + target_disposition, user_gesture, &popupFeatures, &windowInfo, + &clientStruct, &settings, &extra_infoStruct, &no_javascript_accessInt); // Restore param:client; type: refptr_same_byref if (clientStruct) { @@ -110,6 +111,30 @@ bool CefLifeSpanHandlerCToCpp::OnBeforePopup( return _retval ? true : false; } +NO_SANITIZE("cfi-icall") +void CefLifeSpanHandlerCToCpp::OnBeforePopupAborted( + CefRefPtr browser, + int popup_id) { + shutdown_checker::AssertNotShutdown(); + + cef_life_span_handler_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_before_popup_aborted)) { + return; + } + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser; type: refptr_diff + DCHECK(browser.get()); + if (!browser.get()) { + return; + } + + // Execute + _struct->on_before_popup_aborted(_struct, CefBrowserCppToC::Wrap(browser), + popup_id); +} + NO_SANITIZE("cfi-icall") void CefLifeSpanHandlerCToCpp::OnBeforeDevToolsPopup( CefRefPtr browser, diff --git a/libcef_dll/ctocpp/life_span_handler_ctocpp.h b/libcef_dll/ctocpp/life_span_handler_ctocpp.h index ed692ff2e..9819026a1 100644 --- a/libcef_dll/ctocpp/life_span_handler_ctocpp.h +++ b/libcef_dll/ctocpp/life_span_handler_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=787463118d294d6a9b1004b54fb26b05c9f0a47b$ +// $hash=2e0ae9e4f7e647e43dc4d28fde904298550349e6$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_LIFE_SPAN_HANDLER_CTOCPP_H_ @@ -39,6 +39,7 @@ class CefLifeSpanHandlerCToCpp // CefLifeSpanHandler methods. bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, WindowOpenDisposition target_disposition, @@ -49,6 +50,8 @@ class CefLifeSpanHandlerCToCpp CefBrowserSettings& settings, CefRefPtr& extra_info, bool* no_javascript_access) override; + void OnBeforePopupAborted(CefRefPtr browser, + int popup_id) override; void OnBeforeDevToolsPopup(CefRefPtr browser, CefWindowInfo& windowInfo, CefRefPtr& client, diff --git a/libcef_dll/wrapper/libcef_dll_dylib.cc b/libcef_dll/wrapper/libcef_dll_dylib.cc index efe941e1d..3e7a61528 100644 --- a/libcef_dll/wrapper/libcef_dll_dylib.cc +++ b/libcef_dll/wrapper/libcef_dll_dylib.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=85864cf7616899c4d51fbaf995d8e0db55249bd7$ +// $hash=c4ecb087f15c4b73a529efc0b0715064ebb7fe2b$ // #include @@ -152,6 +152,8 @@ struct libcef_pointers { decltype(&cef_browser_host_create_browser) cef_browser_host_create_browser; decltype(&cef_browser_host_create_browser_sync) cef_browser_host_create_browser_sync; + decltype(&cef_browser_host_get_browser_by_identifier) + cef_browser_host_get_browser_by_identifier; decltype(&cef_command_line_create) cef_command_line_create; decltype(&cef_command_line_get_global) cef_command_line_get_global; decltype(&cef_cookie_manager_get_global_manager) @@ -397,6 +399,7 @@ int libcef_init_pointers(const char* path) { INIT_ENTRY(cef_is_feature_enabled_for_tests); INIT_ENTRY(cef_browser_host_create_browser); INIT_ENTRY(cef_browser_host_create_browser_sync); + INIT_ENTRY(cef_browser_host_get_browser_by_identifier); INIT_ENTRY(cef_command_line_create); INIT_ENTRY(cef_command_line_get_global); INIT_ENTRY(cef_cookie_manager_get_global_manager); @@ -901,6 +904,13 @@ struct _cef_browser_t* cef_browser_host_create_browser_sync( windowInfo, client, url, settings, extra_info, request_context); } +NO_SANITIZE("cfi-icall") +struct _cef_browser_t* cef_browser_host_get_browser_by_identifier( + int browser_id) { + return g_libcef_pointers.cef_browser_host_get_browser_by_identifier( + browser_id); +} + NO_SANITIZE("cfi-icall") struct _cef_command_line_t* cef_command_line_create() { return g_libcef_pointers.cef_command_line_create(); } diff --git a/patch/patch.cfg b/patch/patch.cfg index 62b049d50..1fd69b908 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -400,6 +400,9 @@ patches = [ # Change ContentBrowserClient::ConfigureNetworkContextParams return type to # bool to support cancellation of NetworkContext creation during shutdown. # https://github.com/chromiumembedded/cef/issues/2985 + # + # Add ContentBrowserClient::CreateWindowResult callback. + # https://github.com/chromiumembedded/cef/issues/3776 'name': 'content_2015', }, { @@ -639,6 +642,9 @@ patches = [ # Partially reverts the below changes. # https://chromium-review.googlesource.com/c/chromium/src/+/4829483 # https://bugs.chromium.org/p/chromium/issues/detail?id=1470837#c22 + # + # Add ContentBrowserClient::CreateWindowResult callback. + # https://github.com/chromiumembedded/cef/issues/3776 'name': 'rfh_navigation_4829483' }, { diff --git a/patch/patches/content_2015.patch b/patch/patches/content_2015.patch index 08c0f3c7b..636434268 100644 --- a/patch/patches/content_2015.patch +++ b/patch/patches/content_2015.patch @@ -68,10 +68,23 @@ index 21967547790ca..7bdf48b830a21 100644 std::vector diff --git content/public/browser/content_browser_client.h content/public/browser/content_browser_client.h -index 0f6781dc459e1..8d8deaba4e049 100644 +index 0f6781dc459e1..3f3095cce2ba6 100644 --- content/public/browser/content_browser_client.h +++ content/public/browser/content_browser_client.h -@@ -2188,7 +2188,7 @@ class CONTENT_EXPORT ContentBrowserClient { +@@ -1329,6 +1329,12 @@ class CONTENT_EXPORT ContentBrowserClient { + bool opener_suppressed, + bool* no_javascript_access); + ++ // Called to report the result of new window creation after CanCreateWindow() ++ // returns true. There are cases where the new window may still be canceled. ++ virtual void CreateWindowResult( ++ RenderFrameHost* opener, ++ bool success) {} ++ + // Allows the embedder to return a delegate for the SpeechRecognitionManager. + // The delegate will be owned by the manager. It's valid to return nullptr. + virtual SpeechRecognitionManagerDelegate* +@@ -2188,7 +2194,7 @@ class CONTENT_EXPORT ContentBrowserClient { // // If |relative_partition_path| is the empty string, it means this needs to // create the default NetworkContext for the BrowserContext. @@ -80,7 +93,7 @@ index 0f6781dc459e1..8d8deaba4e049 100644 BrowserContext* context, bool in_memory, const base::FilePath& relative_partition_path, -@@ -2412,6 +2412,22 @@ class CONTENT_EXPORT ContentBrowserClient { +@@ -2412,6 +2418,22 @@ class CONTENT_EXPORT ContentBrowserClient { const net::IsolationInfo& isolation_info, mojo::PendingRemote* out_factory); @@ -103,7 +116,7 @@ index 0f6781dc459e1..8d8deaba4e049 100644 // Creates an OverlayWindow to be used for video or Picture-in-Picture. // This window will house the content shown when in Picture-in-Picture mode. // This will return a new OverlayWindow. -@@ -2472,6 +2488,10 @@ class CONTENT_EXPORT ContentBrowserClient { +@@ -2472,6 +2494,10 @@ class CONTENT_EXPORT ContentBrowserClient { // Used as part of the user agent string. virtual std::string GetProduct(); diff --git a/patch/patches/rfh_navigation_4829483.patch b/patch/patches/rfh_navigation_4829483.patch index 1e7e285b7..4072dead7 100644 --- a/patch/patches/rfh_navigation_4829483.patch +++ b/patch/patches/rfh_navigation_4829483.patch @@ -1,8 +1,25 @@ diff --git content/browser/renderer_host/render_frame_host_impl.cc content/browser/renderer_host/render_frame_host_impl.cc -index 98c865765c57d..7694cd4516dc9 100644 +index 98c865765c57d..f31210f9c3070 100644 --- content/browser/renderer_host/render_frame_host_impl.cc +++ content/browser/renderer_host/render_frame_host_impl.cc -@@ -11466,6 +11466,7 @@ void RenderFrameHostImpl::CommitNavigation( +@@ -9125,6 +9125,16 @@ void RenderFrameHostImpl::CreateNewWindow( + return; + } + ++ callback = base::BindOnce( ++ [](RenderFrameHostImpl* self, ++ CreateNewWindowCallback callback, ++ mojom::CreateNewWindowStatus status, ++ mojom::CreateNewWindowReplyPtr reply) { ++ GetContentClient()->browser()->CreateWindowResult( ++ self, status == mojom::CreateNewWindowStatus::kSuccess); ++ std::move(callback).Run(status, std::move(reply)); ++ }, base::Unretained(this), std::move(callback)); ++ + // Otherwise, consume user activation before we proceed. In particular, it is + // important to do this before we return from the |opener_suppressed| case + // below. +@@ -11466,6 +11476,7 @@ void RenderFrameHostImpl::CommitNavigation( auto browser_calc_origin_to_commit = navigation_request->GetOriginToCommitWithDebugInfo(); if (!process_lock.is_error_page() && !is_mhtml_subframe && diff --git a/tests/cefclient/browser/base_client_handler.cc b/tests/cefclient/browser/base_client_handler.cc index c2886fb26..b27c2cb0e 100644 --- a/tests/cefclient/browser/base_client_handler.cc +++ b/tests/cefclient/browser/base_client_handler.cc @@ -61,7 +61,8 @@ void BaseClientHandler::OnAfterCreated(CefRefPtr browser) { } if (track_as_other_browser_) { - MainContext::Get()->GetRootWindowManager()->OtherBrowserCreated(); + MainContext::Get()->GetRootWindowManager()->OtherBrowserCreated( + browser->GetIdentifier(), browser->GetHost()->GetOpenerIdentifier()); } } @@ -79,7 +80,8 @@ void BaseClientHandler::OnBeforeClose(CefRefPtr browser) { } if (track_as_other_browser_) { - MainContext::Get()->GetRootWindowManager()->OtherBrowserClosed(); + MainContext::Get()->GetRootWindowManager()->OtherBrowserClosed( + browser->GetIdentifier(), browser->GetHost()->GetOpenerIdentifier()); } } diff --git a/tests/cefclient/browser/client_handler.cc b/tests/cefclient/browser/client_handler.cc index 39e93316b..7b17560a7 100644 --- a/tests/cefclient/browser/client_handler.cc +++ b/tests/cefclient/browser/client_handler.cc @@ -924,6 +924,7 @@ bool ClientHandler::OnPreKeyEvent(CefRefPtr browser, bool ClientHandler::OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, CefLifeSpanHandler::WindowOpenDisposition target_disposition, @@ -944,13 +945,20 @@ bool ClientHandler::OnBeforePopup( // Potentially create a new RootWindow for the popup browser that will be // created asynchronously. - CreatePopupWindow(browser, /*is_devtools=*/false, popupFeatures, windowInfo, - client, settings); + CreatePopupWindow(browser, popup_id, /*is_devtools=*/false, popupFeatures, + windowInfo, client, settings); // Allow popup creation. return false; } +void ClientHandler::OnBeforePopupAborted(CefRefPtr browser, + int popup_id) { + CEF_REQUIRE_UI_THREAD(); + MainContext::Get()->GetRootWindowManager()->AbortOrClosePopup( + browser->GetIdentifier(), popup_id); +} + void ClientHandler::OnBeforeDevToolsPopup( CefRefPtr browser, CefWindowInfo& windowInfo, @@ -962,8 +970,8 @@ void ClientHandler::OnBeforeDevToolsPopup( // Potentially create a new RootWindow for the DevTools popup browser that // will be created immediately after this method returns. - if (!CreatePopupWindow(browser, /*is_devtools=*/true, CefPopupFeatures(), - windowInfo, client, settings)) { + if (!CreatePopupWindow(browser, /*popup_id=*/-1, /*is_devtools=*/true, + CefPopupFeatures(), windowInfo, client, settings)) { *use_default_window = true; } } @@ -998,6 +1006,10 @@ bool ClientHandler::DoClose(CefRefPtr browser) { void ClientHandler::OnBeforeClose(CefRefPtr browser) { CEF_REQUIRE_UI_THREAD(); + + // Close all popups that have this browser as the opener. + OnBeforePopupAborted(browser, /*popup_id=*/-1); + BaseClientHandler::OnBeforeClose(browser); NotifyBrowserClosed(browser); } @@ -1308,6 +1320,7 @@ void ClientHandler::ShowSSLInformation(CefRefPtr browser) { } bool ClientHandler::CreatePopupWindow(CefRefPtr browser, + int popup_id, bool is_devtools, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, @@ -1320,7 +1333,8 @@ bool ClientHandler::CreatePopupWindow(CefRefPtr browser, // May return nullptr if UseDefaultPopup() returns true. return !!MainContext::Get()->GetRootWindowManager()->CreateRootWindowAsPopup( use_views_, use_alloy_style_, with_controls_ && !is_devtools, is_osr_, - is_devtools, popupFeatures, windowInfo, client, settings); + browser->GetIdentifier(), popup_id, is_devtools, popupFeatures, + windowInfo, client, settings); } void ClientHandler::NotifyBrowserCreated(CefRefPtr browser) { diff --git a/tests/cefclient/browser/client_handler.h b/tests/cefclient/browser/client_handler.h index 42e86cdf3..e3c9a0b7f 100644 --- a/tests/cefclient/browser/client_handler.h +++ b/tests/cefclient/browser/client_handler.h @@ -212,6 +212,7 @@ class ClientHandler : public BaseClientHandler, bool OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, CefLifeSpanHandler::WindowOpenDisposition target_disposition, @@ -222,6 +223,8 @@ class ClientHandler : public BaseClientHandler, CefBrowserSettings& settings, CefRefPtr& extra_info, bool* no_javascript_access) override; + void OnBeforePopupAborted(CefRefPtr browser, + int popup_id) override; void OnBeforeDevToolsPopup(CefRefPtr browser, CefWindowInfo& windowInfo, CefRefPtr& client, @@ -331,6 +334,7 @@ class ClientHandler : public BaseClientHandler, // will be true if the window will be used for DevTools. Returns true if a // RootWindow was created for the popup. bool CreatePopupWindow(CefRefPtr browser, + int popup_id, bool is_devtools, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, diff --git a/tests/cefclient/browser/default_client_handler.cc b/tests/cefclient/browser/default_client_handler.cc index 3c649c1a1..5d5173de2 100644 --- a/tests/cefclient/browser/default_client_handler.cc +++ b/tests/cefclient/browser/default_client_handler.cc @@ -27,6 +27,7 @@ CefRefPtr DefaultClientHandler::GetForClient( bool DefaultClientHandler::OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, CefLifeSpanHandler::WindowOpenDisposition target_disposition, @@ -52,11 +53,27 @@ bool DefaultClientHandler::OnBeforePopup( // created asynchronously. MainContext::Get()->GetRootWindowManager()->CreateRootWindowAsPopup( config.use_views, use_alloy_style_, config.with_controls, - /*is_osr=*/false, /*is_devtools=*/false, popupFeatures, windowInfo, - client, settings); + /*is_osr=*/false, browser->GetIdentifier(), popup_id, + /*is_devtools=*/false, popupFeatures, windowInfo, client, settings); // Allow popup creation. return false; } +void DefaultClientHandler::OnBeforePopupAborted(CefRefPtr browser, + int popup_id) { + CEF_REQUIRE_UI_THREAD(); + MainContext::Get()->GetRootWindowManager()->AbortOrClosePopup( + browser->GetIdentifier(), popup_id); +} + +void DefaultClientHandler::OnBeforeClose(CefRefPtr browser) { + CEF_REQUIRE_UI_THREAD(); + + // Close all popups that have this browser as the opener. + OnBeforePopupAborted(browser, /*popup_id=*/-1); + + BaseClientHandler::OnBeforeClose(browser); +} + } // namespace client diff --git a/tests/cefclient/browser/default_client_handler.h b/tests/cefclient/browser/default_client_handler.h index e404ba115..dd219006f 100644 --- a/tests/cefclient/browser/default_client_handler.h +++ b/tests/cefclient/browser/default_client_handler.h @@ -29,6 +29,7 @@ class DefaultClientHandler : public BaseClientHandler { bool OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, CefLifeSpanHandler::WindowOpenDisposition target_disposition, @@ -39,6 +40,9 @@ class DefaultClientHandler : public BaseClientHandler { CefBrowserSettings& settings, CefRefPtr& extra_info, bool* no_javascript_access) override; + void OnBeforePopupAborted(CefRefPtr browser, + int popup_id) override; + void OnBeforeClose(CefRefPtr browser) override; private: // Used to determine the object type. diff --git a/tests/cefclient/browser/root_window.cc b/tests/cefclient/browser/root_window.cc index 1daea5bb5..f48e90a34 100644 --- a/tests/cefclient/browser/root_window.cc +++ b/tests/cefclient/browser/root_window.cc @@ -29,4 +29,28 @@ scoped_refptr RootWindow::GetForBrowser(int browser_id) { browser_id); } +bool RootWindow::IsWindowCreated() const { + REQUIRE_MAIN_THREAD(); + return window_created_; +} + +void RootWindow::SetPopupId(int opener_browser_id, int popup_id) { + DCHECK_GT(opener_browser_id, 0); + DCHECK_GT(popup_id, 0); + opener_browser_id_ = opener_browser_id; + popup_id_ = popup_id; +} + +bool RootWindow::IsPopupIdMatch(int opener_browser_id, int popup_id) const { + if (opener_browser_id_ == 0 || popup_id_ == 0) { + // Not a popup. + return false; + } + if (popup_id < 0) { + // Only checking the opener. + return opener_browser_id == opener_browser_id_; + } + return opener_browser_id == opener_browser_id_ && popup_id == popup_id_; +} + } // namespace client diff --git a/tests/cefclient/browser/root_window.h b/tests/cefclient/browser/root_window.h index 56ece0aa6..6a1a5e00a 100644 --- a/tests/cefclient/browser/root_window.h +++ b/tests/cefclient/browser/root_window.h @@ -209,6 +209,19 @@ class RootWindow // Returns true if this window is using windowless rendering (osr). virtual bool WithWindowlessRendering() const = 0; + // Returns true if this object has been initialized. + bool IsInitialized() const { return initialized_; } + + // Returns true if the platform window has been created. + bool IsWindowCreated() const; + + // Used to uniquely identify popup windows. + void SetPopupId(int opener_browser_id, int popup_id); + // If |popup_id| is -1 only match |opener_browser_id|. + bool IsPopupIdMatch(int opener_browser_id, int popup_id) const; + int opener_browser_id() const { return opener_browser_id_; } + int popup_id() const { return popup_id_; } + protected: // Allow deletion via scoped_refptr only. friend struct DeleteOnMainThread; @@ -217,10 +230,19 @@ class RootWindow explicit RootWindow(bool use_alloy_style); virtual ~RootWindow(); + // Members set during initialization. Safe to access from any thread. Delegate* delegate_ = nullptr; + bool initialized_ = false; + + // Only accessed on the main thread. + bool window_created_ = false; private: const bool use_alloy_style_; + + // Members set during initialization. Safe to access from any thread. + int opener_browser_id_ = 0; + int popup_id_ = 0; }; } // namespace client diff --git a/tests/cefclient/browser/root_window_gtk.cc b/tests/cefclient/browser/root_window_gtk.cc index 7026cd225..2a61deb76 100644 --- a/tests/cefclient/browser/root_window_gtk.cc +++ b/tests/cefclient/browser/root_window_gtk.cc @@ -86,7 +86,6 @@ RootWindowGtk::RootWindowGtk(bool use_alloy_style) always_on_top_(false), with_osr_(false), is_popup_(false), - initialized_(false), window_(nullptr), back_button_(nullptr), forward_button_(nullptr), @@ -443,6 +442,8 @@ void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings, browser_window_->ShowPopup(parent, browser_bounds_.x, browser_bounds_.y, browser_bounds_.width, browser_bounds_.height); } + + window_created_ = true; } void RootWindowGtk::OnBrowserCreated(CefRefPtr browser) { diff --git a/tests/cefclient/browser/root_window_gtk.h b/tests/cefclient/browser/root_window_gtk.h index 36480cc77..568c2088b 100644 --- a/tests/cefclient/browser/root_window_gtk.h +++ b/tests/cefclient/browser/root_window_gtk.h @@ -131,7 +131,6 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate { bool is_popup_; CefRect start_rect_; std::unique_ptr browser_window_; - bool initialized_; // Main window. GtkWidget* window_; diff --git a/tests/cefclient/browser/root_window_mac.mm b/tests/cefclient/browser/root_window_mac.mm index 4628990e1..1f7b98d22 100644 --- a/tests/cefclient/browser/root_window_mac.mm +++ b/tests/cefclient/browser/root_window_mac.mm @@ -240,7 +240,6 @@ class RootWindowMacImpl CefRect initial_bounds_; cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL; std::unique_ptr browser_window_; - bool initialized_ = false; // Main window. NSWindow* window_ = nil; @@ -273,7 +272,7 @@ RootWindowMacImpl::~RootWindowMacImpl() { void RootWindowMacImpl::Init(RootWindow::Delegate* delegate, std::unique_ptr config, const CefBrowserSettings& settings) { - DCHECK(!initialized_); + DCHECK(!root_window_.initialized_); with_controls_ = config->with_controls; with_osr_ = config->with_osr; @@ -294,7 +293,7 @@ void RootWindowMacImpl::Init(RootWindow::Delegate* delegate, CreateBrowserWindow(config->url); - initialized_ = true; + root_window_.initialized_ = true; CreateRootWindow(settings, config->initially_hidden); } @@ -307,7 +306,7 @@ void RootWindowMacImpl::InitAsPopup(RootWindow::Delegate* delegate, CefRefPtr& client, CefBrowserSettings& settings) { DCHECK(delegate); - DCHECK(!initialized_); + DCHECK(!root_window_.initialized_); with_controls_ = with_controls; with_osr_ = with_osr; @@ -328,7 +327,7 @@ void RootWindowMacImpl::InitAsPopup(RootWindow::Delegate* delegate, CreateBrowserWindow(std::string()); - initialized_ = true; + root_window_.initialized_ = true; // The new popup is initially parented to a temporary window. The native root // window will be created after the browser is created and the popup window @@ -632,6 +631,8 @@ void RootWindowMacImpl::CreateRootWindow(const CefBrowserSettings& settings, // Show the window. Show(mode); } + + root_window_.window_created_ = true; } void RootWindowMacImpl::OnBrowserCreated(CefRefPtr browser) { diff --git a/tests/cefclient/browser/root_window_manager.cc b/tests/cefclient/browser/root_window_manager.cc index e3d3abf56..55689b856 100644 --- a/tests/cefclient/browser/root_window_manager.cc +++ b/tests/cefclient/browser/root_window_manager.cc @@ -125,6 +125,8 @@ scoped_refptr RootWindowManager::CreateRootWindowAsPopup( bool use_alloy_style, bool with_controls, bool with_osr, + int opener_browser_id, + int popup_id, bool is_devtools, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, @@ -142,6 +144,9 @@ scoped_refptr RootWindowManager::CreateRootWindowAsPopup( return nullptr; } + CHECK_GT(opener_browser_id, 0); + CHECK(popup_id > 0 || is_devtools); + SanityCheckWindowConfig(is_devtools, use_views, use_alloy_style, with_osr); if (!temp_window_ && !use_views) { @@ -154,6 +159,9 @@ scoped_refptr RootWindowManager::CreateRootWindowAsPopup( scoped_refptr root_window = RootWindow::Create(use_views, use_alloy_style); + if (!is_devtools) { + root_window->SetPopupId(opener_browser_id, popup_id); + } root_window->InitAsPopup(this, with_controls, with_osr, popupFeatures, windowInfo, client, settings); @@ -163,6 +171,12 @@ scoped_refptr RootWindowManager::CreateRootWindowAsPopup( return root_window; } +void RootWindowManager::AbortOrClosePopup(int opener_browser_id, int popup_id) { + CEF_REQUIRE_UI_THREAD(); + // Continue on the main thread. + OnAbortOrClosePopup(opener_browser_id, popup_id); +} + scoped_refptr RootWindowManager::GetWindowForBrowser( int browser_id) const { REQUIRE_MAIN_THREAD(); @@ -202,28 +216,48 @@ void RootWindowManager::CloseAllWindows(bool force) { } } -void RootWindowManager::OtherBrowserCreated() { +void RootWindowManager::OtherBrowserCreated(int browser_id, + int opener_browser_id) { if (!CURRENTLY_ON_MAIN_THREAD()) { // Execute this method on the main thread. MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::OtherBrowserCreated, - base::Unretained(this))); + base::Unretained(this), browser_id, + opener_browser_id)); return; } other_browser_ct_++; + + // Track ownership of popup browsers that don't have a RootWindow. + if (opener_browser_id > 0) { + other_browser_owners_[opener_browser_id].insert(browser_id); + } } -void RootWindowManager::OtherBrowserClosed() { +void RootWindowManager::OtherBrowserClosed(int browser_id, + int opener_browser_id) { if (!CURRENTLY_ON_MAIN_THREAD()) { // Execute this method on the main thread. MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::OtherBrowserClosed, - base::Unretained(this))); + base::Unretained(this), browser_id, + opener_browser_id)); return; } DCHECK_GT(other_browser_ct_, 0); other_browser_ct_--; + // Track ownership of popup browsers that don't have a RootWindow. + if (opener_browser_id > 0) { + DCHECK(other_browser_owners_.contains(opener_browser_id)); + auto& child_set = other_browser_owners_[opener_browser_id]; + DCHECK(child_set.contains(browser_id)); + child_set.erase(browser_id); + if (child_set.empty()) { + other_browser_owners_.erase(opener_browser_id); + } + } + MaybeCleanup(); } @@ -244,6 +278,55 @@ void RootWindowManager::OnRootWindowCreated( } } +void RootWindowManager::OnAbortOrClosePopup(int opener_browser_id, + int popup_id) { + if (!CURRENTLY_ON_MAIN_THREAD()) { + // Execute this method on the main thread. + MAIN_POST_CLOSURE(base::BindOnce(&RootWindowManager::OnAbortOrClosePopup, + base::Unretained(this), opener_browser_id, + popup_id)); + return; + } + + // Use a copy of |root_windows_| because the original set may be modified + // in OnRootWindowDestroyed while iterating. + RootWindowSet root_windows = root_windows_; + + // Close or destroy the associated RootWindow(s). This may be a specific popup + // (|popup_id| > 0), or all popups if the opener is closing (|popup_id| < 0). + for (auto root_window : root_windows) { + if (root_window->IsPopupIdMatch(opener_browser_id, popup_id)) { + const bool window_created = root_window->IsWindowCreated(); + LOG(INFO) << (window_created ? "Closing" : "Aborting") << " popup " + << root_window->popup_id() << " of browser " + << opener_browser_id; + if (window_created) { + // Close the window in the usual way. Will result in a call to + // OnRootWindowDestroyed. + root_window->Close(/*force=*/false); + } else { + // The window was not created, so destroy directly. + OnRootWindowDestroyed(root_window.get()); + } + } + } + + // Close all other associated popups if the opener is closing. These popups + // don't have a RootWindow (e.g. when running with `--use-default-popup`). + if (popup_id < 0 && other_browser_owners_.contains(opener_browser_id)) { + // Use a copy as the original set may be modified in OtherBrowserClosed + // while iterating. + auto set = other_browser_owners_[opener_browser_id]; + for (auto browser_id : set) { + if (auto browser = CefBrowserHost::GetBrowserByIdentifier(browser_id)) { + LOG(INFO) << "Closing popup browser " << browser_id << " of browser " + << opener_browser_id; + browser->GetHost()->CloseBrowser(/*force=*/false); + } + } + } +} + CefRefPtr RootWindowManager::GetRequestContext() { REQUIRE_MAIN_THREAD(); return CreateRequestContext(RequestContextCallback()); diff --git a/tests/cefclient/browser/root_window_manager.h b/tests/cefclient/browser/root_window_manager.h index 6dfea0663..1def1f98c 100644 --- a/tests/cefclient/browser/root_window_manager.h +++ b/tests/cefclient/browser/root_window_manager.h @@ -6,6 +6,7 @@ #define CEF_TESTS_CEFCLIENT_BROWSER_ROOT_WINDOW_MANAGER_H_ #pragma once +#include #include #include @@ -40,12 +41,18 @@ class RootWindowManager : public RootWindow::Delegate { bool use_alloy_style, bool with_controls, bool with_osr, + int opener_browser_id, + int popup_id, bool is_devtools, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings); + // Abort or close the popup matching the specified identifiers. If |popup_id| + // is -1 then all popups for |opener_browser_id| will be impacted. + void AbortOrClosePopup(int opener_browser_id, int popup_id); + // Returns the RootWindow associated with the specified browser ID. Must be // called on the main thread. scoped_refptr GetWindowForBrowser(int browser_id) const; @@ -63,8 +70,11 @@ class RootWindowManager : public RootWindow::Delegate { } // Track other browsers that are not directly associated with a RootWindow. - void OtherBrowserCreated(); - void OtherBrowserClosed(); + // This may be an overlay browser, a popup created with `--use-default-popup`, + // or a browser using default Chrome UI. |opener_browser_id| will be > 0 for + // popup browsers. + void OtherBrowserCreated(int browser_id, int opener_browser_id); + void OtherBrowserClosed(int browser_id, int opener_browser_id); private: // Allow deletion via std::unique_ptr only. @@ -73,6 +83,7 @@ class RootWindowManager : public RootWindow::Delegate { ~RootWindowManager() override; void OnRootWindowCreated(scoped_refptr root_window); + void OnAbortOrClosePopup(int opener_browser_id, int popup_id); // RootWindow::Delegate methods. CefRefPtr GetRequestContext() override; @@ -95,12 +106,19 @@ class RootWindowManager : public RootWindow::Delegate { bool request_context_shared_cache_; // Existing root windows. Only accessed on the main thread. - typedef std::set> RootWindowSet; + using RootWindowSet = std::set>; RootWindowSet root_windows_; - // Count of other browsers. Only accessed on the main thread. + // Count of browsers that are not directly associated with a RootWindow. Only + // accessed on the main thread. int other_browser_ct_ = 0; + // Map of owner browser ID to popup browser IDs for popups that don't have a + // RootWindow. Only accessed on the main thread. + using BrowserIdSet = std::set; + using BrowserOwnerMap = std::map; + BrowserOwnerMap other_browser_owners_; + // The currently active/foreground RootWindow. Only accessed on the main // thread. scoped_refptr active_root_window_; diff --git a/tests/cefclient/browser/root_window_views.cc b/tests/cefclient/browser/root_window_views.cc index d39bf9f0d..69f378ac9 100644 --- a/tests/cefclient/browser/root_window_views.cc +++ b/tests/cefclient/browser/root_window_views.cc @@ -235,6 +235,17 @@ void RootWindowViews::OnViewsWindowCreated(CefRefPtr window) { DCHECK(!window_); window_ = window; window_->SetAlwaysOnTop(config_->always_on_top); + + if (CURRENTLY_ON_MAIN_THREAD()) { + window_created_ = true; + } else { + // Execute on the main thread. + MAIN_POST_CLOSURE(base::BindOnce( + [](scoped_refptr self) { + self->window_created_ = true; + }, + scoped_refptr(this))); + } } void RootWindowViews::OnViewsWindowClosing(CefRefPtr window) { diff --git a/tests/cefclient/browser/root_window_views.h b/tests/cefclient/browser/root_window_views.h index e36ad51d1..43dc7ea17 100644 --- a/tests/cefclient/browser/root_window_views.h +++ b/tests/cefclient/browser/root_window_views.h @@ -105,7 +105,6 @@ class RootWindowViews : public RootWindow, // Members set during initialization. Safe to access from any thread. std::unique_ptr config_; CefRefPtr client_handler_; - bool initialized_ = false; // Only accessed on the main thread. CefRefPtr browser_; diff --git a/tests/cefclient/browser/root_window_win.cc b/tests/cefclient/browser/root_window_win.cc index aca122db4..48c877624 100644 --- a/tests/cefclient/browser/root_window_win.cc +++ b/tests/cefclient/browser/root_window_win.cc @@ -1017,6 +1017,8 @@ void RootWindowWin::OnCreate(LPCREATESTRUCT lpCreateStruct) { browser_window_->ShowPopup(hwnd_, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); } + + window_created_ = true; } bool RootWindowWin::OnClose() { diff --git a/tests/cefclient/browser/root_window_win.h b/tests/cefclient/browser/root_window_win.h index ec5d74ee0..b1745442d 100644 --- a/tests/cefclient/browser/root_window_win.h +++ b/tests/cefclient/browser/root_window_win.h @@ -126,7 +126,6 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate { cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL; std::unique_ptr browser_window_; CefBrowserSettings browser_settings_; - bool initialized_ = false; // Main window. HWND hwnd_ = nullptr; diff --git a/tests/ceftests/frame_handler_unittest.cc b/tests/ceftests/frame_handler_unittest.cc index a99e5341d..d83e37646 100644 --- a/tests/ceftests/frame_handler_unittest.cc +++ b/tests/ceftests/frame_handler_unittest.cc @@ -412,6 +412,11 @@ struct FrameStatus { EXPECT_FALSE(browser->IsValid()) << func; } + const auto browser_id = browser->GetIdentifier(); + EXPECT_GT(browser_id, 0) << func; + auto get_browser = CefBrowserHost::GetBrowserByIdentifier(browser_id); + EXPECT_TRUE(get_browser && get_browser->IsSame(browser)) << func; + // Note that this might not be the same main frame as us when navigating // cross-origin, because the new main frame object is assigned to the // browser before the CefFrameHandler callbacks related to main frame change @@ -1641,6 +1646,7 @@ class ParentOrderMainTestHandler : public OrderMainTestHandler { bool OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, CefLifeSpanHandler::WindowOpenDisposition target_disposition, diff --git a/tests/ceftests/navigation_unittest.cc b/tests/ceftests/navigation_unittest.cc index 8039a9063..1aff16e3d 100644 --- a/tests/ceftests/navigation_unittest.cc +++ b/tests/ceftests/navigation_unittest.cc @@ -1480,6 +1480,7 @@ class OrderNavTestHandler : public TestHandler { bool OnBeforePopup( CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, CefLifeSpanHandler::WindowOpenDisposition target_disposition, @@ -2270,6 +2271,7 @@ class PopupSimultaneousTestHandler : public TestHandler { bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, @@ -2429,6 +2431,7 @@ class PopupJSWindowOpenTestHandler : public TestHandler { bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, @@ -2576,6 +2579,7 @@ class PopupJSWindowEmptyTestHandler : public TestHandler { bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, @@ -3602,6 +3606,7 @@ class ExtraInfoNavTestHandler : public TestHandler { bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, diff --git a/tests/ceftests/osr_display_unittest.cc b/tests/ceftests/osr_display_unittest.cc index 808755b3b..9d27393ee 100644 --- a/tests/ceftests/osr_display_unittest.cc +++ b/tests/ceftests/osr_display_unittest.cc @@ -213,6 +213,7 @@ class OsrPopupJSOtherClientTestHandler : public TestHandler, bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, @@ -346,6 +347,7 @@ class OsrPopupJSOtherCefClient : public CefClient, bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, diff --git a/tests/ceftests/request_context_unittest.cc b/tests/ceftests/request_context_unittest.cc index 0f85cc83e..11f57f5f6 100644 --- a/tests/ceftests/request_context_unittest.cc +++ b/tests/ceftests/request_context_unittest.cc @@ -203,6 +203,8 @@ class PopupTestHandler : public TestHandler { enum Mode { MODE_WINDOW_OPEN, MODE_TARGETED_LINK, + // The no-referrer popup won't have an opener from the renderer's + // perspective, but we still track it from the browser's perspective. MODE_NOREFERRER_LINK, }; @@ -284,6 +286,7 @@ class PopupTestHandler : public TestHandler { bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, @@ -294,8 +297,14 @@ class PopupTestHandler : public TestHandler { CefBrowserSettings& settings, CefRefPtr& extra_info, bool* no_javascript_access) override { + EXPECT_FALSE(got_on_before_popup_); + EXPECT_FALSE(got_on_before_popup_aborted_); + EXPECT_FALSE(got_on_after_created_popup_); got_on_before_popup_.yes(); + // Only ever a single popup with this test. + EXPECT_EQ(1, popup_id); + const std::string& url = target_url; EXPECT_STREQ(url.c_str(), popup_url_.c_str()); @@ -310,6 +319,30 @@ class PopupTestHandler : public TestHandler { return false; } + void OnBeforePopupAborted(CefRefPtr browser, + int popup_id) override { + EXPECT_TRUE(got_on_before_popup_); + EXPECT_FALSE(got_on_before_popup_aborted_); + EXPECT_FALSE(got_on_after_created_popup_); + got_on_before_popup_aborted_.yes(); + + // Only ever a single popup with this test. + EXPECT_EQ(1, popup_id); + } + + void OnAfterCreated(CefRefPtr browser) override { + if (browser->IsPopup()) { + EXPECT_TRUE(got_on_before_popup_); + EXPECT_FALSE(got_on_before_popup_aborted_); + got_on_after_created_popup_.yes(); + // Opener is the main browser. + EXPECT_EQ(GetBrowserId(), browser->GetHost()->GetOpenerIdentifier()); + } else { + EXPECT_EQ(0, browser->GetHost()->GetOpenerIdentifier()); + } + TestHandler::OnAfterCreated(browser); + } + void OnBeforeClose(CefRefPtr browser) override { TestHandler::OnBeforeClose(browser); @@ -372,6 +405,8 @@ class PopupTestHandler : public TestHandler { // Verify test expectations. EXPECT_TRUE(got_load_end1_); EXPECT_TRUE(got_on_before_popup_); + EXPECT_FALSE(got_on_before_popup_aborted_); + EXPECT_TRUE(got_on_after_created_popup_); EXPECT_TRUE(got_load_end2_); EXPECT_TRUE(got_cookie1_); EXPECT_TRUE(got_cookie2_); @@ -389,6 +424,8 @@ class PopupTestHandler : public TestHandler { TrackCallback got_load_end1_; TrackCallback got_on_before_popup_; + TrackCallback got_on_before_popup_aborted_; + TrackCallback got_on_after_created_popup_; TrackCallback got_load_end2_; TrackCallback got_cookie1_; TrackCallback got_cookie2_; @@ -516,6 +553,7 @@ class PopupNavTestHandler : public TestHandler { bool OnBeforePopup(CefRefPtr browser, CefRefPtr frame, + int popup_id, const CefString& target_url, const CefString& target_frame_name, cef_window_open_disposition_t target_disposition, @@ -527,10 +565,16 @@ class PopupNavTestHandler : public TestHandler { CefRefPtr& extra_info, bool* no_javascript_access) override { EXPECT_FALSE(got_on_before_popup_); + EXPECT_FALSE(got_on_before_popup_aborted_); got_on_before_popup_.yes(); EXPECT_TRUE(CefCurrentlyOn(TID_UI)); - EXPECT_EQ(GetBrowserId(), browser->GetIdentifier()); + + // Only ever a single popup with this test. + EXPECT_EQ(1, popup_id); + opener_browser_id_ = browser->GetIdentifier(); + EXPECT_EQ(GetBrowserId(), opener_browser_id_); + EXPECT_STREQ(kPopupNavPageUrl, frame->GetURL().ToString().c_str()); EXPECT_STREQ(kPopupNavPopupUrl, target_url.ToString().c_str()); EXPECT_STREQ(kPopupNavPopupName, target_frame_name.ToString().c_str()); @@ -547,8 +591,32 @@ class PopupNavTestHandler : public TestHandler { return (mode_ == DENY); // Return true to cancel the popup. } + void OnBeforePopupAborted(CefRefPtr browser, + int popup_id) override { + EXPECT_TRUE(got_on_before_popup_); + EXPECT_FALSE(got_on_before_popup_aborted_); + got_on_before_popup_aborted_.yes(); + + EXPECT_TRUE(CefCurrentlyOn(TID_UI)); + + // Can't use GetBrowserId() here because the opener is already closing. + EXPECT_EQ(opener_browser_id_, browser->GetIdentifier()); + EXPECT_FALSE(browser->IsValid()); + + // Only ever a single popup with this test. + EXPECT_EQ(1, popup_id); + } + void OnAfterCreated(CefRefPtr browser) override { TestHandler::OnAfterCreated(browser); + if (browser->IsPopup()) { + EXPECT_TRUE(got_on_before_popup_); + EXPECT_EQ(opener_browser_id_, browser->GetHost()->GetOpenerIdentifier()); + } else { + EXPECT_FALSE(got_on_before_popup_); + EXPECT_EQ(0, browser->GetHost()->GetOpenerIdentifier()); + } + EXPECT_FALSE(got_on_before_popup_aborted_); if (browser->IsPopup() && (mode_ == DESTROY_PARENT_AFTER_CREATION || mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE)) { @@ -684,10 +752,24 @@ class PopupNavTestHandler : public TestHandler { EXPECT_TRUE(got_load_end_); // OnBeforePopup may come before or after browser destruction with the - // DESTROY_PARENT_BEFORE_CREATION* tests. + // DESTROY_PARENT_BEFORE_CREATION* tests and Alloy style browsers. if (mode_ != DESTROY_PARENT_BEFORE_CREATION && mode_ != DESTROY_PARENT_BEFORE_CREATION_FORCE) { EXPECT_TRUE(got_on_before_popup_); + } else if (!use_alloy_style_browser()) { + EXPECT_FALSE(got_on_before_popup_); + } + + if (mode_ == DESTROY_PARENT_DURING_CREATION || + mode_ == DESTROY_PARENT_DURING_CREATION_FORCE || + mode_ == DESTROY_PARENT_AFTER_CREATION || + mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE) { + // Timing of Alloy style browsers may not result in abort. + if (!use_alloy_style_browser()) { + EXPECT_TRUE(got_on_before_popup_aborted_); + } + } else { + EXPECT_FALSE(got_on_before_popup_aborted_); } if (mode_ == ALLOW_CLOSE_POPUP_FIRST || mode_ == ALLOW_CLOSE_POPUP_LAST) { @@ -737,6 +819,8 @@ class PopupNavTestHandler : public TestHandler { const std::string rc_cache_path_; TrackCallback got_on_before_popup_; + int opener_browser_id_ = 0; + TrackCallback got_on_before_popup_aborted_; TrackCallback got_load_start_; TrackCallback got_load_error_; TrackCallback got_load_end_;