1315 lines
39 KiB
C++
1315 lines
39 KiB
C++
// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights
|
|
// reserved. Use of this source code is governed by a BSD-style license that
|
|
// can be found in the LICENSE file.
|
|
|
|
#include "tests/cefclient/browser/root_window_win.h"
|
|
|
|
#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"
|
|
#include "tests/cefclient/browser/window_test_runner_win.h"
|
|
#include "tests/shared/browser/geometry_util.h"
|
|
#include "tests/shared/browser/main_message_loop.h"
|
|
#include "tests/shared/browser/util_win.h"
|
|
#include "tests/shared/common/client_switches.h"
|
|
|
|
#define MAX_URL_LENGTH 255
|
|
#define BUTTON_WIDTH 72
|
|
#define URLBAR_HEIGHT 24
|
|
|
|
namespace client {
|
|
|
|
namespace {
|
|
|
|
// Message handler for the About box.
|
|
INT_PTR CALLBACK AboutWndProc(HWND hDlg,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
switch (message) {
|
|
case WM_INITDIALOG:
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
|
|
EndDialog(hDlg, LOWORD(wParam));
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Returns true if the process is per monitor DPI aware.
|
|
bool IsProcessPerMonitorDpiAware() {
|
|
enum class PerMonitorDpiAware {
|
|
UNKNOWN = 0,
|
|
PER_MONITOR_DPI_UNAWARE,
|
|
PER_MONITOR_DPI_AWARE,
|
|
};
|
|
static PerMonitorDpiAware per_monitor_dpi_aware = PerMonitorDpiAware::UNKNOWN;
|
|
if (per_monitor_dpi_aware == PerMonitorDpiAware::UNKNOWN) {
|
|
per_monitor_dpi_aware = PerMonitorDpiAware::PER_MONITOR_DPI_UNAWARE;
|
|
HMODULE shcore_dll = ::LoadLibrary(L"shcore.dll");
|
|
if (shcore_dll) {
|
|
typedef HRESULT(WINAPI * GetProcessDpiAwarenessPtr)(
|
|
HANDLE, PROCESS_DPI_AWARENESS*);
|
|
GetProcessDpiAwarenessPtr func_ptr =
|
|
reinterpret_cast<GetProcessDpiAwarenessPtr>(
|
|
::GetProcAddress(shcore_dll, "GetProcessDpiAwareness"));
|
|
if (func_ptr) {
|
|
PROCESS_DPI_AWARENESS awareness;
|
|
if (SUCCEEDED(func_ptr(nullptr, &awareness)) &&
|
|
awareness == PROCESS_PER_MONITOR_DPI_AWARE) {
|
|
per_monitor_dpi_aware = PerMonitorDpiAware::PER_MONITOR_DPI_AWARE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return per_monitor_dpi_aware == PerMonitorDpiAware::PER_MONITOR_DPI_AWARE;
|
|
}
|
|
|
|
// DPI value for 1x scale factor.
|
|
#define DPI_1X 96.0f
|
|
|
|
float GetWindowScaleFactor(HWND hwnd) {
|
|
if (hwnd && IsProcessPerMonitorDpiAware()) {
|
|
typedef UINT(WINAPI * GetDpiForWindowPtr)(HWND);
|
|
static GetDpiForWindowPtr func_ptr = reinterpret_cast<GetDpiForWindowPtr>(
|
|
GetProcAddress(GetModuleHandle(L"user32.dll"), "GetDpiForWindow"));
|
|
if (func_ptr) {
|
|
return static_cast<float>(func_ptr(hwnd)) / DPI_1X;
|
|
}
|
|
}
|
|
|
|
return client::GetDeviceScaleFactor();
|
|
}
|
|
|
|
int GetButtonWidth(HWND hwnd) {
|
|
return LogicalToDevice(BUTTON_WIDTH, GetWindowScaleFactor(hwnd));
|
|
}
|
|
|
|
int GetURLBarHeight(HWND hwnd) {
|
|
return LogicalToDevice(URLBAR_HEIGHT, GetWindowScaleFactor(hwnd));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
RootWindowWin::RootWindowWin() {
|
|
// Create a HRGN representing the draggable window area.
|
|
draggable_region_ = ::CreateRectRgn(0, 0, 0, 0);
|
|
}
|
|
|
|
RootWindowWin::~RootWindowWin() {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
::DeleteObject(draggable_region_);
|
|
::DeleteObject(font_);
|
|
|
|
// The window and browser should already have been destroyed.
|
|
DCHECK(window_destroyed_);
|
|
DCHECK(browser_destroyed_);
|
|
}
|
|
|
|
void RootWindowWin::Init(RootWindow::Delegate* delegate,
|
|
std::unique_ptr<RootWindowConfig> config,
|
|
const CefBrowserSettings& settings) {
|
|
DCHECK(delegate);
|
|
DCHECK(!initialized_);
|
|
|
|
delegate_ = delegate;
|
|
with_controls_ = config->with_controls;
|
|
always_on_top_ = config->always_on_top;
|
|
with_osr_ = config->with_osr;
|
|
with_extension_ = config->with_extension;
|
|
|
|
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;
|
|
|
|
CreateRootWindow(settings, config->initially_hidden);
|
|
}
|
|
|
|
void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate,
|
|
bool with_controls,
|
|
bool with_osr,
|
|
const CefPopupFeatures& popupFeatures,
|
|
CefWindowInfo& windowInfo,
|
|
CefRefPtr<CefClient>& client,
|
|
CefBrowserSettings& settings) {
|
|
CEF_REQUIRE_UI_THREAD();
|
|
|
|
DCHECK(delegate);
|
|
DCHECK(!initialized_);
|
|
|
|
delegate_ = delegate;
|
|
with_controls_ = with_controls;
|
|
with_osr_ = with_osr;
|
|
is_popup_ = true;
|
|
|
|
if (popupFeatures.xSet) {
|
|
initial_bounds_.x = popupFeatures.x;
|
|
}
|
|
if (popupFeatures.ySet) {
|
|
initial_bounds_.y = popupFeatures.y;
|
|
}
|
|
if (popupFeatures.widthSet) {
|
|
initial_bounds_.width = popupFeatures.width;
|
|
}
|
|
if (popupFeatures.heightSet) {
|
|
initial_bounds_.height = popupFeatures.height;
|
|
}
|
|
|
|
CreateBrowserWindow(std::string());
|
|
|
|
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
|
|
// will be re-parented to it at that time.
|
|
browser_window_->GetPopupConfig(TempWindow::GetWindowHandle(), windowInfo,
|
|
client, settings);
|
|
}
|
|
|
|
void RootWindowWin::Show(ShowMode mode) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (!hwnd_) {
|
|
return;
|
|
}
|
|
|
|
int nCmdShow = SW_SHOWNORMAL;
|
|
switch (mode) {
|
|
case ShowMinimized:
|
|
nCmdShow = SW_SHOWMINIMIZED;
|
|
break;
|
|
case ShowMaximized:
|
|
nCmdShow = SW_SHOWMAXIMIZED;
|
|
break;
|
|
case ShowNoActivate:
|
|
nCmdShow = SW_SHOWNOACTIVATE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ShowWindow(hwnd_, nCmdShow);
|
|
if (mode != ShowMinimized) {
|
|
UpdateWindow(hwnd_);
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::Hide() {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (hwnd_) {
|
|
ShowWindow(hwnd_, SW_HIDE);
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::SetBounds(int x, int y, size_t width, size_t height) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (hwnd_) {
|
|
SetWindowPos(hwnd_, nullptr, x, y, static_cast<int>(width),
|
|
static_cast<int>(height), SWP_NOZORDER);
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::Close(bool force) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (hwnd_) {
|
|
if (force) {
|
|
DestroyWindow(hwnd_);
|
|
} else {
|
|
PostMessage(hwnd_, WM_CLOSE, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::SetDeviceScaleFactor(float device_scale_factor) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (browser_window_ && with_osr_) {
|
|
browser_window_->SetDeviceScaleFactor(device_scale_factor);
|
|
}
|
|
}
|
|
|
|
float RootWindowWin::GetDeviceScaleFactor() const {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (browser_window_ && with_osr_) {
|
|
return browser_window_->GetDeviceScaleFactor();
|
|
}
|
|
|
|
NOTREACHED();
|
|
return 0.0f;
|
|
}
|
|
|
|
CefRefPtr<CefBrowser> RootWindowWin::GetBrowser() const {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (browser_window_) {
|
|
return browser_window_->GetBrowser();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ClientWindowHandle RootWindowWin::GetWindowHandle() const {
|
|
REQUIRE_MAIN_THREAD();
|
|
return hwnd_;
|
|
}
|
|
|
|
bool RootWindowWin::WithWindowlessRendering() const {
|
|
REQUIRE_MAIN_THREAD();
|
|
return with_osr_;
|
|
}
|
|
|
|
bool RootWindowWin::WithExtension() const {
|
|
REQUIRE_MAIN_THREAD();
|
|
return with_extension_;
|
|
}
|
|
|
|
void RootWindowWin::CreateBrowserWindow(const std::string& startup_url) {
|
|
if (with_osr_) {
|
|
OsrRendererSettings settings = {};
|
|
MainContext::Get()->PopulateOsrSettings(&settings);
|
|
browser_window_.reset(
|
|
new BrowserWindowOsrWin(this, with_controls_, startup_url, settings));
|
|
} else {
|
|
browser_window_.reset(
|
|
new BrowserWindowStdWin(this, with_controls_, startup_url));
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::CreateRootWindow(const CefBrowserSettings& settings,
|
|
bool initially_hidden) {
|
|
REQUIRE_MAIN_THREAD();
|
|
DCHECK(!hwnd_);
|
|
|
|
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
|
|
|
// Load strings from the resource file.
|
|
const std::wstring& window_title = GetResourceString(IDS_APP_TITLE);
|
|
const std::wstring& window_class = GetResourceString(IDC_CEFCLIENT);
|
|
|
|
const cef_color_t background_color = MainContext::Get()->GetBackgroundColor();
|
|
const HBRUSH background_brush = CreateSolidBrush(
|
|
RGB(CefColorGetR(background_color), CefColorGetG(background_color),
|
|
CefColorGetB(background_color)));
|
|
|
|
// Register the window class.
|
|
RegisterRootClass(hInstance, window_class, background_brush);
|
|
|
|
// Register the message used with the find dialog.
|
|
find_message_id_ = RegisterWindowMessage(FINDMSGSTRING);
|
|
CHECK(find_message_id_);
|
|
|
|
CefRefPtr<CefCommandLine> command_line =
|
|
CefCommandLine::GetGlobalCommandLine();
|
|
const bool no_activate = command_line->HasSwitch(switches::kNoActivate);
|
|
|
|
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 (initial_bounds_.IsEmpty()) {
|
|
// Use the default window position/size.
|
|
x = y = width = height = CW_USEDEFAULT;
|
|
} else {
|
|
x = initial_bounds_.x;
|
|
y = initial_bounds_.y;
|
|
width = initial_bounds_.width;
|
|
height = initial_bounds_.height;
|
|
|
|
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;
|
|
|
|
// Create the main window initially hidden.
|
|
CreateWindowEx(dwExStyle, window_class.c_str(), window_title.c_str(), dwStyle,
|
|
x, y, width, height, nullptr, nullptr, hInstance, this);
|
|
CHECK(hwnd_);
|
|
|
|
if (!called_enable_non_client_dpi_scaling_ && IsProcessPerMonitorDpiAware()) {
|
|
// This call gets Windows to scale the non-client area when WM_DPICHANGED
|
|
// is fired on Windows versions < 10.0.14393.0.
|
|
// Derived signature; not available in headers.
|
|
typedef LRESULT(WINAPI * EnableChildWindowDpiMessagePtr)(HWND, BOOL);
|
|
static EnableChildWindowDpiMessagePtr func_ptr =
|
|
reinterpret_cast<EnableChildWindowDpiMessagePtr>(GetProcAddress(
|
|
GetModuleHandle(L"user32.dll"), "EnableChildWindowDpiMessage"));
|
|
if (func_ptr) {
|
|
func_ptr(hwnd_, TRUE);
|
|
}
|
|
}
|
|
|
|
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(mode);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void RootWindowWin::RegisterRootClass(HINSTANCE hInstance,
|
|
const std::wstring& window_class,
|
|
HBRUSH background_brush) {
|
|
// Only register the class one time.
|
|
static bool class_registered = false;
|
|
if (class_registered) {
|
|
return;
|
|
}
|
|
class_registered = true;
|
|
|
|
WNDCLASSEX wcex;
|
|
|
|
wcex.cbSize = sizeof(WNDCLASSEX);
|
|
|
|
wcex.style = CS_HREDRAW | CS_VREDRAW;
|
|
wcex.lpfnWndProc = RootWndProc;
|
|
wcex.cbClsExtra = 0;
|
|
wcex.cbWndExtra = 0;
|
|
wcex.hInstance = hInstance;
|
|
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CEFCLIENT));
|
|
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wcex.hbrBackground = background_brush;
|
|
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CEFCLIENT);
|
|
wcex.lpszClassName = window_class.c_str();
|
|
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
|
|
|
|
RegisterClassEx(&wcex);
|
|
}
|
|
|
|
// static
|
|
LRESULT CALLBACK RootWindowWin::EditWndProc(HWND hWnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
RootWindowWin* self = GetUserDataPtr<RootWindowWin*>(hWnd);
|
|
DCHECK(self);
|
|
DCHECK(hWnd == self->edit_hwnd_);
|
|
|
|
switch (message) {
|
|
case WM_CHAR:
|
|
if (wParam == VK_RETURN) {
|
|
// When the user hits the enter key load the URL.
|
|
CefRefPtr<CefBrowser> browser = self->GetBrowser();
|
|
if (browser) {
|
|
wchar_t strPtr[MAX_URL_LENGTH + 1] = {0};
|
|
*((LPWORD)strPtr) = MAX_URL_LENGTH;
|
|
LRESULT strLen = SendMessage(hWnd, EM_GETLINE, 0, (LPARAM)strPtr);
|
|
if (strLen > 0) {
|
|
strPtr[strLen] = 0;
|
|
browser->GetMainFrame()->LoadURL(strPtr);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
case WM_NCDESTROY:
|
|
// Clear the reference to |self|.
|
|
SetUserDataPtr(hWnd, nullptr);
|
|
self->edit_hwnd_ = nullptr;
|
|
break;
|
|
}
|
|
|
|
return CallWindowProc(self->edit_wndproc_old_, hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
// static
|
|
LRESULT CALLBACK RootWindowWin::FindWndProc(HWND hWnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
RootWindowWin* self = GetUserDataPtr<RootWindowWin*>(hWnd);
|
|
DCHECK(self);
|
|
DCHECK(hWnd == self->find_hwnd_);
|
|
|
|
switch (message) {
|
|
case WM_ACTIVATE:
|
|
// Set this dialog as current when activated.
|
|
MainMessageLoop::Get()->SetCurrentModelessDialog(wParam == 0 ? nullptr
|
|
: hWnd);
|
|
return FALSE;
|
|
case WM_NCDESTROY:
|
|
// Clear the reference to |self|.
|
|
SetUserDataPtr(hWnd, nullptr);
|
|
self->find_hwnd_ = nullptr;
|
|
break;
|
|
}
|
|
|
|
return CallWindowProc(self->find_wndproc_old_, hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
// static
|
|
LRESULT CALLBACK RootWindowWin::RootWndProc(HWND hWnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
RootWindowWin* self = nullptr;
|
|
if (message != WM_NCCREATE) {
|
|
self = GetUserDataPtr<RootWindowWin*>(hWnd);
|
|
if (!self) {
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
DCHECK_EQ(hWnd, self->hwnd_);
|
|
}
|
|
|
|
if (self && message == self->find_message_id_) {
|
|
// Message targeting the find dialog.
|
|
LPFINDREPLACE lpfr = reinterpret_cast<LPFINDREPLACE>(lParam);
|
|
CHECK(lpfr == &self->find_state_);
|
|
self->OnFindEvent();
|
|
return 0;
|
|
}
|
|
|
|
// Callback for the main window
|
|
switch (message) {
|
|
case WM_COMMAND:
|
|
if (self->OnCommand(LOWORD(wParam))) {
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_GETOBJECT: {
|
|
// Only the lower 32 bits of lParam are valid when checking the object id
|
|
// because it sometimes gets sign-extended incorrectly (but not always).
|
|
DWORD obj_id = static_cast<DWORD>(static_cast<DWORD_PTR>(lParam));
|
|
|
|
// Accessibility readers will send an OBJID_CLIENT message.
|
|
if (static_cast<DWORD>(OBJID_CLIENT) == obj_id) {
|
|
if (self->GetBrowser() && self->GetBrowser()->GetHost()) {
|
|
self->GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case WM_PAINT:
|
|
self->OnPaint();
|
|
return 0;
|
|
|
|
case WM_ACTIVATE:
|
|
self->OnActivate(LOWORD(wParam) != WA_INACTIVE);
|
|
// Allow DefWindowProc to set keyboard focus.
|
|
break;
|
|
|
|
case WM_SETFOCUS:
|
|
self->OnFocus();
|
|
return 0;
|
|
|
|
case WM_ENABLE:
|
|
if (wParam == TRUE) {
|
|
// Give focus to the browser after EnableWindow enables this window
|
|
// (e.g. after a modal dialog is dismissed).
|
|
self->OnFocus();
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
self->OnSize(wParam == SIZE_MINIMIZED);
|
|
break;
|
|
|
|
case WM_MOVING:
|
|
case WM_MOVE:
|
|
self->OnMove();
|
|
return 0;
|
|
|
|
case WM_DPICHANGED:
|
|
self->OnDpiChanged(wParam, lParam);
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
if (self->OnEraseBkgnd()) {
|
|
break;
|
|
}
|
|
// Don't erase the background.
|
|
return 0;
|
|
|
|
case WM_ENTERMENULOOP:
|
|
if (!wParam) {
|
|
// Entering the menu loop for the application menu.
|
|
CefSetOSModalLoop(true);
|
|
}
|
|
break;
|
|
|
|
case WM_EXITMENULOOP:
|
|
if (!wParam) {
|
|
// Exiting the menu loop for the application menu.
|
|
CefSetOSModalLoop(false);
|
|
}
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
if (self->OnClose()) {
|
|
return 0; // Cancel the close.
|
|
}
|
|
break;
|
|
|
|
case WM_NCHITTEST: {
|
|
LRESULT hit = DefWindowProc(hWnd, message, wParam, lParam);
|
|
if (hit == HTCLIENT) {
|
|
POINTS points = MAKEPOINTS(lParam);
|
|
POINT point = {points.x, points.y};
|
|
::ScreenToClient(hWnd, &point);
|
|
if (::PtInRegion(self->draggable_region_, point.x, point.y)) {
|
|
// If cursor is inside a draggable region return HTCAPTION to allow
|
|
// dragging.
|
|
return HTCAPTION;
|
|
}
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
case WM_NCCREATE: {
|
|
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
|
|
self = reinterpret_cast<RootWindowWin*>(cs->lpCreateParams);
|
|
DCHECK(self);
|
|
// Associate |self| with the main window.
|
|
SetUserDataPtr(hWnd, self);
|
|
self->hwnd_ = hWnd;
|
|
|
|
self->OnNCCreate(cs);
|
|
} break;
|
|
|
|
case WM_CREATE:
|
|
self->OnCreate(reinterpret_cast<CREATESTRUCT*>(lParam));
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
// Clear the reference to |self|.
|
|
SetUserDataPtr(hWnd, nullptr);
|
|
self->hwnd_ = nullptr;
|
|
self->OnDestroyed();
|
|
break;
|
|
}
|
|
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
void RootWindowWin::OnPaint() {
|
|
PAINTSTRUCT ps;
|
|
BeginPaint(hwnd_, &ps);
|
|
EndPaint(hwnd_, &ps);
|
|
}
|
|
|
|
void RootWindowWin::OnFocus() {
|
|
// Selecting "Close window" from the task bar menu may send a focus
|
|
// notification even though the window is currently disabled (e.g. while a
|
|
// modal JS dialog is displayed).
|
|
if (browser_window_ && ::IsWindowEnabled(hwnd_)) {
|
|
browser_window_->SetFocus(true);
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnActivate(bool active) {
|
|
if (active) {
|
|
delegate_->OnRootWindowActivated(this);
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnSize(bool minimized) {
|
|
if (minimized) {
|
|
// Notify the browser window that it was hidden and do nothing further.
|
|
if (browser_window_) {
|
|
browser_window_->Hide();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (browser_window_) {
|
|
browser_window_->Show();
|
|
}
|
|
|
|
RECT rect;
|
|
GetClientRect(hwnd_, &rect);
|
|
|
|
if (with_controls_ && edit_hwnd_) {
|
|
const int button_width = GetButtonWidth(hwnd_);
|
|
const int urlbar_height = GetURLBarHeight(hwnd_);
|
|
const int font_height = LogicalToDevice(14, GetWindowScaleFactor(hwnd_));
|
|
|
|
if (font_height != font_height_) {
|
|
font_height_ = font_height;
|
|
if (font_) {
|
|
DeleteObject(font_);
|
|
}
|
|
|
|
// Create a scaled font.
|
|
font_ =
|
|
::CreateFont(-font_height, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE,
|
|
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
|
|
DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, L"Arial");
|
|
|
|
SendMessage(back_hwnd_, WM_SETFONT, reinterpret_cast<WPARAM>(font_),
|
|
TRUE);
|
|
SendMessage(forward_hwnd_, WM_SETFONT, reinterpret_cast<WPARAM>(font_),
|
|
TRUE);
|
|
SendMessage(reload_hwnd_, WM_SETFONT, reinterpret_cast<WPARAM>(font_),
|
|
TRUE);
|
|
SendMessage(stop_hwnd_, WM_SETFONT, reinterpret_cast<WPARAM>(font_),
|
|
TRUE);
|
|
SendMessage(edit_hwnd_, WM_SETFONT, reinterpret_cast<WPARAM>(font_),
|
|
TRUE);
|
|
}
|
|
|
|
// Resize the window and address bar to match the new frame size.
|
|
rect.top += urlbar_height;
|
|
|
|
int x_offset = rect.left;
|
|
|
|
// |browser_hwnd| may be nullptr if the browser has not yet been created.
|
|
HWND browser_hwnd = nullptr;
|
|
if (browser_window_) {
|
|
browser_hwnd = browser_window_->GetWindowHandle();
|
|
}
|
|
|
|
// Resize all controls.
|
|
HDWP hdwp = BeginDeferWindowPos(browser_hwnd ? 6 : 5);
|
|
hdwp = DeferWindowPos(hdwp, back_hwnd_, nullptr, x_offset, 0, button_width,
|
|
urlbar_height, SWP_NOZORDER);
|
|
x_offset += button_width;
|
|
hdwp = DeferWindowPos(hdwp, forward_hwnd_, nullptr, x_offset, 0,
|
|
button_width, urlbar_height, SWP_NOZORDER);
|
|
x_offset += button_width;
|
|
hdwp = DeferWindowPos(hdwp, reload_hwnd_, nullptr, x_offset, 0,
|
|
button_width, urlbar_height, SWP_NOZORDER);
|
|
x_offset += button_width;
|
|
hdwp = DeferWindowPos(hdwp, stop_hwnd_, nullptr, x_offset, 0, button_width,
|
|
urlbar_height, SWP_NOZORDER);
|
|
x_offset += button_width;
|
|
hdwp = DeferWindowPos(hdwp, edit_hwnd_, nullptr, x_offset, 0,
|
|
rect.right - x_offset, urlbar_height, SWP_NOZORDER);
|
|
|
|
if (browser_hwnd) {
|
|
hdwp = DeferWindowPos(hdwp, browser_hwnd, nullptr, rect.left, rect.top,
|
|
rect.right - rect.left, rect.bottom - rect.top,
|
|
SWP_NOZORDER);
|
|
}
|
|
|
|
[[maybe_unused]] BOOL result = EndDeferWindowPos(hdwp);
|
|
DCHECK(result);
|
|
} else if (browser_window_) {
|
|
// Size the browser window to the whole client area.
|
|
browser_window_->SetBounds(0, 0, rect.right, rect.bottom);
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnMove() {
|
|
// Notify the browser of move events so that popup windows are displayed
|
|
// in the correct location and dismissed when the window moves.
|
|
CefRefPtr<CefBrowser> browser = GetBrowser();
|
|
if (browser) {
|
|
browser->GetHost()->NotifyMoveOrResizeStarted();
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnDpiChanged(WPARAM wParam, LPARAM lParam) {
|
|
if (LOWORD(wParam) != HIWORD(wParam)) {
|
|
NOTIMPLEMENTED() << "Received non-square scaling factors";
|
|
return;
|
|
}
|
|
|
|
if (browser_window_ && with_osr_) {
|
|
// Scale factor for the new display.
|
|
const float display_scale_factor =
|
|
static_cast<float>(LOWORD(wParam)) / DPI_1X;
|
|
browser_window_->SetDeviceScaleFactor(display_scale_factor);
|
|
}
|
|
|
|
// Suggested size and position of the current window scaled for the new DPI.
|
|
const RECT* rect = reinterpret_cast<RECT*>(lParam);
|
|
SetBounds(rect->left, rect->top, rect->right - rect->left,
|
|
rect->bottom - rect->top);
|
|
}
|
|
|
|
bool RootWindowWin::OnEraseBkgnd() {
|
|
// Erase the background when the browser does not exist.
|
|
return (GetBrowser() == nullptr);
|
|
}
|
|
|
|
bool RootWindowWin::OnCommand(UINT id) {
|
|
if (id >= ID_TESTS_FIRST && id <= ID_TESTS_LAST) {
|
|
delegate_->OnTest(this, id);
|
|
return true;
|
|
}
|
|
|
|
switch (id) {
|
|
case IDM_ABOUT:
|
|
OnAbout();
|
|
return true;
|
|
case IDM_EXIT:
|
|
delegate_->OnExit(this);
|
|
return true;
|
|
case ID_FIND:
|
|
OnFind();
|
|
return true;
|
|
case IDC_NAV_BACK: // Back button
|
|
if (CefRefPtr<CefBrowser> browser = GetBrowser()) {
|
|
browser->GoBack();
|
|
}
|
|
return true;
|
|
case IDC_NAV_FORWARD: // Forward button
|
|
if (CefRefPtr<CefBrowser> browser = GetBrowser()) {
|
|
browser->GoForward();
|
|
}
|
|
return true;
|
|
case IDC_NAV_RELOAD: // Reload button
|
|
if (CefRefPtr<CefBrowser> browser = GetBrowser()) {
|
|
browser->Reload();
|
|
}
|
|
return true;
|
|
case IDC_NAV_STOP: // Stop button
|
|
if (CefRefPtr<CefBrowser> browser = GetBrowser()) {
|
|
browser->StopLoad();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void RootWindowWin::OnFind() {
|
|
if (find_hwnd_) {
|
|
// Give focus to the existing find dialog.
|
|
::SetFocus(find_hwnd_);
|
|
return;
|
|
}
|
|
|
|
// Configure dialog state.
|
|
ZeroMemory(&find_state_, sizeof(find_state_));
|
|
find_state_.lStructSize = sizeof(find_state_);
|
|
find_state_.hwndOwner = hwnd_;
|
|
find_state_.lpstrFindWhat = find_buff_;
|
|
find_state_.wFindWhatLen = sizeof(find_buff_);
|
|
find_state_.Flags = FR_HIDEWHOLEWORD | FR_DOWN;
|
|
|
|
// Create the dialog.
|
|
find_hwnd_ = FindText(&find_state_);
|
|
|
|
// Override the dialog's window procedure.
|
|
find_wndproc_old_ = SetWndProcPtr(find_hwnd_, FindWndProc);
|
|
|
|
// Associate |self| with the dialog.
|
|
SetUserDataPtr(find_hwnd_, this);
|
|
}
|
|
|
|
void RootWindowWin::OnFindEvent() {
|
|
CefRefPtr<CefBrowser> browser = GetBrowser();
|
|
|
|
if (find_state_.Flags & FR_DIALOGTERM) {
|
|
// The find dialog box has been dismissed so invalidate the handle and
|
|
// reset the search results.
|
|
if (browser) {
|
|
browser->GetHost()->StopFinding(true);
|
|
find_what_last_.clear();
|
|
find_next_ = false;
|
|
}
|
|
} else if ((find_state_.Flags & FR_FINDNEXT) && browser) {
|
|
// Search for the requested string.
|
|
bool match_case = ((find_state_.Flags & FR_MATCHCASE) ? true : false);
|
|
const std::wstring& find_what = find_buff_;
|
|
if (match_case != find_match_case_last_ || find_what != find_what_last_) {
|
|
// The search string has changed, so reset the search results.
|
|
if (!find_what.empty()) {
|
|
browser->GetHost()->StopFinding(true);
|
|
find_next_ = false;
|
|
}
|
|
find_match_case_last_ = match_case;
|
|
find_what_last_ = find_buff_;
|
|
}
|
|
|
|
browser->GetHost()->Find(find_what,
|
|
(find_state_.Flags & FR_DOWN) ? true : false,
|
|
match_case, find_next_);
|
|
if (!find_next_) {
|
|
find_next_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnAbout() {
|
|
// Show the about box.
|
|
DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd_,
|
|
AboutWndProc);
|
|
}
|
|
|
|
void RootWindowWin::OnNCCreate(LPCREATESTRUCT lpCreateStruct) {
|
|
if (IsProcessPerMonitorDpiAware()) {
|
|
// This call gets Windows to scale the non-client area when WM_DPICHANGED
|
|
// is fired on Windows versions >= 10.0.14393.0.
|
|
typedef BOOL(WINAPI * EnableNonClientDpiScalingPtr)(HWND);
|
|
static EnableNonClientDpiScalingPtr func_ptr =
|
|
reinterpret_cast<EnableNonClientDpiScalingPtr>(GetProcAddress(
|
|
GetModuleHandle(L"user32.dll"), "EnableNonClientDpiScaling"));
|
|
called_enable_non_client_dpi_scaling_ = !!(func_ptr && func_ptr(hwnd_));
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnCreate(LPCREATESTRUCT lpCreateStruct) {
|
|
const HINSTANCE hInstance = lpCreateStruct->hInstance;
|
|
|
|
RECT rect;
|
|
GetClientRect(hwnd_, &rect);
|
|
|
|
if (with_controls_) {
|
|
// Create the child controls.
|
|
int x_offset = 0;
|
|
|
|
const int button_width = GetButtonWidth(hwnd_);
|
|
const int urlbar_height = GetURLBarHeight(hwnd_);
|
|
|
|
back_hwnd_ = CreateWindow(
|
|
L"BUTTON", L"Back", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
|
|
x_offset, 0, button_width, urlbar_height, hwnd_,
|
|
reinterpret_cast<HMENU>(IDC_NAV_BACK), hInstance, 0);
|
|
CHECK(back_hwnd_);
|
|
x_offset += button_width;
|
|
|
|
forward_hwnd_ =
|
|
CreateWindow(L"BUTTON", L"Forward",
|
|
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
|
|
x_offset, 0, button_width, urlbar_height, hwnd_,
|
|
reinterpret_cast<HMENU>(IDC_NAV_FORWARD), hInstance, 0);
|
|
CHECK(forward_hwnd_);
|
|
x_offset += button_width;
|
|
|
|
reload_hwnd_ =
|
|
CreateWindow(L"BUTTON", L"Reload",
|
|
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
|
|
x_offset, 0, button_width, urlbar_height, hwnd_,
|
|
reinterpret_cast<HMENU>(IDC_NAV_RELOAD), hInstance, 0);
|
|
CHECK(reload_hwnd_);
|
|
x_offset += button_width;
|
|
|
|
stop_hwnd_ = CreateWindow(
|
|
L"BUTTON", L"Stop", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED,
|
|
x_offset, 0, button_width, urlbar_height, hwnd_,
|
|
reinterpret_cast<HMENU>(IDC_NAV_STOP), hInstance, 0);
|
|
CHECK(stop_hwnd_);
|
|
x_offset += button_width;
|
|
|
|
edit_hwnd_ = CreateWindow(L"EDIT", 0,
|
|
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT |
|
|
ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_DISABLED,
|
|
x_offset, 0, rect.right - button_width * 4,
|
|
urlbar_height, hwnd_, 0, hInstance, 0);
|
|
CHECK(edit_hwnd_);
|
|
|
|
// Override the edit control's window procedure.
|
|
edit_wndproc_old_ = SetWndProcPtr(edit_hwnd_, EditWndProc);
|
|
|
|
// Associate |this| with the edit window.
|
|
SetUserDataPtr(edit_hwnd_, this);
|
|
|
|
rect.top += urlbar_height;
|
|
|
|
if (!with_osr_) {
|
|
// Remove the menu items that are only used with OSR.
|
|
HMENU hMenu = ::GetMenu(hwnd_);
|
|
if (hMenu) {
|
|
HMENU hTestMenu = ::GetSubMenu(hMenu, 2);
|
|
if (hTestMenu) {
|
|
::RemoveMenu(hTestMenu, ID_TESTS_OSR_FPS, MF_BYCOMMAND);
|
|
::RemoveMenu(hTestMenu, ID_TESTS_OSR_DSF, MF_BYCOMMAND);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// No controls so also remove the default menu.
|
|
::SetMenu(hwnd_, nullptr);
|
|
}
|
|
|
|
const float device_scale_factor = GetWindowScaleFactor(hwnd_);
|
|
|
|
if (with_osr_) {
|
|
browser_window_->SetDeviceScaleFactor(device_scale_factor);
|
|
}
|
|
|
|
if (!is_popup_) {
|
|
// Create the browser window.
|
|
CefRect cef_rect(rect.left, rect.top, rect.right - rect.left,
|
|
rect.bottom - rect.top);
|
|
browser_window_->CreateBrowser(hwnd_, cef_rect, browser_settings_, nullptr,
|
|
delegate_->GetRequestContext(this));
|
|
} else {
|
|
// With popups we already have a browser window. Parent the browser window
|
|
// to the root window and show it in the correct location.
|
|
browser_window_->ShowPopup(hwnd_, rect.left, rect.top,
|
|
rect.right - rect.left, rect.bottom - rect.top);
|
|
}
|
|
}
|
|
|
|
bool RootWindowWin::OnClose() {
|
|
if (browser_window_ && !browser_window_->IsClosing()) {
|
|
CefRefPtr<CefBrowser> browser = GetBrowser();
|
|
if (browser) {
|
|
// Notify the browser window that we would like to close it. This
|
|
// will result in a call to ClientHandler::DoClose() if the
|
|
// JavaScript 'onbeforeunload' event handler allows it.
|
|
browser->GetHost()->CloseBrowser(false);
|
|
|
|
// Cancel the close.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
void RootWindowWin::OnDestroyed() {
|
|
window_destroyed_ = true;
|
|
NotifyDestroyedIfDone();
|
|
}
|
|
|
|
void RootWindowWin::OnBrowserCreated(CefRefPtr<CefBrowser> browser) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (is_popup_) {
|
|
// For popup browsers create the root window once the browser has been
|
|
// created.
|
|
CreateRootWindow(CefBrowserSettings(), false);
|
|
} else {
|
|
// Make sure the browser is sized correctly.
|
|
OnSize(false);
|
|
}
|
|
|
|
delegate_->OnBrowserCreated(this, browser);
|
|
}
|
|
|
|
void RootWindowWin::OnBrowserWindowDestroyed() {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
browser_window_.reset();
|
|
|
|
if (!window_destroyed_) {
|
|
// The browser was destroyed first. This could be due to the use of
|
|
// off-screen rendering or execution of JavaScript window.close().
|
|
// Close the RootWindow.
|
|
Close(true);
|
|
}
|
|
|
|
browser_destroyed_ = true;
|
|
NotifyDestroyedIfDone();
|
|
}
|
|
|
|
void RootWindowWin::OnSetAddress(const std::string& url) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (edit_hwnd_) {
|
|
SetWindowText(edit_hwnd_, CefString(url).ToWString().c_str());
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnSetTitle(const std::string& title) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (hwnd_) {
|
|
SetWindowText(hwnd_, CefString(title).ToWString().c_str());
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnSetFullscreen(bool fullscreen) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
CefRefPtr<CefBrowser> browser = GetBrowser();
|
|
if (browser) {
|
|
std::unique_ptr<window_test::WindowTestRunnerWin> test_runner(
|
|
new window_test::WindowTestRunnerWin());
|
|
if (fullscreen) {
|
|
test_runner->Maximize(browser);
|
|
} else {
|
|
test_runner->Restore(browser);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::OnAutoResize(const CefSize& new_size) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (!hwnd_) {
|
|
return;
|
|
}
|
|
|
|
int new_width = new_size.width;
|
|
|
|
// Make the window wide enough to drag by the top menu bar.
|
|
if (new_width < 200) {
|
|
new_width = 200;
|
|
}
|
|
|
|
const float device_scale_factor = GetWindowScaleFactor(hwnd_);
|
|
RECT rect = {0, 0, LogicalToDevice(new_width, device_scale_factor),
|
|
LogicalToDevice(new_size.height, device_scale_factor)};
|
|
DWORD style = GetWindowLong(hwnd_, GWL_STYLE);
|
|
DWORD ex_style = GetWindowLong(hwnd_, GWL_EXSTYLE);
|
|
bool has_menu = !(style & WS_CHILD) && (GetMenu(hwnd_) != nullptr);
|
|
|
|
// The size value is for the client area. Calculate the whole window size
|
|
// based on the current style.
|
|
AdjustWindowRectEx(&rect, style, has_menu, ex_style);
|
|
|
|
// Size the window. The left/top values may be negative.
|
|
// Also show the window if it's not currently visible.
|
|
SetWindowPos(hwnd_, nullptr, 0, 0, rect.right - rect.left,
|
|
rect.bottom - rect.top,
|
|
SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
|
}
|
|
|
|
void RootWindowWin::OnSetLoadingState(bool isLoading,
|
|
bool canGoBack,
|
|
bool canGoForward) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
if (with_controls_) {
|
|
EnableWindow(back_hwnd_, canGoBack);
|
|
EnableWindow(forward_hwnd_, canGoForward);
|
|
EnableWindow(reload_hwnd_, !isLoading);
|
|
EnableWindow(stop_hwnd_, isLoading);
|
|
EnableWindow(edit_hwnd_, TRUE);
|
|
}
|
|
|
|
if (!isLoading && GetWindowLongPtr(hwnd_, GWL_EXSTYLE) & WS_EX_NOACTIVATE) {
|
|
// Done with the initial navigation. Remove the WS_EX_NOACTIVATE style so
|
|
// that future mouse clicks inside the browser correctly activate and focus
|
|
// the window. For the top-level window removing this style causes Windows
|
|
// to display the task bar button.
|
|
SetWindowLongPtr(hwnd_, GWL_EXSTYLE,
|
|
GetWindowLongPtr(hwnd_, GWL_EXSTYLE) & ~WS_EX_NOACTIVATE);
|
|
|
|
if (browser_window_) {
|
|
HWND browser_hwnd = browser_window_->GetWindowHandle();
|
|
SetWindowLongPtr(
|
|
browser_hwnd, GWL_EXSTYLE,
|
|
GetWindowLongPtr(browser_hwnd, GWL_EXSTYLE) & ~WS_EX_NOACTIVATE);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
LPCWSTR kParentWndProc = L"CefParentWndProc";
|
|
LPCWSTR kDraggableRegion = L"CefDraggableRegion";
|
|
|
|
LRESULT CALLBACK SubclassedWindowProc(HWND hWnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam) {
|
|
WNDPROC hParentWndProc =
|
|
reinterpret_cast<WNDPROC>(::GetPropW(hWnd, kParentWndProc));
|
|
HRGN hRegion = reinterpret_cast<HRGN>(::GetPropW(hWnd, kDraggableRegion));
|
|
|
|
if (message == WM_NCHITTEST) {
|
|
LRESULT hit = CallWindowProc(hParentWndProc, hWnd, message, wParam, lParam);
|
|
if (hit == HTCLIENT) {
|
|
POINTS points = MAKEPOINTS(lParam);
|
|
POINT point = {points.x, points.y};
|
|
::ScreenToClient(hWnd, &point);
|
|
if (::PtInRegion(hRegion, point.x, point.y)) {
|
|
// Let the parent window handle WM_NCHITTEST by returning HTTRANSPARENT
|
|
// in child windows.
|
|
return HTTRANSPARENT;
|
|
}
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
return CallWindowProc(hParentWndProc, hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
void SubclassWindow(HWND hWnd, HRGN hRegion) {
|
|
HANDLE hParentWndProc = ::GetPropW(hWnd, kParentWndProc);
|
|
if (hParentWndProc) {
|
|
return;
|
|
}
|
|
|
|
SetLastError(0);
|
|
LONG_PTR hOldWndProc = SetWindowLongPtr(
|
|
hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(SubclassedWindowProc));
|
|
if (hOldWndProc == 0 && GetLastError() != ERROR_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
::SetPropW(hWnd, kParentWndProc, reinterpret_cast<HANDLE>(hOldWndProc));
|
|
::SetPropW(hWnd, kDraggableRegion, reinterpret_cast<HANDLE>(hRegion));
|
|
}
|
|
|
|
void UnSubclassWindow(HWND hWnd) {
|
|
LONG_PTR hParentWndProc =
|
|
reinterpret_cast<LONG_PTR>(::GetPropW(hWnd, kParentWndProc));
|
|
if (hParentWndProc) {
|
|
[[maybe_unused]] LONG_PTR hPreviousWndProc =
|
|
SetWindowLongPtr(hWnd, GWLP_WNDPROC, hParentWndProc);
|
|
DCHECK_EQ(hPreviousWndProc,
|
|
reinterpret_cast<LONG_PTR>(SubclassedWindowProc));
|
|
}
|
|
|
|
::RemovePropW(hWnd, kParentWndProc);
|
|
::RemovePropW(hWnd, kDraggableRegion);
|
|
}
|
|
|
|
BOOL CALLBACK SubclassWindowsProc(HWND hwnd, LPARAM lParam) {
|
|
SubclassWindow(hwnd, reinterpret_cast<HRGN>(lParam));
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CALLBACK UnSubclassWindowsProc(HWND hwnd, LPARAM lParam) {
|
|
UnSubclassWindow(hwnd);
|
|
return TRUE;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void RootWindowWin::OnSetDraggableRegions(
|
|
const std::vector<CefDraggableRegion>& regions) {
|
|
REQUIRE_MAIN_THREAD();
|
|
|
|
// Reset draggable region.
|
|
::SetRectRgn(draggable_region_, 0, 0, 0, 0);
|
|
|
|
// Determine new draggable region.
|
|
std::vector<CefDraggableRegion>::const_iterator it = regions.begin();
|
|
for (; it != regions.end(); ++it) {
|
|
HRGN region = ::CreateRectRgn(it->bounds.x, it->bounds.y,
|
|
it->bounds.x + it->bounds.width,
|
|
it->bounds.y + it->bounds.height);
|
|
::CombineRgn(draggable_region_, draggable_region_, region,
|
|
it->draggable ? RGN_OR : RGN_DIFF);
|
|
::DeleteObject(region);
|
|
}
|
|
|
|
// Subclass child window procedures in order to do hit-testing.
|
|
// This will be a no-op, if it is already subclassed.
|
|
if (hwnd_) {
|
|
WNDENUMPROC proc =
|
|
!regions.empty() ? SubclassWindowsProc : UnSubclassWindowsProc;
|
|
::EnumChildWindows(hwnd_, proc,
|
|
reinterpret_cast<LPARAM>(draggable_region_));
|
|
}
|
|
}
|
|
|
|
void RootWindowWin::NotifyDestroyedIfDone() {
|
|
// Notify once both the window and the browser have been destroyed.
|
|
if (window_destroyed_ && browser_destroyed_) {
|
|
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
|