windows: Fix size/placement with multi-DPI screen setup (fixes issue #3359)

Use ScreenWin functions to correctly compute DIP/pixel conversions for
CEF-created top-level windows.

Fix incorrect DIPToScreenRect usage in DesktopWindowTreeHostWin when
|has_external_parent_| is true.
This commit is contained in:
Marshall Greenblatt 2022-10-24 19:47:04 -04:00
parent 07bf5dbacc
commit 767c4422ac
8 changed files with 160 additions and 144 deletions

View File

@ -28,6 +28,7 @@
#include "ui/base/win/shell.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/win/screen_win.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
@ -122,84 +123,65 @@ void ExecuteExternalProtocol(const GURL& url) {
}
}
// 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();
}
struct ScreenInfo {
float scale_factor;
CefRect rect;
};
ScreenInfo GetScreenInfo(int x, int y) {
gfx::Rect GetDisplayWorkAreaNearestPoint(gfx::Point dip_point) {
const auto display =
display::Screen::GetScreen()->GetDisplayNearestPoint(gfx::Point(x, y));
const auto rect = display.work_area();
return ScreenInfo{display.device_scale_factor(),
CefRect(rect.x(), rect.y(), rect.width(), rect.height())};
display::Screen::GetScreen()->GetDisplayNearestPoint(dip_point);
// Work area in DIP.
return display.work_area();
}
CefRect GetFrameRectFromLogicalContentRect(CefRect content,
DWORD style,
DWORD ex_style,
bool has_menu,
float scale) {
const auto scaled_rect = gfx::ScaleToRoundedRect(
gfx::Rect(content.x, content.y, content.width, content.height), scale);
CefRect GetScreenFrameRectFromDIPContentRect(HWND window,
gfx::Rect dip_rect,
DWORD style,
DWORD ex_style,
bool has_menu) {
// Convert from DIP using a method that can handle multiple displays with
// different DPI. If |window| is nullptr the closest display will be used.
const auto screen_rect =
display::win::ScreenWin::DIPToScreenRect(window, dip_rect);
RECT rect = {0, 0, scaled_rect.width(), scaled_rect.height()};
RECT rect = {screen_rect.x(), screen_rect.y(),
screen_rect.x() + screen_rect.width(),
screen_rect.y() + screen_rect.height()};
AdjustWindowRectEx(&rect, style, has_menu, ex_style);
return CefRect(scaled_rect.x(), scaled_rect.y(), rect.right - rect.left,
// Keep the original origin while potentially increasing the size to include
// the frame non-client area.
return CefRect(screen_rect.x(), screen_rect.y(), rect.right - rect.left,
rect.bottom - rect.top);
}
CefRect GetAdjustedWindowRect(CefRect content,
DWORD style,
DWORD ex_style,
bool has_menu) {
// If height or width is not provided, let OS determine position and size,
// similarly to Chromium behavior
if (content.width == CW_USEDEFAULT || content.height == CW_USEDEFAULT) {
CefRect GetAdjustedScreenFrameRect(CefRect screen_rect,
DWORD style,
DWORD ex_style,
bool has_menu) {
// If height or width is not provided let the OS determine the position and
// size similar to Chromium behavior. Note that |CW_USEDEFAULT| cannot be
// stored in a gfx::Rect due to clamping.
if (screen_rect.width == CW_USEDEFAULT ||
screen_rect.height == CW_USEDEFAULT) {
return CefRect(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
}
if (content.x == CW_USEDEFAULT) {
content.x = 0;
if (screen_rect.x == CW_USEDEFAULT) {
screen_rect.x = 0;
}
if (content.y == CW_USEDEFAULT) {
content.y = 0;
if (screen_rect.y == CW_USEDEFAULT) {
screen_rect.y = 0;
}
const ScreenInfo screen = GetScreenInfo(content.x, content.y);
const CefRect rect = MakeVisibleOnScreenRect(content, screen.rect);
// Convert to DIP using a method that can handle multiple displays with
// different DPI.
const auto dip_rect = display::win::ScreenWin::ScreenToDIPRect(
nullptr, gfx::Rect(screen_rect.x, screen_rect.y, screen_rect.width,
screen_rect.height));
const auto visible_dip_rect = MakeVisibleOnScreenRect(
dip_rect, GetDisplayWorkAreaNearestPoint(dip_rect.origin()));
return GetFrameRectFromLogicalContentRect(rect, style, ex_style, has_menu,
screen.scale_factor);
return GetScreenFrameRectFromDIPContentRect(
/*window=*/nullptr, visible_dip_rect, style, ex_style, has_menu);
}
} // namespace
@ -245,8 +227,8 @@ bool CefBrowserPlatformDelegateNativeWin::CreateHostWindow() {
if (!window_info_.parent_window) {
const bool has_menu =
!(window_info_.style & WS_CHILD) && (window_info_.menu != NULL);
window_rect = GetAdjustedWindowRect(window_rect, window_info_.style,
window_info_.ex_style, has_menu);
window_rect = GetAdjustedScreenFrameRect(window_rect, window_info_.style,
window_info_.ex_style, has_menu);
}
// Create the new browser window.
@ -283,13 +265,13 @@ bool CefBrowserPlatformDelegateNativeWin::CreateHostWindow() {
DCHECK(!window_widget_);
// Convert from device coordinates to logical coordinates.
RECT cr;
GetClientRect(window_info_.window, &cr);
gfx::Point point = gfx::Point(cr.right, cr.bottom);
const float scale = GetWindowScaleFactor(window_info_.window);
point =
gfx::ToFlooredPoint(gfx::ScalePoint(gfx::PointF(point), 1.0f / scale));
// Convert to DIP using a method that can handle multiple displays with
// different DPI. Client coordinates always have origin (0,0).
const gfx::Rect dip_rect = display::win::ScreenWin::ScreenToDIPRect(
window_info_.window, gfx::Rect(0, 0, cr.right, cr.bottom));
// Stay on top if top-most window hosting the web view is topmost.
HWND top_level_window = GetAncestor(window_info_.window, GA_ROOT);
@ -301,7 +283,7 @@ bool CefBrowserPlatformDelegateNativeWin::CreateHostWindow() {
CefWindowDelegateView* delegate_view = new CefWindowDelegateView(
GetBackgroundColor(), always_on_top, GetBoundsChangedCallback());
delegate_view->Init(window_info_.window, web_contents_,
gfx::Rect(0, 0, point.x(), point.y()));
gfx::Rect(0, 0, dip_rect.width(), dip_rect.height()));
window_widget_ = delegate_view->GetWidget();
@ -440,11 +422,9 @@ void CefBrowserPlatformDelegateNativeWin::SizeTo(int width, int height) {
const DWORD style = GetWindowLong(window, GWL_STYLE);
const DWORD ex_style = GetWindowLong(window, GWL_EXSTYLE);
const bool has_menu = !(style & WS_CHILD) && (GetMenu(window) != NULL);
const float scale = GetWindowScaleFactor(window);
const CefRect content_rect(0, 0, width, height);
const CefRect frame_rect = GetFrameRectFromLogicalContentRect(
content_rect, style, ex_style, has_menu, scale);
const auto frame_rect = GetScreenFrameRectFromDIPContentRect(
window, gfx::Rect(0, 0, width, height), style, ex_style, has_menu);
// Size the window. The left/top values may be negative.
SetWindowPos(window, NULL, 0, 0, frame_rect.width, frame_rect.height,

View File

@ -6,6 +6,8 @@
#include <algorithm>
#include "ui/gfx/geometry/rect.h"
namespace {
constexpr int kMinWidth = 0;
@ -25,15 +27,17 @@ int clamp_segment_start(int start, int len, int min, int max) {
} // namespace
CefRect MakeVisibleOnScreenRect(const CefRect& rect, const CefRect& screen) {
const int width = std::clamp(rect.width, kMinWidth, screen.width);
const int height = std::clamp(rect.height, kMinHeight, screen.height);
gfx::Rect MakeVisibleOnScreenRect(const gfx::Rect& rect,
const gfx::Rect& screen) {
const int width = std::clamp(rect.width(), kMinWidth, screen.width());
const int height = std::clamp(rect.height(), kMinHeight, screen.height());
const int right_border = screen.x + screen.width;
const int x = clamp_segment_start(rect.x, width, screen.x, right_border);
const int right_border = screen.x() + screen.width();
const int x = clamp_segment_start(rect.x(), width, screen.x(), right_border);
const int bottom_border = screen.y + screen.height;
const int y = clamp_segment_start(rect.y, height, screen.y, bottom_border);
const int bottom_border = screen.y() + screen.height();
const int y =
clamp_segment_start(rect.y(), height, screen.y(), bottom_border);
return CefRect(x, y, width, height);
return gfx::Rect(x, y, width, height);
}

View File

@ -6,12 +6,15 @@
#define CEF_LIBCEF_BROWSER_SCREEN_UTIL_H_
#pragma once
#include "include/internal/cef_types_wrappers.h"
namespace gfx {
class Rect;
}
// Create a new rectangle from the input |rect| rectangle that is fully visible
// on provided |screen_rect| screen. The width and height of the resulting
// rectangle are clamped to the screen width and height respectively if they
// would overflow.
CefRect MakeVisibleOnScreenRect(const CefRect& rect, const CefRect& screen);
gfx::Rect MakeVisibleOnScreenRect(const gfx::Rect& rect,
const gfx::Rect& screen);
#endif // CEF_LIBCEF_BROWSER_SCREEN_UTIL_H_

View File

@ -5,77 +5,79 @@
#include "cef/libcef/browser/screen_util.h"
#include "tests/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
namespace {
constexpr int kScreenWidth = 1024;
constexpr int kScreenHeight = 768;
const CefRect kMainScreen(0, 0, kScreenWidth, kScreenHeight);
const CefRect kLeftScreen(-1024, 0, kScreenWidth, kScreenHeight);
const gfx::Rect kMainScreen(0, 0, kScreenWidth, kScreenHeight);
const gfx::Rect kLeftScreen(-1024, 0, kScreenWidth, kScreenHeight);
} // namespace
TEST(MakeVisibleOnScreenRect, RectSizeIsBiggerThanScreen) {
const CefRect rect{400, 500, 1500, 800};
const gfx::Rect rect{400, 500, 1500, 800};
auto result = MakeVisibleOnScreenRect(rect, kMainScreen);
EXPECT_EQ(result.x, 0);
EXPECT_EQ(result.width, kMainScreen.width);
EXPECT_EQ(result.y, 0);
EXPECT_EQ(result.height, kMainScreen.height);
EXPECT_EQ(result.x(), 0);
EXPECT_EQ(result.width(), kMainScreen.width());
EXPECT_EQ(result.y(), 0);
EXPECT_EQ(result.height(), kMainScreen.height());
}
TEST(MakeVisibleOnScreenRect, RightBorderIsOutsideTheScreen) {
const CefRect rect{600, 400, 500, 300};
const gfx::Rect rect{600, 400, 500, 300};
auto result = MakeVisibleOnScreenRect(rect, kMainScreen);
EXPECT_EQ(result.x, 524);
EXPECT_EQ(result.width, rect.width);
EXPECT_EQ(result.y, rect.y);
EXPECT_EQ(result.height, rect.height);
EXPECT_EQ(result.x(), 524);
EXPECT_EQ(result.width(), rect.width());
EXPECT_EQ(result.y(), rect.y());
EXPECT_EQ(result.height(), rect.height());
}
TEST(MakeVisibleOnScreenRect, LeftBorderIsOutsideTheScreen) {
const CefRect rect{-400, 400, 500, 300};
const gfx::Rect rect{-400, 400, 500, 300};
auto result = MakeVisibleOnScreenRect(rect, kMainScreen);
EXPECT_EQ(result.x, 0);
EXPECT_EQ(result.width, rect.width);
EXPECT_EQ(result.y, rect.y);
EXPECT_EQ(result.height, rect.height);
EXPECT_EQ(result.x(), 0);
EXPECT_EQ(result.width(), rect.width());
EXPECT_EQ(result.y(), rect.y());
EXPECT_EQ(result.height(), rect.height());
}
TEST(MakeVisibleOnScreenRect, BottomBorderIsOutsideTheScreen) {
const CefRect rect{600, 500, 300, 300};
const gfx::Rect rect{600, 500, 300, 300};
auto result = MakeVisibleOnScreenRect(rect, kMainScreen);
EXPECT_EQ(result.x, 600);
EXPECT_EQ(result.width, rect.width);
EXPECT_EQ(result.y, 468);
EXPECT_EQ(result.height, rect.height);
EXPECT_EQ(result.x(), 600);
EXPECT_EQ(result.width(), rect.width());
EXPECT_EQ(result.y(), 468);
EXPECT_EQ(result.height(), rect.height());
}
TEST(MakeVisibleOnScreenRect, RectIsVisibleOnTheLeftScreen) {
const CefRect rect{-500, 300, 300, 300};
const gfx::Rect rect{-500, 300, 300, 300};
auto result = MakeVisibleOnScreenRect(rect, kLeftScreen);
EXPECT_EQ(result.x, rect.x);
EXPECT_EQ(result.width, rect.width);
EXPECT_EQ(result.y, rect.y);
EXPECT_EQ(result.height, rect.height);
EXPECT_EQ(result.x(), rect.x());
EXPECT_EQ(result.width(), rect.width());
EXPECT_EQ(result.y(), rect.y());
EXPECT_EQ(result.height(), rect.height());
}
TEST(MakeVisibleOnScreenRect, RectSizeIsBiggerThanLeftScreen) {
const CefRect rect{-500, 300, 3000, 3000};
const gfx::Rect rect{-500, 300, 3000, 3000};
auto result = MakeVisibleOnScreenRect(rect, kLeftScreen);
EXPECT_EQ(result.x, kLeftScreen.x);
EXPECT_EQ(result.width, kLeftScreen.width);
EXPECT_EQ(result.y, kLeftScreen.y);
EXPECT_EQ(result.height, kLeftScreen.height);
EXPECT_EQ(result.x(), kLeftScreen.x());
EXPECT_EQ(result.width(), kLeftScreen.width());
EXPECT_EQ(result.y(), kLeftScreen.y());
EXPECT_EQ(result.height(), kLeftScreen.height());
}

View File

@ -118,6 +118,10 @@ patches = [
# Allow override of RWHVBase::GetNewScreenInfosForUpdate() which is now
# required due to https://crrev.com/96938eb36e in order to use
# RWHVBase::UpdateScreenInfo() with OSR.
#
# Windows: Fix incorrect DIPToScreenRect usage in DesktopWindowTreeHostWin
# when |has_external_parent_| is true.
# https://bitbucket.org/chromiumembedded/cef/issues/3359
'name': 'views_widget',
},
{

View File

@ -258,10 +258,10 @@ index 774a2d23a87a6..88769ad800d22 100644
// Calculate initial bounds.
diff --git ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 0d5595767664e..dc9162d3c6eb3 100644
index 0d5595767664e..d36964f634683 100644
--- ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -183,8 +183,12 @@ void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
@@ -183,16 +183,28 @@ void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
native_widget_delegate_);
HWND parent_hwnd = nullptr;
@ -275,7 +275,26 @@ index 0d5595767664e..dc9162d3c6eb3 100644
remove_standard_frame_ = params.remove_standard_frame;
has_non_client_view_ = Widget::RequiresNonClientView(params.type);
@@ -1033,11 +1037,15 @@ void DesktopWindowTreeHostWin::HandleFrameChanged() {
z_order_ = params.EffectiveZOrderLevel();
- // We don't have an HWND yet, so scale relative to the nearest screen.
- gfx::Rect pixel_bounds =
- display::win::ScreenWin::DIPToScreenRect(nullptr, params.bounds);
+ gfx::Rect pixel_bounds;
+ if (has_external_parent_) {
+ // Scale relative to the screen that contains the parent window.
+ // Child windows always have origin (0,0).
+ pixel_bounds.set_size(display::win::ScreenWin::DIPToScreenSize(
+ parent_hwnd, params.bounds.size()));
+ } else {
+ // We don't have an HWND yet, so scale relative to the nearest screen.
+ pixel_bounds =
+ display::win::ScreenWin::DIPToScreenRect(nullptr, params.bounds);
+ }
message_handler_->Init(parent_hwnd, pixel_bounds, params.headless_mode);
CreateCompositor(params.force_software_compositing);
OnAcceleratedWidgetAvailable();
@@ -1033,11 +1045,15 @@ void DesktopWindowTreeHostWin::HandleFrameChanged() {
}
void DesktopWindowTreeHostWin::HandleNativeFocus(HWND last_focused_window) {
@ -293,7 +312,7 @@ index 0d5595767664e..dc9162d3c6eb3 100644
}
bool DesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
@@ -1045,6 +1053,12 @@ bool DesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
@@ -1045,6 +1061,12 @@ bool DesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
if (ui::PlatformEventSource::ShouldIgnoreNativePlatformEvents())
return true;
@ -306,6 +325,24 @@ index 0d5595767664e..dc9162d3c6eb3 100644
SendEventToSink(event);
return event->handled();
}
@@ -1224,8 +1246,16 @@ void DesktopWindowTreeHostWin::SetBoundsInDIP(const gfx::Rect& bounds) {
// positions in variable-DPI situations. See https://crbug.com/1224715 for
// details.
aura::Window* root = nullptr;
- const gfx::Rect bounds_in_pixels =
+ if (has_external_parent_) {
+ // Scale relative to the screen that contains the parent window.
+ root = AsWindowTreeHost()->window();
+ }
+ gfx::Rect bounds_in_pixels =
display::Screen::GetScreen()->DIPToScreenRectInWindow(root, bounds);
+ if (has_external_parent_) {
+ // Child windows always have origin (0,0).
+ bounds_in_pixels.set_origin(gfx::Point(0, 0));
+ }
AsWindowTreeHost()->SetBoundsInPixels(bounds_in_pixels);
}
diff --git ui/views/widget/desktop_aura/desktop_window_tree_host_win.h ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index cec35ceb25477..6eab66d5676b5 100644
--- ui/views/widget/desktop_aura/desktop_window_tree_host_win.h

View File

@ -30,6 +30,10 @@ namespace {
const char kDefaultExtensionIcon[] = "window_icon";
// Default window size.
constexpr int kDefaultWidth = 800;
constexpr int kDefaultHeight = 600;
// Control IDs for Views in the top-level Window.
enum ControlIds {
ID_WINDOW = 1,
@ -513,13 +517,10 @@ void ViewsWindow::OnWindowCreated(CefRefPtr<CefWindow> window) {
delegate_->OnViewsWindowCreated(this);
const CefRect bounds = GetInitialBounds();
if (bounds.x == 0 && bounds.y == 0) {
// Size the Window and center it.
window_->CenterWindow(CefSize(bounds.width, bounds.height));
} else {
// Set the Window bounds as specified.
window_->SetBounds(bounds);
const CefRect bounds = delegate_->GetWindowBounds();
if (bounds.IsEmpty()) {
// Size the Window and center it at the default size.
window_->CenterWindow(CefSize(kDefaultWidth, kDefaultHeight));
}
// Set the background color for regions that are not obscured by other Views.
@ -609,14 +610,12 @@ CefRefPtr<CefWindow> ViewsWindow::GetParentWindow(CefRefPtr<CefWindow> window,
CefRect ViewsWindow::GetInitialBounds(CefRefPtr<CefWindow> window) {
CEF_REQUIRE_UI_THREAD();
if (frameless_) {
const CefRect bounds = delegate_->GetWindowBounds();
if (frameless_ && bounds.IsEmpty()) {
// Need to provide a size for frameless windows that will be centered.
const CefRect bounds = GetInitialBounds();
if (bounds.x == 0 && bounds.y == 0) {
return bounds;
}
return CefRect(0, 0, kDefaultWidth, kDefaultHeight);
}
return CefRect();
return bounds;
}
cef_show_state_t ViewsWindow::GetInitialShowState(CefRefPtr<CefWindow> window) {
@ -1130,15 +1129,4 @@ void ViewsWindow::OnExtensionWindowClosed() {
extension_button_pressed_lock_ = nullptr;
}
CefRect ViewsWindow::GetInitialBounds() const {
CEF_REQUIRE_UI_THREAD();
CefRect bounds = delegate_->GetWindowBounds();
if (bounds.IsEmpty()) {
// Use the default size.
bounds.width = 800;
bounds.height = 600;
}
return bounds;
}
} // namespace client

View File

@ -220,8 +220,6 @@ class ViewsWindow : public CefBrowserViewDelegate,
const ImageCache::ImageSet& images);
void OnExtensionWindowClosed();
CefRect GetInitialBounds() const;
Delegate* delegate_; // Not owned by this object.
CefRefPtr<CefBrowserView> browser_view_;
bool frameless_;