From 882bc19fddd687b72d538cb3394964bb1e5e79ec Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Fri, 28 Oct 2022 14:17:18 -0400 Subject: [PATCH] cefclient: views: Support window state restore (see issue #3359) The cefclient sample app will persist window state across application restart if run with views, cache_path and persist_user_references enabled. To test: 1. Run `cefclient --use-views --cache-path=/path/to/cache --persist-user-preferences` 2. Move or resize the window, maximize, minimize, etc. 3. Exit cefclient. 4. Run cefclient again with the same arguments. The previous window state will be restored. --- cef_paths2.gypi | 2 + include/capi/views/cef_window_delegate_capi.h | 17 +- include/cef_api_hash.h | 8 +- include/views/cef_window_delegate.h | 14 ++ libcef/browser/views/window_impl.cc | 3 + libcef/browser/views/window_view.cc | 6 + .../cpptoc/views/window_delegate_cpptoc.cc | 53 ++++- .../ctocpp/views/window_delegate_ctocpp.cc | 42 +++- .../ctocpp/views/window_delegate_ctocpp.h | 5 +- tests/cefclient/browser/client_browser.cc | 11 + tests/cefclient/browser/client_prefs.cc | 202 ++++++++++++++++++ tests/cefclient/browser/client_prefs.h | 29 +++ tests/cefclient/browser/root_window.cc | 8 +- tests/cefclient/browser/root_window.h | 14 +- tests/cefclient/browser/root_window_views.cc | 56 +++-- tests/cefclient/browser/root_window_views.h | 22 +- tests/cefclient/browser/views_window.cc | 67 ++++-- tests/cefclient/browser/views_window.h | 17 +- 18 files changed, 511 insertions(+), 65 deletions(-) create mode 100644 tests/cefclient/browser/client_prefs.cc create mode 100644 tests/cefclient/browser/client_prefs.h diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 6634f9cea..1b876e3b6 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -234,6 +234,8 @@ 'tests/cefclient/browser/client_handler_osr.h', 'tests/cefclient/browser/client_handler_std.cc', 'tests/cefclient/browser/client_handler_std.h', + 'tests/cefclient/browser/client_prefs.cc', + 'tests/cefclient/browser/client_prefs.h', 'tests/cefclient/browser/client_types.h', 'tests/cefclient/browser/dialog_test.cc', 'tests/cefclient/browser/dialog_test.h', diff --git a/include/capi/views/cef_window_delegate_capi.h b/include/capi/views/cef_window_delegate_capi.h index d6ca7df75..a74f9e910 100644 --- a/include/capi/views/cef_window_delegate_capi.h +++ b/include/capi/views/cef_window_delegate_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=ea84b76b6965d1419e416581d87e82f74680bd07$ +// $hash=e1657ed68132b846ad638dc87bc5ee9b9c10f014$ // #ifndef CEF_INCLUDE_CAPI_VIEWS_CEF_WINDOW_DELEGATE_CAPI_H_ @@ -65,6 +65,12 @@ typedef struct _cef_window_delegate_t { void(CEF_CALLBACK* on_window_created)(struct _cef_window_delegate_t* self, struct _cef_window_t* window); + /// + /// Called when |window| is closing. + /// + void(CEF_CALLBACK* on_window_closing)(struct _cef_window_delegate_t* self, + struct _cef_window_t* window); + /// /// Called when |window| is destroyed. Release all references to |window| and /// do not attempt to execute any functions on |window| after this callback @@ -81,6 +87,15 @@ typedef struct _cef_window_delegate_t { struct _cef_window_t* window, int active); + /// + /// Called when |window| bounds have changed. |new_bounds| will be in DIP + /// screen coordinates. + /// + void(CEF_CALLBACK* on_window_bounds_changed)( + struct _cef_window_delegate_t* self, + struct _cef_window_t* window, + const cef_rect_t* new_bounds); + /// /// Return the parent for |window| or NULL if the |window| does not have a /// parent. Windows with parents will not get a taskbar button. Set |is_menu| diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index d4919355f..dc4fd2ae6 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 "043531d536c019ddd0c7c3096e7642bda2748755" +#define CEF_API_HASH_UNIVERSAL "46cd296ee58a28371e4f82c52cf447c516a4dc4b" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "4f82dbb840cb3ce49078c43de0c9d96b4aec37b8" +#define CEF_API_HASH_PLATFORM "92c7ff6ca220e1c55081d020fd2b1d96d102a8fa" #elif defined(OS_MAC) -#define CEF_API_HASH_PLATFORM "a6da25ff35d7fb833a3f91bc4586a21354c90457" +#define CEF_API_HASH_PLATFORM "7d01ca5ec82dbefa6f3f84fa4883a0059d96ccb5" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "72eeaa876bc8c0a6f2fba5c54c855375683f2b9e" +#define CEF_API_HASH_PLATFORM "f0334f17e0823c7cf7ee15bb53960efb21d31468" #endif #ifdef __cplusplus diff --git a/include/views/cef_window_delegate.h b/include/views/cef_window_delegate.h index 959768bd6..22c2aef52 100644 --- a/include/views/cef_window_delegate.h +++ b/include/views/cef_window_delegate.h @@ -55,6 +55,12 @@ class CefWindowDelegate : public CefPanelDelegate { /*--cef()--*/ virtual void OnWindowCreated(CefRefPtr window) {} + /// + /// Called when |window| is closing. + /// + /*--cef()--*/ + virtual void OnWindowClosing(CefRefPtr window) {} + /// /// Called when |window| is destroyed. Release all references to |window| and /// do not attempt to execute any methods on |window| after this callback @@ -70,6 +76,14 @@ class CefWindowDelegate : public CefPanelDelegate { virtual void OnWindowActivationChanged(CefRefPtr window, bool active) {} + /// + /// Called when |window| bounds have changed. |new_bounds| will be in DIP + /// screen coordinates. + /// + /*--cef()--*/ + virtual void OnWindowBoundsChanged(CefRefPtr window, + const CefRect& new_bounds) {} + /// /// Return the parent for |window| or NULL if the |window| does not have a /// parent. Windows with parents will not get a taskbar button. Set |is_menu| diff --git a/libcef/browser/views/window_impl.cc b/libcef/browser/views/window_impl.cc index 8bc2174e6..64e5c443e 100644 --- a/libcef/browser/views/window_impl.cc +++ b/libcef/browser/views/window_impl.cc @@ -391,6 +391,9 @@ void CefWindowImpl::OnWindowClosing() { #if defined(USE_AURA) unhandled_key_event_handler_.reset(); #endif + + if (delegate()) + delegate()->OnWindowClosing(this); } void CefWindowImpl::OnWindowViewDeleted() { diff --git a/libcef/browser/views/window_view.cc b/libcef/browser/views/window_view.cc index 573674edd..1e8d58a32 100644 --- a/libcef/browser/views/window_view.cc +++ b/libcef/browser/views/window_view.cc @@ -535,6 +535,12 @@ void CefWindowView::OnWidgetActivationChanged(views::Widget* widget, void CefWindowView::OnWidgetBoundsChanged(views::Widget* widget, const gfx::Rect& new_bounds) { MoveOverlaysIfNecessary(); + + if (cef_delegate()) { + cef_delegate()->OnWindowBoundsChanged( + GetCefWindow(), {new_bounds.x(), new_bounds.y(), new_bounds.width(), + new_bounds.height()}); + } } display::Display CefWindowView::GetDisplay() const { diff --git a/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc b/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc index be7c4a61e..b998529c0 100644 --- a/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc +++ b/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=2ecdf3e890e54e962286430f350c5b49249a9a9e$ +// $hash=9657432e6ca2ba72aeeb1ced5c8cf5ee71cf7221$ // #include "libcef_dll/cpptoc/views/window_delegate_cpptoc.h" @@ -41,6 +41,26 @@ window_delegate_on_window_created(struct _cef_window_delegate_t* self, CefWindowCToCpp::Wrap(window)); } +void CEF_CALLBACK +window_delegate_on_window_closing(struct _cef_window_delegate_t* self, + cef_window_t* window) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: window; type: refptr_diff + DCHECK(window); + if (!window) + return; + + // Execute + CefWindowDelegateCppToC::Get(self)->OnWindowClosing( + CefWindowCToCpp::Wrap(window)); +} + void CEF_CALLBACK window_delegate_on_window_destroyed(struct _cef_window_delegate_t* self, cef_window_t* window) { @@ -82,6 +102,34 @@ void CEF_CALLBACK window_delegate_on_window_activation_changed( CefWindowCToCpp::Wrap(window), active ? true : false); } +void CEF_CALLBACK +window_delegate_on_window_bounds_changed(struct _cef_window_delegate_t* self, + cef_window_t* window, + const cef_rect_t* new_bounds) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) + return; + // Verify param: window; type: refptr_diff + DCHECK(window); + if (!window) + return; + // Verify param: new_bounds; type: simple_byref_const + DCHECK(new_bounds); + if (!new_bounds) + return; + + // Translate param: new_bounds; type: simple_byref_const + CefRect new_boundsVal = new_bounds ? *new_bounds : CefRect(); + + // Execute + CefWindowDelegateCppToC::Get(self)->OnWindowBoundsChanged( + CefWindowCToCpp::Wrap(window), new_boundsVal); +} + cef_window_t* CEF_CALLBACK window_delegate_get_parent_window(struct _cef_window_delegate_t* self, cef_window_t* window, @@ -588,9 +636,12 @@ void CEF_CALLBACK window_delegate_on_blur(struct _cef_view_delegate_t* self, CefWindowDelegateCppToC::CefWindowDelegateCppToC() { GetStruct()->on_window_created = window_delegate_on_window_created; + GetStruct()->on_window_closing = window_delegate_on_window_closing; GetStruct()->on_window_destroyed = window_delegate_on_window_destroyed; GetStruct()->on_window_activation_changed = window_delegate_on_window_activation_changed; + GetStruct()->on_window_bounds_changed = + window_delegate_on_window_bounds_changed; GetStruct()->get_parent_window = window_delegate_get_parent_window; GetStruct()->get_initial_bounds = window_delegate_get_initial_bounds; GetStruct()->get_initial_show_state = window_delegate_get_initial_show_state; diff --git a/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc b/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc index d45ffc9ce..3c39a34d5 100644 --- a/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc +++ b/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=423d12cda6148d99e5afb0cc4aa11818e0740e1e$ +// $hash=07d3613588fb17a2d07d817a4b1390db3b6cffe7$ // #include "libcef_dll/ctocpp/views/window_delegate_ctocpp.h" @@ -38,6 +38,25 @@ void CefWindowDelegateCToCpp::OnWindowCreated(CefRefPtr window) { _struct->on_window_created(_struct, CefWindowCppToC::Wrap(window)); } +NO_SANITIZE("cfi-icall") +void CefWindowDelegateCToCpp::OnWindowClosing(CefRefPtr window) { + shutdown_checker::AssertNotShutdown(); + + cef_window_delegate_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_window_closing)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: window; type: refptr_diff + DCHECK(window.get()); + if (!window.get()) + return; + + // Execute + _struct->on_window_closing(_struct, CefWindowCppToC::Wrap(window)); +} + NO_SANITIZE("cfi-icall") void CefWindowDelegateCToCpp::OnWindowDestroyed(CefRefPtr window) { shutdown_checker::AssertNotShutdown(); @@ -79,6 +98,27 @@ void CefWindowDelegateCToCpp::OnWindowActivationChanged( active); } +NO_SANITIZE("cfi-icall") +void CefWindowDelegateCToCpp::OnWindowBoundsChanged(CefRefPtr window, + const CefRect& new_bounds) { + shutdown_checker::AssertNotShutdown(); + + cef_window_delegate_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, on_window_bounds_changed)) + return; + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: window; type: refptr_diff + DCHECK(window.get()); + if (!window.get()) + return; + + // Execute + _struct->on_window_bounds_changed(_struct, CefWindowCppToC::Wrap(window), + &new_bounds); +} + NO_SANITIZE("cfi-icall") CefRefPtr CefWindowDelegateCToCpp::GetParentWindow( CefRefPtr window, diff --git a/libcef_dll/ctocpp/views/window_delegate_ctocpp.h b/libcef_dll/ctocpp/views/window_delegate_ctocpp.h index 60f6945ad..b8e3d4da3 100644 --- a/libcef_dll/ctocpp/views/window_delegate_ctocpp.h +++ b/libcef_dll/ctocpp/views/window_delegate_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=4041b496f1e9ed673f99211be26b8fa98967fece$ +// $hash=a7d0db45a4032026a7235d653b3cfed9929ad519$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_VIEWS_WINDOW_DELEGATE_CTOCPP_H_ @@ -38,9 +38,12 @@ class CefWindowDelegateCToCpp // CefWindowDelegate methods. void OnWindowCreated(CefRefPtr window) override; + void OnWindowClosing(CefRefPtr window) override; void OnWindowDestroyed(CefRefPtr window) override; void OnWindowActivationChanged(CefRefPtr window, bool active) override; + void OnWindowBoundsChanged(CefRefPtr window, + const CefRect& new_bounds) override; CefRefPtr GetParentWindow(CefRefPtr window, bool* is_menu, bool* can_activate_menu) override; diff --git a/tests/cefclient/browser/client_browser.cc b/tests/cefclient/browser/client_browser.cc index b2dd05748..0ef19532d 100644 --- a/tests/cefclient/browser/client_browser.cc +++ b/tests/cefclient/browser/client_browser.cc @@ -8,6 +8,7 @@ #include "include/cef_command_line.h" #include "include/cef_crash_util.h" #include "include/cef_file_util.h" +#include "tests/cefclient/browser/client_prefs.h" #include "tests/shared/common/client_switches.h" namespace client { @@ -19,6 +20,16 @@ class ClientBrowserDelegate : public ClientAppBrowser::Delegate { public: ClientBrowserDelegate() {} + void OnRegisterCustomPreferences( + CefRefPtr app, + cef_preferences_type_t type, + CefRawPtr registrar) override { + if (type == CEF_PREFERENCES_TYPE_GLOBAL) { + // Register global preferences with default values. + prefs::RegisterGlobalPreferences(registrar); + } + } + void OnContextInitialized(CefRefPtr app) override { if (CefCrashReportingEnabled()) { // Set some crash keys for testing purposes. Keys must be defined in the diff --git a/tests/cefclient/browser/client_prefs.cc b/tests/cefclient/browser/client_prefs.cc new file mode 100644 index 000000000..2c229205c --- /dev/null +++ b/tests/cefclient/browser/client_prefs.cc @@ -0,0 +1,202 @@ +// Copyright (c) 2022 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#include "tests/cefclient/browser/client_prefs.h" + +#include + +#include "include/base/cef_logging.h" +#include "include/cef_command_line.h" +#include "include/cef_preference.h" +#include "include/cef_values.h" +#include "include/views/cef_display.h" +#include "include/wrapper/cef_helpers.h" +#include "tests/shared/common/client_switches.h" +#include "tests/shared/common/string_util.h" + +namespace client { +namespace prefs { + +namespace { + +constexpr char kPrefWindowRestore[] = "cefclient.window_restore"; + +constexpr char kWindowRestoreStateKey[] = "state"; +constexpr char kWindowRestoreBoundsKey[] = "bounds"; +constexpr char kWindowRestoreBoundsKey_X[] = "x"; +constexpr char kWindowRestoreBoundsKey_Y[] = "y"; +constexpr char kWindowRestoreBoundsKey_W[] = "w"; +constexpr char kWindowRestoreBoundsKey_H[] = "h"; + +static struct { + const char* str; + cef_show_state_t state; +} const kWindowRestoreStateValueMap[] = { + {"normal", CEF_SHOW_STATE_NORMAL}, + {"minimized", CEF_SHOW_STATE_MINIMIZED}, + {"maximized", CEF_SHOW_STATE_MAXIMIZED}, + {"fullscreen", CEF_SHOW_STATE_FULLSCREEN}, +}; + +std::optional ShowStateFromString(const std::string& str) { + const auto strLower = AsciiStrToLower(str); + for (size_t i = 0; i < std::size(kWindowRestoreStateValueMap); ++i) { + if (strLower == kWindowRestoreStateValueMap[i].str) { + return kWindowRestoreStateValueMap[i].state; + } + } + return std::nullopt; +} + +const char* ShowStateToString(cef_show_state_t show_state) { + for (size_t i = 0; i < std::size(kWindowRestoreStateValueMap); ++i) { + if (show_state == kWindowRestoreStateValueMap[i].state) { + return kWindowRestoreStateValueMap[i].str; + } + } + NOTREACHED(); + return nullptr; +} + +// Create the CefValue representation that will be stored in preferences. +CefRefPtr CreateWindowRestoreValue( + cef_show_state_t show_state, + std::optional dip_bounds) { + auto dict = CefDictionaryValue::Create(); + + // Show state is required. + dict->SetString(kWindowRestoreStateKey, ShowStateToString(show_state)); + + // Bounds is optional. + if (dip_bounds) { + auto bounds_dict = CefDictionaryValue::Create(); + bounds_dict->SetInt(kWindowRestoreBoundsKey_X, dip_bounds->x); + bounds_dict->SetInt(kWindowRestoreBoundsKey_Y, dip_bounds->y); + bounds_dict->SetInt(kWindowRestoreBoundsKey_W, dip_bounds->width); + bounds_dict->SetInt(kWindowRestoreBoundsKey_H, dip_bounds->height); + dict->SetDictionary(kWindowRestoreBoundsKey, bounds_dict); + } + + auto value = CefValue::Create(); + value->SetDictionary(dict); + return value; +} + +CefRefPtr CreateDefaultWindowRestoreValue() { + return CreateWindowRestoreValue(CEF_SHOW_STATE_NORMAL, std::nullopt); +} + +// Parse the CefValue representation that was stored in preferences. +bool ParseWindowRestoreValue(CefRefPtr value, + cef_show_state_t& show_state, + std::optional& dip_bounds) { + if (!value || value->GetType() != VTYPE_DICTIONARY) { + return false; + } + + auto dict = value->GetDictionary(); + + bool has_state = false; + + // Show state is required. + if (dict->GetType(kWindowRestoreStateKey) == VTYPE_STRING) { + auto result = ShowStateFromString(dict->GetString(kWindowRestoreStateKey)); + if (result) { + show_state = *result; + has_state = true; + } + } + + // Bounds is optional. + if (has_state && dict->GetType(kWindowRestoreBoundsKey) == VTYPE_DICTIONARY) { + auto bounds_dict = dict->GetDictionary(kWindowRestoreBoundsKey); + if (bounds_dict->GetType(kWindowRestoreBoundsKey_X) == VTYPE_INT && + bounds_dict->GetType(kWindowRestoreBoundsKey_Y) == VTYPE_INT && + bounds_dict->GetType(kWindowRestoreBoundsKey_W) == VTYPE_INT && + bounds_dict->GetType(kWindowRestoreBoundsKey_H) == VTYPE_INT) { + dip_bounds = CefRect(bounds_dict->GetInt(kWindowRestoreBoundsKey_X), + bounds_dict->GetInt(kWindowRestoreBoundsKey_Y), + bounds_dict->GetInt(kWindowRestoreBoundsKey_W), + bounds_dict->GetInt(kWindowRestoreBoundsKey_H)); + } + } + + return has_state; +} + +// Keep the bounds inside the closest display work area. +CefRect ClampBoundsToDisplay(const CefRect& dip_bounds) { + auto display = CefDisplay::GetDisplayMatchingBounds( + dip_bounds, /*input_pixel_coords=*/false); + const auto work_area = display->GetWorkArea(); + + CefRect bounds = dip_bounds; + + if (bounds.width > work_area.width) + bounds.width = work_area.width; + if (bounds.height > work_area.height) + bounds.height = work_area.height; + + if (bounds.x < work_area.x) + bounds.x = work_area.x; + else if (bounds.x + bounds.width >= work_area.x + work_area.width) + bounds.x = work_area.x + work_area.width - bounds.width; + + if (bounds.y < work_area.y) + bounds.y = work_area.y; + else if (bounds.y + bounds.height >= work_area.y + work_area.height) + bounds.y = work_area.y + work_area.height - bounds.height; + + return bounds; +} + +} // namespace + +void RegisterGlobalPreferences(CefRawPtr registrar) { + registrar->AddPreference(kPrefWindowRestore, + CreateDefaultWindowRestoreValue()); +} + +bool LoadWindowRestorePreferences(cef_show_state_t& show_state, + std::optional& dip_bounds) { + CEF_REQUIRE_UI_THREAD(); + + // Check if show state was specified on the command-line. + auto command_line = CefCommandLine::GetGlobalCommandLine(); + if (command_line->HasSwitch(switches::kInitialShowState)) { + auto result = ShowStateFromString( + command_line->GetSwitchValue(switches::kInitialShowState)); + if (result) { + show_state = *result; + return true; + } + } + + // Check if show state was saved in global preferences. + auto manager = CefPreferenceManager::GetGlobalPreferenceManager(); + if (ParseWindowRestoreValue(manager->GetPreference(kPrefWindowRestore), + show_state, dip_bounds)) { + if (dip_bounds) { + // Keep the bounds inside the closest display. + dip_bounds = ClampBoundsToDisplay(*dip_bounds); + } + return true; + } + + return false; +} + +bool SaveWindowRestorePreferences(cef_show_state_t show_state, + std::optional dip_bounds) { + CEF_REQUIRE_UI_THREAD(); + auto manager = CefPreferenceManager::GetGlobalPreferenceManager(); + + CefString error; + return manager->SetPreference( + kPrefWindowRestore, CreateWindowRestoreValue(show_state, dip_bounds), + error); +} + +} // namespace prefs +} // namespace client diff --git a/tests/cefclient/browser/client_prefs.h b/tests/cefclient/browser/client_prefs.h new file mode 100644 index 000000000..577290d06 --- /dev/null +++ b/tests/cefclient/browser/client_prefs.h @@ -0,0 +1,29 @@ +// Copyright (c) 2022 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +#ifndef CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_PREFS_H_ +#define CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_PREFS_H_ +#pragma once + +#include + +#include "include/cef_base.h" +#include "include/cef_preference.h" + +namespace client { +namespace prefs { + +// Register global preferences with default values. +void RegisterGlobalPreferences(CefRawPtr registrar); + +// Load/save window restore info. +bool LoadWindowRestorePreferences(cef_show_state_t& show_state, + std::optional& dip_bounds); +bool SaveWindowRestorePreferences(cef_show_state_t show_state, + std::optional dip_bounds); + +} // namespace prefs +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_CLIENT_PREFS_H_ diff --git a/tests/cefclient/browser/root_window.cc b/tests/cefclient/browser/root_window.cc index 5b8468a04..1c2046c62 100644 --- a/tests/cefclient/browser/root_window.cc +++ b/tests/cefclient/browser/root_window.cc @@ -11,13 +11,7 @@ namespace client { -RootWindowConfig::RootWindowConfig() - : always_on_top(false), - with_controls(true), - with_osr(false), - with_extension(false), - initially_hidden(false), - url(MainContext::Get()->GetMainURL()) {} +RootWindowConfig::RootWindowConfig() : url(MainContext::Get()->GetMainURL()) {} RootWindow::RootWindow() : delegate_(nullptr) {} diff --git a/tests/cefclient/browser/root_window.h b/tests/cefclient/browser/root_window.h index 7341355d6..6dbd09a74 100644 --- a/tests/cefclient/browser/root_window.h +++ b/tests/cefclient/browser/root_window.h @@ -25,19 +25,19 @@ struct RootWindowConfig { RootWindowConfig(); // If true the window will always display above other windows. - bool always_on_top; + bool always_on_top = false; // If true the window will show controls. - bool with_controls; + bool with_controls = true; // If true the window will use off-screen rendering. - bool with_osr; + bool with_osr = false; // If true the window is hosting an extension app. - bool with_extension; + bool with_extension = false; // If true the window will be created initially hidden. - bool initially_hidden; + bool initially_hidden = false; // Requested window position. If |bounds| and |source_bounds| are empty the // default window size and location will be used. @@ -49,6 +49,10 @@ struct RootWindowConfig { // based windows when |initially_hidden| is also true. CefRect source_bounds; + // Requested window show state. This is currently only implemented for Views- + // based windows when |bounds| is non-empty and |initially_hidden| is false. + cef_show_state_t show_state = CEF_SHOW_STATE_NORMAL; + // Parent window. Only used for Views-based windows. CefRefPtr parent_window; diff --git a/tests/cefclient/browser/root_window_views.cc b/tests/cefclient/browser/root_window_views.cc index 0625814cd..0c51c6e41 100644 --- a/tests/cefclient/browser/root_window_views.cc +++ b/tests/cefclient/browser/root_window_views.cc @@ -11,6 +11,7 @@ #include "include/cef_app.h" #include "include/wrapper/cef_helpers.h" #include "tests/cefclient/browser/client_handler_std.h" +#include "tests/cefclient/browser/client_prefs.h" namespace client { @@ -37,19 +38,11 @@ void RootWindowViews::Init(RootWindow::Delegate* delegate, delegate_ = delegate; config_ = std::move(config); - if (config_->initially_hidden && !config_->source_bounds.IsEmpty()) { - // The window will be sized and positioned in OnAutoResize(). - initial_bounds_ = config_->source_bounds; - position_on_resize_ = true; - } else { - initial_bounds_ = config_->bounds; - } - CreateClientHandler(config_->url); initialized_ = true; // Continue initialization on the UI thread. - InitOnUIThread(settings, config_->url, delegate_->GetRequestContext(this)); + InitOnUIThread(settings, delegate_->GetRequestContext(this)); } void RootWindowViews::InitAsPopup(RootWindow::Delegate* delegate, @@ -68,7 +61,6 @@ void RootWindowViews::InitAsPopup(RootWindow::Delegate* delegate, delegate_ = delegate; config_ = std::make_unique(); config_->with_controls = with_controls; - is_popup_ = true; if (popupFeatures.xSet) initial_bounds_.x = popupFeatures.x; @@ -217,11 +209,16 @@ CefRefPtr RootWindowViews::GetParentWindow() { return config_->parent_window; } -CefRect RootWindowViews::GetWindowBounds() { +CefRect RootWindowViews::GetInitialBounds() { CEF_REQUIRE_UI_THREAD(); return initial_bounds_; } +cef_show_state_t RootWindowViews::GetInitialShowState() { + CEF_REQUIRE_UI_THREAD(); + return initial_show_state_; +} + scoped_refptr RootWindowViews::GetImageCache() { CEF_REQUIRE_UI_THREAD(); return image_cache_; @@ -239,6 +236,17 @@ void RootWindowViews::OnViewsWindowCreated(CefRefPtr window) { } } +void RootWindowViews::OnViewsWindowClosing(CefRefPtr window) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(window_); + + cef_show_state_t show_state; + std::optional dip_bounds; + if (window_->GetWindowRestorePreferences(show_state, dip_bounds)) { + prefs::SaveWindowRestorePreferences(show_state, dip_bounds); + } +} + void RootWindowViews::OnViewsWindowDestroyed(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); window_ = nullptr; @@ -472,15 +480,32 @@ void RootWindowViews::CreateClientHandler(const std::string& url) { void RootWindowViews::InitOnUIThread( const CefBrowserSettings& settings, - const std::string& startup_url, CefRefPtr request_context) { if (!CefCurrentlyOn(TID_UI)) { // Execute this method on the UI thread. CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::InitOnUIThread, this, - settings, startup_url, request_context)); + settings, request_context)); return; } + if (config_->initially_hidden && !config_->source_bounds.IsEmpty()) { + // The window will be sized and positioned in OnAutoResize(). + initial_bounds_ = config_->source_bounds; + position_on_resize_ = true; + } else if (!config_->bounds.IsEmpty()) { + // Initial state was specified via the config object. + initial_bounds_ = config_->bounds; + initial_show_state_ = config_->show_state; + } else { + // Initial state may be specified via the command-line or global + // preferences. + std::optional bounds; + if (prefs::LoadWindowRestorePreferences(initial_show_state_, bounds) && + bounds) { + initial_bounds_ = *bounds; + } + } + image_cache_ = delegate_->GetImageCache(); // Populate the default image cache. @@ -490,12 +515,11 @@ void RootWindowViews::InitOnUIThread( image_cache_->LoadImages( image_set, base::BindOnce(&RootWindowViews::CreateViewsWindow, this, - settings, startup_url, request_context)); + settings, request_context)); } void RootWindowViews::CreateViewsWindow( const CefBrowserSettings& settings, - const std::string& startup_url, CefRefPtr request_context, const ImageCache::ImageSet& images) { CEF_REQUIRE_UI_THREAD(); @@ -510,7 +534,7 @@ void RootWindowViews::CreateViewsWindow( #endif // Create the ViewsWindow. It will show itself after creation. - ViewsWindow::Create(this, client_handler_, startup_url, settings, + ViewsWindow::Create(this, client_handler_, config_->url, settings, request_context); } diff --git a/tests/cefclient/browser/root_window_views.h b/tests/cefclient/browser/root_window_views.h index 2cabd97f7..d282f6041 100644 --- a/tests/cefclient/browser/root_window_views.h +++ b/tests/cefclient/browser/root_window_views.h @@ -54,9 +54,11 @@ class RootWindowViews : public RootWindow, bool WithExtension() override; bool InitiallyHidden() override; CefRefPtr GetParentWindow() override; - CefRect GetWindowBounds() override; + CefRect GetInitialBounds() override; + cef_show_state_t GetInitialShowState() override; scoped_refptr GetImageCache() override; void OnViewsWindowCreated(CefRefPtr window) override; + void OnViewsWindowClosing(CefRefPtr window) override; void OnViewsWindowDestroyed(CefRefPtr window) override; void OnViewsWindowActivated(CefRefPtr window) override; ViewsWindow::Delegate* GetDelegateForPopup( @@ -90,10 +92,8 @@ class RootWindowViews : public RootWindow, void CreateClientHandler(const std::string& url); void InitOnUIThread(const CefBrowserSettings& settings, - const std::string& startup_url, CefRefPtr request_context); void CreateViewsWindow(const CefBrowserSettings& settings, - const std::string& startup_url, CefRefPtr request_context, const ImageCache::ImageSet& images); @@ -101,22 +101,20 @@ class RootWindowViews : public RootWindow, void NotifyViewsWindowActivated(); void NotifyDestroyedIfDone(); - // After initialization all members are only accessed on the main thread - // unless otherwise indicated. - // Members set during initialization. + // Members set during initialization. Safe to access from any thread. std::unique_ptr config_; - bool is_popup_ = false; - CefRect initial_bounds_; - bool position_on_resize_ = false; CefRefPtr client_handler_; - bool initialized_ = false; + + // Only accessed on the main thread. + CefRefPtr browser_; bool window_destroyed_ = false; bool browser_destroyed_ = false; - CefRefPtr browser_; - // Only accessed on the browser process UI thread. + CefRect initial_bounds_; + cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL; + bool position_on_resize_ = false; CefRefPtr window_; ExtensionSet pending_extensions_; scoped_refptr image_cache_; diff --git a/tests/cefclient/browser/views_window.cc b/tests/cefclient/browser/views_window.cc index 9936a15e1..8b36a6cfc 100644 --- a/tests/cefclient/browser/views_window.cc +++ b/tests/cefclient/browser/views_window.cc @@ -336,6 +336,32 @@ void ViewsWindow::OnExtensionsChanged(const ExtensionSet& extensions) { base::BindOnce(&ViewsWindow::OnExtensionIconsLoaded, this, extensions)); } +bool ViewsWindow::GetWindowRestorePreferences( + cef_show_state_t& show_state, + std::optional& dip_bounds) { + CEF_REQUIRE_UI_THREAD(); + if (!window_) + return false; + + show_state = CEF_SHOW_STATE_NORMAL; + if (window_->IsMinimized()) + show_state = CEF_SHOW_STATE_MINIMIZED; + else if (window_->IsMaximized()) + show_state = CEF_SHOW_STATE_MAXIMIZED; + else if (window_->IsFullscreen()) + show_state = CEF_SHOW_STATE_FULLSCREEN; + + if (show_state == CEF_SHOW_STATE_NORMAL) { + // Use the current visible bounds. + dip_bounds = window_->GetBoundsInScreen(); + } else { + // Use the last known visible bounds. + dip_bounds = last_visible_bounds_; + } + + return true; +} + CefRefPtr ViewsWindow::GetDelegateForPopupBrowserView( CefRefPtr browser_view, const CefBrowserSettings& settings, @@ -517,10 +543,14 @@ void ViewsWindow::OnWindowCreated(CefRefPtr window) { delegate_->OnViewsWindowCreated(this); - const CefRect bounds = delegate_->GetWindowBounds(); + const CefRect bounds = delegate_->GetInitialBounds(); if (bounds.IsEmpty()) { // Size the Window and center it at the default size. window_->CenterWindow(CefSize(kDefaultWidth, kDefaultHeight)); + } else { + // Remember the bounds from the previous application run in case the user + // does not move or resize the window during this application run. + last_visible_bounds_ = bounds; } // Set the background color for regions that are not obscured by other Views. @@ -539,7 +569,7 @@ void ViewsWindow::OnWindowCreated(CefRefPtr window) { AddAccelerators(); // Hide the top controls while in full-screen mode. - if (initial_show_state_ == CEF_SHOW_STATE_FULLSCREEN) { + if (delegate_->GetInitialShowState() == CEF_SHOW_STATE_FULLSCREEN) { ShowTopControls(false); } } else { @@ -558,6 +588,13 @@ void ViewsWindow::OnWindowCreated(CefRefPtr window) { } } +void ViewsWindow::OnWindowClosing(CefRefPtr window) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(window_); + + delegate_->OnViewsWindowClosing(this); +} + void ViewsWindow::OnWindowDestroyed(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(window_); @@ -577,12 +614,22 @@ void ViewsWindow::OnWindowDestroyed(CefRefPtr window) { void ViewsWindow::OnWindowActivationChanged(CefRefPtr window, bool active) { - if (!active) + if (!active) { return; + } delegate_->OnViewsWindowActivated(this); } +void ViewsWindow::OnWindowBoundsChanged(CefRefPtr window, + const CefRect& new_bounds) { + if (!window->IsMinimized() && !window->IsMaximized() && + !window->IsFullscreen()) { + // Track the last visible bounds for window restore purposes. + last_visible_bounds_ = new_bounds; + } +} + bool ViewsWindow::CanClose(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); @@ -610,7 +657,7 @@ CefRefPtr ViewsWindow::GetParentWindow(CefRefPtr window, CefRect ViewsWindow::GetInitialBounds(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); - const CefRect bounds = delegate_->GetWindowBounds(); + const CefRect bounds = delegate_->GetInitialBounds(); if (frameless_ && bounds.IsEmpty()) { // Need to provide a size for frameless windows that will be centered. return CefRect(0, 0, kDefaultWidth, kDefaultHeight); @@ -620,7 +667,7 @@ CefRect ViewsWindow::GetInitialBounds(CefRefPtr window) { cef_show_state_t ViewsWindow::GetInitialShowState(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); - return initial_show_state_; + return delegate_->GetInitialShowState(); } bool ViewsWindow::IsFrameless(CefRefPtr window) { @@ -807,16 +854,6 @@ ViewsWindow::ViewsWindow(Delegate* delegate, chrome_toolbar_type_ = CEF_CTT_NONE; } - const std::string& show_state = - command_line->GetSwitchValue(switches::kInitialShowState); - if (show_state == "minimized") { - initial_show_state_ = CEF_SHOW_STATE_MINIMIZED; - } else if (show_state == "maximized") { - initial_show_state_ = CEF_SHOW_STATE_MAXIMIZED; - } else if (show_state == "fullscreen") { - initial_show_state_ = CEF_SHOW_STATE_FULLSCREEN; - } - #if !defined(OS_MAC) // On Mac we don't show a top menu on the window. The options are available in // the app menu instead. diff --git a/tests/cefclient/browser/views_window.h b/tests/cefclient/browser/views_window.h index da7c866e3..b49dd43f7 100644 --- a/tests/cefclient/browser/views_window.h +++ b/tests/cefclient/browser/views_window.h @@ -6,6 +6,7 @@ #define CEF_TESTS_CEFCLIENT_BROWSER_VIEWS_WINDOW_H_ #pragma once +#include #include #include #include @@ -57,7 +58,10 @@ class ViewsWindow : public CefBrowserViewDelegate, virtual CefRefPtr GetParentWindow() = 0; // Return the initial window bounds. - virtual CefRect GetWindowBounds() = 0; + virtual CefRect GetInitialBounds() = 0; + + // Return the initial window show state. + virtual cef_show_state_t GetInitialShowState() = 0; // Returns the ImageCache. virtual scoped_refptr GetImageCache() = 0; @@ -65,6 +69,9 @@ class ViewsWindow : public CefBrowserViewDelegate, // Called when the ViewsWindow is created. virtual void OnViewsWindowCreated(CefRefPtr window) = 0; + // Called when the ViewsWindow is closing. + virtual void OnViewsWindowClosing(CefRefPtr window) = 0; + // Called when the ViewsWindow is destroyed. All references to |window| // should be released in this callback. virtual void OnViewsWindowDestroyed(CefRefPtr window) = 0; @@ -121,6 +128,9 @@ class ViewsWindow : public CefBrowserViewDelegate, void OnBeforeContextMenu(CefRefPtr model); void OnExtensionsChanged(const ExtensionSet& extensions); + bool GetWindowRestorePreferences(cef_show_state_t& show_state, + std::optional& dip_bounds); + // CefBrowserViewDelegate methods: CefRefPtr GetDelegateForPopupBrowserView( CefRefPtr browser_view, @@ -152,9 +162,12 @@ class ViewsWindow : public CefBrowserViewDelegate, // CefWindowDelegate methods: void OnWindowCreated(CefRefPtr window) override; + void OnWindowClosing(CefRefPtr window) override; void OnWindowDestroyed(CefRefPtr window) override; void OnWindowActivationChanged(CefRefPtr window, bool active) override; + void OnWindowBoundsChanged(CefRefPtr window, + const CefRect& new_bounds) override; CefRefPtr GetParentWindow(CefRefPtr window, bool* is_menu, bool* can_activate_menu) override; @@ -235,9 +248,9 @@ class ViewsWindow : public CefBrowserViewDelegate, CefRefPtr location_bar_; bool menu_has_focus_; int last_focused_view_; + std::optional last_visible_bounds_; CefSize minimum_window_size_; - cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL; CefRefPtr overlay_controls_;