Windows: Fix per-monitor DPI scaling of top-level browser windows and placement of context menus (issue #2313)

This commit is contained in:
Marshall Greenblatt 2018-02-21 16:52:02 -05:00
parent 57b9cf9ddd
commit a5a5e7ff08
5 changed files with 119 additions and 57 deletions

View File

@ -20,6 +20,7 @@
#include "base/memory/ref_counted_memory.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "third_party/WebKit/public/platform/WebMouseEvent.h"
#include "third_party/WebKit/public/platform/WebMouseWheelEvent.h"
@ -95,6 +96,31 @@ void ExecuteExternalProtocol(const GURL& url) {
ShellExecuteA(NULL, "open", address.c_str(), NULL, NULL, SW_SHOWNORMAL);
}
// DPI value for 1x scale factor.
#define DPI_1X 96.0f
float GetWindowScaleFactor(HWND hwnd) {
DCHECK(hwnd);
if (base::win::IsProcessPerMonitorDpiAware()) {
// Let Windows tell us the correct DPI.
static auto get_dpi_for_window_func = []() {
return reinterpret_cast<decltype(::GetDpiForWindow)*>(
GetProcAddress(GetModuleHandle(L"user32.dll"), "GetDpiForWindow"));
}();
if (get_dpi_for_window_func)
return static_cast<float>(get_dpi_for_window_func(hwnd)) / DPI_1X;
}
// Fallback to the monitor that contains the window center point.
RECT cr;
GetWindowRect(hwnd, &cr);
return display::Screen::GetScreen()
->GetDisplayNearestPoint(
gfx::Point((cr.right - cr.left) / 2, (cr.bottom - cr.top) / 2))
.device_scale_factor();
}
} // namespace
CefBrowserPlatformDelegateNativeWin::CefBrowserPlatformDelegateNativeWin(
@ -117,40 +143,49 @@ void CefBrowserPlatformDelegateNativeWin::BrowserDestroyed(
bool CefBrowserPlatformDelegateNativeWin::CreateHostWindow() {
RegisterWindowClass();
has_frame_ = !(window_info_.style & WS_CHILD);
std::wstring windowName(CefString(&window_info_.window_name));
// Create the new browser window.
window_info_.window = CreateWindowEx(
window_info_.ex_style, GetWndClass(), windowName.c_str(),
window_info_.style, window_info_.x, window_info_.y, window_info_.width,
window_info_.height, window_info_.parent_window, window_info_.menu,
::GetModuleHandle(NULL), NULL);
CreateWindowEx(window_info_.ex_style, GetWndClass(), windowName.c_str(),
window_info_.style, window_info_.x, window_info_.y,
window_info_.width, window_info_.height,
window_info_.parent_window, window_info_.menu,
::GetModuleHandle(NULL), this);
// It's possible for CreateWindowEx to fail if the parent window was
// destroyed between the call to CreateBrowser and the above one.
DCHECK(window_info_.window != NULL);
DCHECK(window_info_.window);
if (!window_info_.window)
return false;
host_window_created_ = true;
// Set window user data to this object for future reference from the window
// procedure.
gfx::SetWindowUserData(window_info_.window, this);
// Add a reference that will later be released in DestroyBrowser().
browser_->AddRef();
RECT cr;
GetClientRect(window_info_.window, &cr);
if (!called_enable_non_client_dpi_scaling_ && has_frame_ &&
base::win::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.
static auto enable_child_window_dpi_message_func = []() {
using EnableChildWindowDpiMessagePtr = LRESULT(WINAPI*)(HWND, BOOL);
return reinterpret_cast<EnableChildWindowDpiMessagePtr>(GetProcAddress(
GetModuleHandle(L"user32.dll"), "EnableChildWindowDpiMessage"));
}();
if (enable_child_window_dpi_message_func)
enable_child_window_dpi_message_func(window_info_.window, TRUE);
}
DCHECK(!window_widget_);
// Adjust for potential display scaling.
// Convert from device coordinates to logical coordinates.
RECT cr;
GetClientRect(window_info_.window, &cr);
gfx::Point point = gfx::Point(cr.right, cr.bottom);
float scale = display::Screen::GetScreen()
->GetDisplayNearestPoint(point)
.device_scale_factor();
const float scale = GetWindowScaleFactor(window_info_.window);
point =
gfx::ToFlooredPoint(gfx::ScalePoint(gfx::PointF(point), 1.0f / scale));
@ -270,19 +305,19 @@ gfx::Point CefBrowserPlatformDelegateNativeWin::GetScreenPoint(
if (windowless_handler_)
return windowless_handler_->GetParentScreenPoint(view);
if (!window_widget_)
if (!window_info_.window)
return view;
aura::Window* window = window_widget_->GetNativeView();
const gfx::Rect& bounds_in_screen = window->GetBoundsInScreen();
const gfx::Point& screen_point = gfx::Point(bounds_in_screen.x() + view.x(),
bounds_in_screen.y() + view.y());
// Convert from logical coordinates to device coordinates.
const float scale = GetWindowScaleFactor(window_info_.window);
const gfx::Point& device_pt =
gfx::ToFlooredPoint(gfx::ScalePoint(gfx::PointF(view), scale));
// Adjust for potential display scaling.
float scale = display::Screen::GetScreen()
->GetDisplayNearestPoint(screen_point)
.device_scale_factor();
return gfx::ToFlooredPoint(gfx::ScalePoint(gfx::PointF(screen_point), scale));
// Convert from client coordinates to screen coordinates.
POINT screen_pt = {device_pt.x(), device_pt.y()};
ClientToScreen(window_info_.window, &screen_pt);
return gfx::Point(screen_pt.x, screen_pt.y);
}
void CefBrowserPlatformDelegateNativeWin::ViewText(const std::string& text) {
@ -562,12 +597,15 @@ LRESULT CALLBACK CefBrowserPlatformDelegateNativeWin::WndProc(HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam) {
CefBrowserPlatformDelegateNativeWin* platform_delegate =
static_cast<CefBrowserPlatformDelegateNativeWin*>(
gfx::GetWindowUserData(hwnd));
CefBrowserPlatformDelegateNativeWin* platform_delegate = nullptr;
CefBrowserHostImpl* browser = nullptr;
if (platform_delegate)
browser = platform_delegate->browser_;
if (message != WM_NCCREATE) {
platform_delegate = static_cast<CefBrowserPlatformDelegateNativeWin*>(
gfx::GetWindowUserData(hwnd));
if (platform_delegate)
browser = platform_delegate->browser_;
}
switch (message) {
case WM_CLOSE:
@ -579,6 +617,31 @@ LRESULT CALLBACK CefBrowserPlatformDelegateNativeWin::WndProc(HWND hwnd,
// Allow the close.
break;
case WM_NCCREATE: {
CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
platform_delegate =
reinterpret_cast<CefBrowserPlatformDelegateNativeWin*>(
cs->lpCreateParams);
DCHECK(platform_delegate);
// Associate |platform_delegate| with the window handle.
gfx::SetWindowUserData(hwnd, platform_delegate);
platform_delegate->window_info_.window = hwnd;
if (platform_delegate->has_frame_ &&
base::win::IsProcessPerMonitorDpiAware()) {
// This call gets Windows to scale the non-client area when
// WM_DPICHANGED is fired on Windows versions >= 10.0.14393.0.
static auto enable_non_client_dpi_scaling_func = []() {
return reinterpret_cast<decltype(::EnableNonClientDpiScaling)*>(
GetProcAddress(GetModuleHandle(L"user32.dll"),
"EnableNonClientDpiScaling"));
}();
platform_delegate->called_enable_non_client_dpi_scaling_ =
!!(enable_non_client_dpi_scaling_func &&
enable_non_client_dpi_scaling_func(hwnd));
}
} break;
case WM_NCDESTROY:
if (platform_delegate) {
// Clear the user data pointer.
@ -617,6 +680,17 @@ LRESULT CALLBACK CefBrowserPlatformDelegateNativeWin::WndProc(HWND hwnd,
case WM_ERASEBKGND:
return 0;
case WM_DPICHANGED:
if (platform_delegate && platform_delegate->has_frame_) {
// Suggested size and position of the current window scaled for the
// new DPI.
const RECT* rect = reinterpret_cast<RECT*>(lParam);
SetWindowPos(platform_delegate->GetHostWindowHandle(), NULL, rect->left,
rect->top, rect->right - rect->left,
rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
}
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);

View File

@ -68,6 +68,9 @@ class CefBrowserPlatformDelegateNativeWin
// Widget hosting the web contents. It will be deleted automatically when the
// associated root window is destroyed.
views::Widget* window_widget_;
bool has_frame_ = false;
bool called_enable_non_client_dpi_scaling_ = false;
};
#endif // CEF_LIBCEF_BROWSER_NATIVE_BROWSER_PLATFORM_DELEGATE_NATIVE_WIN_H_

View File

@ -77,15 +77,15 @@ bool IsProcessPerMonitorDpiAware() {
}
// DPI value for 1x scale factor.
#define DPI_1X 96
#define DPI_1X 96.0f
int GetWindowScaleFactor(HWND hwnd) {
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 func_ptr(hwnd) / DPI_1X;
return static_cast<float>(func_ptr(hwnd)) / DPI_1X;
}
return client::GetDeviceScaleFactor();
@ -638,6 +638,7 @@ void RootWindowWin::OnSize(bool minimized) {
const int font_height = LogicalToDevice(14, GetWindowScaleFactor(hwnd_));
if (font_height != font_height_) {
font_height_ = font_height;
if (font_) {
DeleteObject(font_);
}
@ -716,18 +717,11 @@ void RootWindowWin::OnDpiChanged(WPARAM wParam, LPARAM lParam) {
return;
}
if (browser_window_) {
if (with_osr_) {
browser_window_->SetDeviceScaleFactor(LOWORD(wParam) / DPI_1X);
} else {
// Forward the DPI change to the browser window handle.
HWND browser_hwnd = browser_window_->GetWindowHandle();
if (browser_hwnd) {
RECT child_rect = {};
SendMessage(browser_hwnd, WM_DPICHANGED, wParam,
reinterpret_cast<LPARAM>(&child_rect));
}
}
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.
@ -928,7 +922,7 @@ void RootWindowWin::OnCreate(LPCREATESTRUCT lpCreateStruct) {
::SetMenu(hwnd_, NULL);
}
const int device_scale_factor = GetWindowScaleFactor(hwnd_);
const float device_scale_factor = GetWindowScaleFactor(hwnd_);
if (with_osr_) {
browser_window_->SetDeviceScaleFactor(device_scale_factor);
@ -938,7 +932,6 @@ void RootWindowWin::OnCreate(LPCREATESTRUCT lpCreateStruct) {
// Create the browser window.
CefRect cef_rect(rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top);
cef_rect = client::DeviceToLogical(cef_rect, device_scale_factor);
browser_window_->CreateBrowser(hwnd_, cef_rect, browser_settings_,
delegate_->GetRequestContext(this));
} else {
@ -1043,7 +1036,7 @@ void RootWindowWin::OnAutoResize(const CefSize& new_size) {
if (new_width < 200)
new_width = 200;
const int device_scale_factor = GetWindowScaleFactor(hwnd_);
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);

View File

@ -25,13 +25,6 @@ int DeviceToLogical(int value, float device_scale_factor) {
return static_cast<int>(std::floor(scaled_val));
}
CefRect DeviceToLogical(const CefRect& value, float device_scale_factor) {
return CefRect(DeviceToLogical(value.x, device_scale_factor),
DeviceToLogical(value.y, device_scale_factor),
DeviceToLogical(value.width, device_scale_factor),
DeviceToLogical(value.height, device_scale_factor));
}
void DeviceToLogical(CefMouseEvent& value, float device_scale_factor) {
value.x = DeviceToLogical(value.x, device_scale_factor);
value.y = DeviceToLogical(value.y, device_scale_factor);

View File

@ -16,7 +16,6 @@ CefRect LogicalToDevice(const CefRect& value, float device_scale_factor);
// Convert |value| from device coordinates to logical coordinates.
int DeviceToLogical(int value, float device_scale_factor);
CefRect DeviceToLogical(const CefRect& value, float device_scale_factor);
void DeviceToLogical(CefMouseEvent& value, float device_scale_factor);
} // namespace client