cefclient: win: Support window state restore (see issue #3359)

The cefclient sample app on Windows will persist window state across application
restart if run with cache_path and persist_user_references enabled.

To test:
1. Run `cefclient --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.
This commit is contained in:
Marshall Greenblatt
2022-11-07 15:21:44 -05:00
parent 882bc19fdd
commit e2a9236106
13 changed files with 310 additions and 94 deletions

View File

@@ -49,8 +49,8 @@ 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.
// Requested window show state. Only used 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.

View File

@@ -6,11 +6,15 @@
#include <shellscalingapi.h>
#include <optional>
#include "include/base/cef_build.h"
#include "include/base/cef_callback.h"
#include "include/cef_app.h"
#include "include/views/cef_display.h"
#include "tests/cefclient/browser/browser_window_osr_win.h"
#include "tests/cefclient/browser/browser_window_std_win.h"
#include "tests/cefclient/browser/client_prefs.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/cefclient/browser/temp_window.h"
@@ -101,35 +105,7 @@ int GetURLBarHeight(HWND hwnd) {
} // namespace
RootWindowWin::RootWindowWin()
: with_controls_(false),
always_on_top_(false),
with_osr_(false),
with_extension_(false),
is_popup_(false),
start_rect_(),
initialized_(false),
hwnd_(nullptr),
draggable_region_(nullptr),
font_(nullptr),
font_height_(0),
back_hwnd_(nullptr),
forward_hwnd_(nullptr),
reload_hwnd_(nullptr),
stop_hwnd_(nullptr),
edit_hwnd_(nullptr),
edit_wndproc_old_(nullptr),
find_hwnd_(nullptr),
find_message_id_(0),
find_wndproc_old_(nullptr),
find_state_(),
find_next_(false),
find_match_case_last_(false),
window_destroyed_(false),
browser_destroyed_(false),
called_enable_non_client_dpi_scaling_(false) {
find_buff_[0] = 0;
RootWindowWin::RootWindowWin() {
// Create a HRGN representing the draggable window area.
draggable_region_ = ::CreateRectRgn(0, 0, 0, 0);
}
@@ -157,22 +133,47 @@ void RootWindowWin::Init(RootWindow::Delegate* delegate,
with_osr_ = config->with_osr;
with_extension_ = config->with_extension;
start_rect_.left = config->bounds.x;
start_rect_.top = config->bounds.y;
start_rect_.right = config->bounds.x + config->bounds.width;
start_rect_.bottom = config->bounds.y + config->bounds.height;
CreateBrowserWindow(config->url);
if (CefCurrentlyOn(TID_UI)) {
ContinueInitOnUIThread(std::move(config), settings);
} else {
CefPostTask(TID_UI, base::BindOnce(&RootWindowWin::ContinueInitOnUIThread,
this, std::move(config), settings));
}
}
void RootWindowWin::ContinueInitOnUIThread(
std::unique_ptr<RootWindowConfig> config,
const CefBrowserSettings& settings) {
CEF_REQUIRE_UI_THREAD();
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<CefRect> bounds;
if (prefs::LoadWindowRestorePreferences(initial_show_state_, bounds) &&
bounds) {
initial_bounds_ = CefDisplay::ConvertScreenRectToPixels(*bounds);
}
}
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowWin::ContinueInitOnMainThread,
this, std::move(config), settings));
}
void RootWindowWin::ContinueInitOnMainThread(
std::unique_ptr<RootWindowConfig> config,
const CefBrowserSettings& settings) {
REQUIRE_MAIN_THREAD();
initialized_ = true;
// Create the native root window on the main thread.
if (CURRENTLY_ON_MAIN_THREAD()) {
CreateRootWindow(settings, config->initially_hidden);
} else {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowWin::CreateRootWindow, this,
settings, config->initially_hidden));
}
CreateRootWindow(settings, config->initially_hidden);
}
void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate,
@@ -193,13 +194,13 @@ void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate,
is_popup_ = true;
if (popupFeatures.xSet)
start_rect_.left = popupFeatures.x;
initial_bounds_.x = popupFeatures.x;
if (popupFeatures.ySet)
start_rect_.top = popupFeatures.y;
initial_bounds_.y = popupFeatures.y;
if (popupFeatures.widthSet)
start_rect_.right = start_rect_.left + popupFeatures.width;
initial_bounds_.width = popupFeatures.width;
if (popupFeatures.heightSet)
start_rect_.bottom = start_rect_.top + popupFeatures.height;
initial_bounds_.height = popupFeatures.height;
CreateBrowserWindow(std::string());
@@ -234,7 +235,8 @@ void RootWindowWin::Show(ShowMode mode) {
}
ShowWindow(hwnd_, nCmdShow);
UpdateWindow(hwnd_);
if (mode != ShowMinimized)
UpdateWindow(hwnd_);
}
void RootWindowWin::Hide() {
@@ -343,26 +345,37 @@ void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings,
CefCommandLine::GetGlobalCommandLine();
const bool no_activate = command_line->HasSwitch(switches::kNoActivate);
const DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN;
DWORD dwExStyle = always_on_top_ ? WS_EX_TOPMOST : 0;
if (no_activate) {
// Don't activate the browser window on creation.
dwExStyle |= WS_EX_NOACTIVATE;
}
if (initial_show_state_ == CEF_SHOW_STATE_MAXIMIZED) {
dwStyle |= WS_MAXIMIZE;
} else if (initial_show_state_ == CEF_SHOW_STATE_MINIMIZED) {
dwStyle |= WS_MINIMIZE;
}
int x, y, width, height;
if (::IsRectEmpty(&start_rect_)) {
if (initial_bounds_.IsEmpty()) {
// Use the default window position/size.
x = y = width = height = CW_USEDEFAULT;
} else {
// Adjust the window size to account for window frame and controls.
RECT window_rect = start_rect_;
::AdjustWindowRectEx(&window_rect, dwStyle, with_controls_, dwExStyle);
x = initial_bounds_.x;
y = initial_bounds_.y;
width = initial_bounds_.width;
height = initial_bounds_.height;
x = start_rect_.left;
y = start_rect_.top;
width = window_rect.right - window_rect.left;
height = window_rect.bottom - window_rect.top;
if (is_popup_) {
// Adjust the window size to account for window frame and controls. Keep
// the origin unchanged.
RECT window_rect = {x, y, x + width, y + height};
::AdjustWindowRectEx(&window_rect, dwStyle, with_controls_, dwExStyle);
width = window_rect.right - window_rect.left;
height = window_rect.bottom - window_rect.top;
}
}
browser_settings_ = settings;
@@ -385,8 +398,17 @@ void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings,
}
if (!initially_hidden) {
ShowMode mode = ShowNormal;
if (no_activate) {
mode = ShowNoActivate;
} else if (initial_show_state_ == CEF_SHOW_STATE_MAXIMIZED) {
mode = ShowMaximized;
} else if (initial_show_state_ == CEF_SHOW_STATE_MINIMIZED) {
mode = ShowMinimized;
}
// Show this window.
Show(no_activate ? ShowNoActivate : ShowNormal);
Show(mode);
}
}
@@ -983,6 +1005,18 @@ bool RootWindowWin::OnClose() {
}
}
// Retrieve current window placement information.
WINDOWPLACEMENT placement;
::GetWindowPlacement(hwnd_, &placement);
if (CefCurrentlyOn(TID_UI)) {
SaveWindowRestoreOnUIThread(placement);
} else {
CefPostTask(
TID_UI,
base::BindOnce(&RootWindowWin::SaveWindowRestoreOnUIThread, placement));
}
// Allow the close.
return false;
}
@@ -1219,4 +1253,25 @@ void RootWindowWin::NotifyDestroyedIfDone() {
delegate_->OnRootWindowDestroyed(this);
}
// static
void RootWindowWin::SaveWindowRestoreOnUIThread(
const WINDOWPLACEMENT& placement) {
CEF_REQUIRE_UI_THREAD();
cef_show_state_t show_state = CEF_SHOW_STATE_NORMAL;
if (placement.showCmd == SW_SHOWMINIMIZED) {
show_state = CEF_SHOW_STATE_MINIMIZED;
} else if (placement.showCmd == SW_SHOWMAXIMIZED) {
show_state = CEF_SHOW_STATE_MAXIMIZED;
}
// Coordinates when the window is in the restored position.
const auto rect = placement.rcNormalPosition;
CefRect pixel_bounds(rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top);
const auto dip_bounds = CefDisplay::ConvertScreenRectFromPixels(pixel_bounds);
prefs::SaveWindowRestorePreferences(show_state, dip_bounds);
}
} // namespace client

View File

@@ -50,6 +50,11 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
bool WithExtension() const override;
private:
void ContinueInitOnUIThread(std::unique_ptr<RootWindowConfig> config,
const CefBrowserSettings& settings);
void ContinueInitOnMainThread(std::unique_ptr<RootWindowConfig> config,
const CefBrowserSettings& settings);
void CreateBrowserWindow(const std::string& startup_url);
void CreateRootWindow(const CefBrowserSettings& settings,
bool initially_hidden);
@@ -109,54 +114,57 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
void NotifyDestroyedIfDone();
static void SaveWindowRestoreOnUIThread(const WINDOWPLACEMENT& placement);
// After initialization all members are only accessed on the main thread.
// Members set during initialization.
bool with_controls_;
bool always_on_top_;
bool with_osr_;
bool with_extension_;
bool is_popup_;
RECT start_rect_;
bool with_controls_ = false;
bool always_on_top_ = false;
bool with_osr_ = false;
bool with_extension_ = false;
bool is_popup_ = false;
CefRect initial_bounds_;
cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL;
std::unique_ptr<BrowserWindow> browser_window_;
CefBrowserSettings browser_settings_;
bool initialized_;
bool initialized_ = false;
// Main window.
HWND hwnd_;
HWND hwnd_ = nullptr;
// Draggable region.
HRGN draggable_region_;
HRGN draggable_region_ = nullptr;
// Font for buttons and text fields.
HFONT font_;
int font_height_;
HFONT font_ = nullptr;
int font_height_ = 0;
// Buttons.
HWND back_hwnd_;
HWND forward_hwnd_;
HWND reload_hwnd_;
HWND stop_hwnd_;
HWND back_hwnd_ = nullptr;
HWND forward_hwnd_ = nullptr;
HWND reload_hwnd_ = nullptr;
HWND stop_hwnd_ = nullptr;
// URL text field.
HWND edit_hwnd_;
WNDPROC edit_wndproc_old_;
HWND edit_hwnd_ = nullptr;
WNDPROC edit_wndproc_old_ = nullptr;
// Find dialog.
HWND find_hwnd_;
UINT find_message_id_;
WNDPROC find_wndproc_old_;
HWND find_hwnd_ = nullptr;
UINT find_message_id_ = 0;
WNDPROC find_wndproc_old_ = nullptr;
// Find dialog state.
FINDREPLACE find_state_;
WCHAR find_buff_[80];
FINDREPLACE find_state_ = {0};
WCHAR find_buff_[80] = {0};
std::wstring find_what_last_;
bool find_next_;
bool find_match_case_last_;
bool find_next_ = false;
bool find_match_case_last_ = false;
bool window_destroyed_;
bool browser_destroyed_;
bool window_destroyed_ = false;
bool browser_destroyed_ = false;
bool called_enable_non_client_dpi_scaling_;
bool called_enable_non_client_dpi_scaling_ = false;
DISALLOW_COPY_AND_ASSIGN(RootWindowWin);
};

View File

@@ -54,7 +54,8 @@ void SetPosImpl(CefRefPtr<CefBrowser> browser,
CefRect window_rect(x, y, width, height);
WindowTestRunner::ModifyBounds(display_rect, window_rect);
if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_MAXIMIZE) {
if (placement.showCmd == SW_SHOWMINIMIZED ||
placement.showCmd == SW_SHOWMAXIMIZED) {
// The window is currently minimized or maximized. Restore it to the desired
// position.
placement.rcNormalPosition.left = window_rect.x;