Support JavaScript window.moveTo/By() and resizeTo/By() (fixes #698)

Adds new CefDisplayHandler::OnContentsBoundsChange and
CefDisplayHandler::GetRootWindowScreenRect callbacks.

cefclient: Implement the above callbacks and call
CefBrowserHost::NotifyScreenInfoChanged when the root window
bounds change.

cefclient: osr: Use real screen bounds by default. Pass
`--fake-screen-bounds` for the old default behavior.

Load https://tests/window in cefclient for additional
implementation details and usage examples.
This commit is contained in:
Marshall Greenblatt
2025-04-23 20:33:07 -04:00
parent f59112d839
commit faa85bf980
68 changed files with 1725 additions and 700 deletions

View File

@ -410,6 +410,8 @@
'tests/cefclient/browser/temp_window_mac.mm',
'tests/cefclient/browser/text_input_client_osr_mac.h',
'tests/cefclient/browser/text_input_client_osr_mac.mm',
'tests/cefclient/browser/util_mac.h',
'tests/cefclient/browser/util_mac.mm',
'tests/cefclient/browser/views_window_mac.mm',
'tests/cefclient/browser/window_test_runner_mac.h',
'tests/cefclient/browser/window_test_runner_mac.mm',

View File

@ -707,12 +707,24 @@ class CefBrowserHost : public virtual CefBaseRefCounted {
virtual void WasHidden(bool hidden) = 0;
///
/// Send a notification to the browser that the screen info has changed. The
/// browser will then call CefRenderHandler::GetScreenInfo to update the
/// screen information with the new values. This simulates moving the webview
/// window from one display to another, or changing the properties of the
/// current display. This method is only used when window rendering is
/// disabled.
/// Notify the browser that screen information has changed. Updated
/// information will be sent to the renderer process to configure screen size
/// and position values used by CSS and JavaScript (window.deviceScaleFactor,
/// window.screenX/Y, window.outerWidth/Height, etc.). For background see
/// https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage.md#markdown-header-coordinate-systems
///
/// This method is used with (a) windowless rendering and (b) windowed
/// rendering with external (client-provided) root window.
///
/// With windowless rendering the browser will call
/// CefRenderHandler::GetScreenInfo, CefRenderHandler::GetRootScreenRect and
/// CefRenderHandler::GetViewRect. This simulates moving or resizing the root
/// window in the current display, moving the root window from one display to
/// another, or changing the properties of the current display.
///
/// With windowed rendering the browser will call
/// CefDisplayHandler::GetRootWindowScreenRect and use the associated
/// display properties.
///
/*--cef()--*/
virtual void NotifyScreenInfoChanged() = 0;

View File

@ -38,6 +38,7 @@
#define CEF_INCLUDE_CEF_DISPLAY_HANDLER_H_
#pragma once
#include "include/cef_api_hash.h"
#include "include/cef_base.h"
#include "include/cef_browser.h"
#include "include/cef_frame.h"
@ -123,7 +124,7 @@ class CefDisplayHandler : public virtual CefBaseRefCounted {
///
/// Called when auto-resize is enabled via
/// CefBrowserHost::SetAutoResizeEnabled and the contents have auto-resized.
/// |new_size| will be the desired size in view coordinates. Return true if
/// |new_size| will be the desired size in DIP coordinates. Return true if
/// the resize was handled or false for default handling.
///
/*--cef()--*/
@ -162,6 +163,46 @@ class CefDisplayHandler : public virtual CefBaseRefCounted {
virtual void OnMediaAccessChange(CefRefPtr<CefBrowser> browser,
bool has_video_access,
bool has_audio_access) {}
#if CEF_API_ADDED(CEF_NEXT)
///
/// Called when JavaScript is requesting new bounds via window.moveTo/By() or
/// window.resizeTo/By(). |new_bounds| are in DIP screen coordinates.
///
/// With Views-hosted browsers |new_bounds| are the desired bounds for
/// the containing CefWindow and may be passed directly to
/// CefWindow::SetBounds. With external (client-provided) parent on macOS and
/// Windows |new_bounds| are the desired frame bounds for the containing root
/// window. With other non-Views browsers |new_bounds| are the desired bounds
/// for the browser content only unless the client implements either
/// CefDisplayHandler::GetRootWindowScreenRect for windowed browsers or
/// CefRenderHandler::GetWindowScreenRect for windowless browsers. Clients may
/// expand browser content bounds to window bounds using OS-specific or
/// CefDisplay methods.
///
/// Return true if this method was handled or false for default handling.
/// Default move/resize behavior is only provided with Views-hosted Chrome
/// style browsers.
///
/*--cef(added=next)--*/
virtual bool OnContentsBoundsChange(CefRefPtr<CefBrowser> browser,
const CefRect& new_bounds) {
return false;
}
///
/// Called to retrieve the external (client-provided) root window rectangle in
/// screen DIP coordinates. Only called for windowed browsers on Windows and
/// Linux. Return true if the rectangle was provided. Return false to use the
/// root window bounds on Windows or the browser content bounds on Linux. For
/// additional usage details see CefBrowserHost::NotifyScreenInfoChanged.
///
/*--cef(added=next)--*/
virtual bool GetRootWindowScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
return false;
}
#endif
};
#endif // CEF_INCLUDE_CEF_DISPLAY_HANDLER_H_

View File

@ -50,6 +50,9 @@
/// indicated. Methods must be called on the browser process UI thread unless
/// otherwise indicated.
///
/// For details on coordinate systems and usage see
/// https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-coordinate-systems
///
/*--cef(source=library)--*/
class CefDisplay : public CefBaseRefCounted {
public:
@ -129,7 +132,9 @@ class CefDisplay : public CefBaseRefCounted {
/// Returns this Display's device pixel scale factor. This specifies how much
/// the UI should be scaled when the actual output has more pixels than
/// standard displays (which is around 100~120dpi). The potential return
/// values differ by platform.
/// values differ by platform. Windowed browsers with 1.0 zoom will have a
/// JavaScript `window.devicePixelRatio` value matching the associated
/// Display's GetDeviceScaleFactor() value.
///
/*--cef()--*/
virtual float GetDeviceScaleFactor() = 0;

View File

@ -416,24 +416,6 @@ void AlloyBrowserHostImpl::WasHidden(bool hidden) {
}
}
void AlloyBrowserHostImpl::NotifyScreenInfoChanged() {
if (!IsWindowless()) {
DCHECK(false) << "Window rendering is not disabled";
return;
}
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(
CEF_UIT,
base::BindOnce(&AlloyBrowserHostImpl::NotifyScreenInfoChanged, this));
return;
}
if (platform_delegate_) {
platform_delegate_->NotifyScreenInfoChanged();
}
}
void AlloyBrowserHostImpl::Invalidate(PaintElementType type) {
if (!IsWindowless()) {
DCHECK(false) << "Window rendering is not disabled";
@ -998,6 +980,11 @@ void AlloyBrowserHostImpl::CloseContents(content::WebContents* source) {
}
}
void AlloyBrowserHostImpl::SetContentsBounds(content::WebContents* source,
const gfx::Rect& bounds) {
contents_delegate_.SetContentsBoundsEx(source, bounds);
}
void AlloyBrowserHostImpl::UpdateTargetURL(content::WebContents* source,
const GURL& url) {
contents_delegate_.UpdateTargetURL(source, url);

View File

@ -90,7 +90,6 @@ class AlloyBrowserHostImpl : public CefBrowserHostBase,
bool IsWindowRenderingDisabled() override;
void WasResized() override;
void WasHidden(bool hidden) override;
void NotifyScreenInfoChanged() override;
void Invalidate(PaintElementType type) override;
void SendExternalBeginFrame() override;
void SendTouchEvent(const CefTouchEvent& event) override;
@ -193,6 +192,8 @@ class AlloyBrowserHostImpl : public CefBrowserHostBase,
void LoadingStateChanged(content::WebContents* source,
bool should_show_loading_ui) override;
void CloseContents(content::WebContents* source) override;
void SetContentsBounds(content::WebContents* source,
const gfx::Rect& bounds) override;
void UpdateTargetURL(content::WebContents* source, const GURL& url) override;
bool DidAddMessageToConsole(content::WebContents* source,
blink::mojom::ConsoleMessageLevel log_level,

View File

@ -142,6 +142,18 @@ content::WebContents* CefBrowserContentsDelegate::OpenURLFromTabEx(
return nullptr;
}
bool CefBrowserContentsDelegate::SetContentsBoundsEx(
content::WebContents* source,
const gfx::Rect& bounds) {
if (auto c = client()) {
if (auto handler = c->GetDisplayHandler()) {
return handler->OnContentsBoundsChange(
browser(), {bounds.x(), bounds.y(), bounds.width(), bounds.height()});
}
}
return false;
}
void CefBrowserContentsDelegate::LoadingStateChanged(
content::WebContents* source,
bool should_show_loading_ui) {

View File

@ -87,6 +87,10 @@ class CefBrowserContentsDelegate : public content::WebContentsDelegate,
base::OnceCallback<void(content::NavigationHandle&)>&
navigation_handle_callback);
// Same as SetContentsBounds but returning false if unhandled.
bool SetContentsBoundsEx(content::WebContents* source,
const gfx::Rect& bounds);
// WebContentsDelegate methods:
void LoadingStateChanged(content::WebContents* source,
bool should_show_loading_ui) override;

View File

@ -817,9 +817,30 @@ void CefBrowserHostBase::NotifyMoveOrResizeStarted() {
if (platform_delegate_) {
platform_delegate_->NotifyMoveOrResizeStarted();
}
#else
LOG(WARNING)
<< "Incorrect usage of CefBrowserHost::NotifyMoveOrResizeStarted";
#endif
}
void CefBrowserHostBase::NotifyScreenInfoChanged() {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(
CEF_UIT,
base::BindOnce(&CefBrowserHostBase::NotifyScreenInfoChanged, this));
return;
}
if (platform_delegate_) {
if (IsWindowless() || platform_delegate_->HasExternalParent()) {
platform_delegate_->NotifyScreenInfoChanged();
} else {
LOG(WARNING)
<< "Incorrect usage of CefBrowserHost::NotifyScreenInfoChanged";
}
}
}
bool CefBrowserHostBase::IsFullscreen() {
if (!CEF_CURRENTLY_ON_UIT()) {
DCHECK(false) << "called on invalid thread";

View File

@ -265,6 +265,7 @@ class CefBrowserHostBase : public CefBrowserHost,
void SetAudioMuted(bool mute) override;
bool IsAudioMuted() override;
void NotifyMoveOrResizeStarted() override;
void NotifyScreenInfoChanged() override;
bool IsFullscreen() override;
void ExitFullscreen(bool will_cause_resize) override;
bool IsRenderProcessUnresponsive() override;

View File

@ -244,8 +244,7 @@ void CefBrowserPlatformDelegate::PopupBrowserCreated(
return;
}
CefRefPtr<CefBrowserView> new_browser_view =
CefBrowserView::GetForBrowser(new_browser);
auto new_browser_view = new_browser->GetBrowserView();
CHECK(new_browser_view);
bool popup_handled = false;
@ -389,9 +388,7 @@ bool CefBrowserPlatformDelegate::IsHidden() const {
return false;
}
void CefBrowserPlatformDelegate::NotifyScreenInfoChanged() {
DCHECK(false);
}
void CefBrowserPlatformDelegate::NotifyScreenInfoChanged() {}
void CefBrowserPlatformDelegate::Invalidate(cef_paint_element_type_t type) {
DCHECK(false);

View File

@ -306,7 +306,7 @@ class CefBrowserPlatformDelegate {
virtual bool IsHidden() const;
// Notify the browser that screen information has changed. Only used with
// windowless rendering.
// windowless rendering and external (client-provided) root window.
virtual void NotifyScreenInfoChanged();
// Invalidate the view. Only used with windowless rendering.

View File

@ -173,6 +173,12 @@ class BrowserDelegate : public content::WebContentsDelegate {
navigation_handle_callback) {
return true;
}
// Same as SetContentsBounds but returning false if unhandled.
virtual bool SetContentsBoundsEx(content::WebContents* source,
const gfx::Rect& bounds) {
return false;
}
};
} // namespace cef

View File

@ -573,6 +573,14 @@ bool ChromeBrowserDelegate::OpenURLFromTabEx(
return true;
}
bool ChromeBrowserDelegate::SetContentsBoundsEx(content::WebContents* source,
const gfx::Rect& bounds) {
if (auto delegate = GetDelegateForWebContents(source)) {
return delegate->SetContentsBoundsEx(source, bounds);
}
return false;
}
void ChromeBrowserDelegate::LoadingStateChanged(content::WebContents* source,
bool should_show_loading_ui) {
if (auto delegate = GetDelegateForWebContents(source)) {

View File

@ -94,6 +94,8 @@ class ChromeBrowserDelegate : public cef::BrowserDelegate {
const content::OpenURLParams& params,
base::OnceCallback<void(content::NavigationHandle&)>&
navigation_handle_callback) override;
bool SetContentsBoundsEx(content::WebContents* source,
const gfx::Rect& bounds) override;
// WebContentsDelegate methods:
void WebContentsCreated(content::WebContents* source_contents,

View File

@ -250,10 +250,6 @@ void ChromeBrowserHostImpl::WasHidden(bool hidden) {
NOTIMPLEMENTED();
}
void ChromeBrowserHostImpl::NotifyScreenInfoChanged() {
NOTIMPLEMENTED();
}
void ChromeBrowserHostImpl::Invalidate(PaintElementType type) {
NOTIMPLEMENTED();
}

View File

@ -84,7 +84,6 @@ class ChromeBrowserHostImpl : public CefBrowserHostBase {
bool IsWindowRenderingDisabled() override { return false; }
void WasResized() override;
void WasHidden(bool hidden) override;
void NotifyScreenInfoChanged() override;
void Invalidate(PaintElementType type) override;
void SendExternalBeginFrame() override;
void SendTouchEvent(const CefTouchEvent& event) override;

View File

@ -14,6 +14,10 @@
#include "ui/views/win/hwnd_util.h"
#endif
#if defined(USE_AURA)
#include "cef/libcef/browser/native/browser_platform_delegate_native_aura.h"
#endif
namespace {
gfx::AcceleratedWidget GetParentWidget(const CefWindowInfo& window_info) {
@ -61,6 +65,9 @@ class ChildWindowDelegate : public CefWindowDelegate {
void OnWindowDestroyed(CefRefPtr<CefWindow> window) override {
browser_view_ = nullptr;
window_ = nullptr;
#if BUILDFLAG(IS_WIN)
native_delegate_ = nullptr;
#endif
}
CefRect GetInitialBounds(CefRefPtr<CefWindow> window) override {
@ -71,22 +78,38 @@ class ChildWindowDelegate : public CefWindowDelegate {
return initial_bounds;
}
#if defined(USE_AURA)
void OnWindowBoundsChanged(CefRefPtr<CefWindow> window,
const CefRect& new_bounds) override {
if (native_delegate_) {
// Send new bounds to the renderer process and trigger the resize event.
native_delegate_->NotifyScreenInfoChanged();
}
}
#endif
private:
void ShowWindow() {
#if defined(USE_AURA)
auto browser = CefBrowserHostBase::FromBrowser(browser_view_->GetBrowser());
auto platform_delegate = browser->platform_delegate();
DCHECK(platform_delegate->IsViewsHosted());
auto chrome_delegate =
static_cast<CefBrowserPlatformDelegateChromeViews*>(platform_delegate);
native_delegate_ = static_cast<CefBrowserPlatformDelegateNativeAura*>(
chrome_delegate->native_delegate());
native_delegate_->InstallRootWindowBoundsCallback();
#if BUILDFLAG(IS_WIN)
auto widget = static_cast<CefWindowImpl*>(window_.get())->widget();
DCHECK(widget);
const HWND widget_hwnd = HWNDForWidget(widget);
DCHECK(widget_hwnd);
// The native delegate needs state to perform some actions.
auto browser = CefBrowserHostBase::FromBrowser(browser_view_->GetBrowser());
auto platform_delegate = browser->platform_delegate();
DCHECK(platform_delegate->IsViewsHosted());
auto chrome_delegate =
static_cast<CefBrowserPlatformDelegateChromeViews*>(platform_delegate);
auto native_delegate = static_cast<CefBrowserPlatformDelegateNativeWin*>(
chrome_delegate->native_delegate());
native_delegate->set_widget(widget, widget_hwnd);
// The Windows delegate needs state to perform some actions.
auto* delegate_win =
static_cast<CefBrowserPlatformDelegateNativeWin*>(native_delegate_);
delegate_win->set_widget(widget, widget_hwnd);
if (window_info_.ex_style & WS_EX_NOACTIVATE) {
const DWORD widget_ex_styles = GetWindowLongPtr(widget_hwnd, GWL_EXSTYLE);
@ -105,6 +128,7 @@ class ChildWindowDelegate : public CefWindowDelegate {
return;
}
#endif // BUILDFLAG(IS_WIN)
#endif // defined(USE_AURA)
window_->Show();
@ -112,7 +136,6 @@ class ChildWindowDelegate : public CefWindowDelegate {
browser_view_->RequestFocus();
}
private:
ChildWindowDelegate(CefRefPtr<CefBrowserView> browser_view,
const CefWindowInfo& window_info)
: browser_view_(browser_view), window_info_(window_info) {}
@ -122,6 +145,11 @@ class ChildWindowDelegate : public CefWindowDelegate {
CefRefPtr<CefWindow> window_;
#if defined(USE_AURA)
base::raw_ptr<CefBrowserPlatformDelegateNativeAura> native_delegate_ =
nullptr;
#endif
IMPLEMENT_REFCOUNTING(ChildWindowDelegate);
};

View File

@ -5,6 +5,7 @@
#include "cef/libcef/browser/native/browser_platform_delegate_native.h"
#include "cef/libcef/browser/alloy/alloy_browser_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
@ -24,3 +25,25 @@ void CefBrowserPlatformDelegateNative::WasResized() {
host->GetWidget()->SynchronizeVisualProperties();
}
}
void CefBrowserPlatformDelegateNative::NotifyScreenInfoChanged() {
content::RenderWidgetHostImpl* render_widget_host = nullptr;
if (web_contents_) {
if (auto* rvh = web_contents_->GetRenderViewHost()) {
render_widget_host =
content::RenderWidgetHostImpl::From(rvh->GetWidget());
}
}
if (!render_widget_host) {
return;
}
// Send updated screen bounds information to the renderer process.
if (render_widget_host->delegate()) {
render_widget_host->delegate()->SendScreenRects();
} else {
render_widget_host->SendScreenRects();
}
render_widget_host->NotifyScreenInfoChanged();
}

View File

@ -32,6 +32,7 @@ class CefBrowserPlatformDelegateNative
// CefBrowserPlatformDelegate methods:
SkColor GetBackgroundColor() const override;
void WasResized() override;
void NotifyScreenInfoChanged() override;
// Translate CEF events to Chromium/Blink Web events.
virtual input::NativeWebKeyboardEvent TranslateWebKeyEvent(

View File

@ -4,6 +4,7 @@
#include "cef/libcef/browser/native/browser_platform_delegate_native_aura.h"
#include "cef/libcef/browser/browser_host_base.h"
#include "cef/libcef/browser/native/menu_runner_views_aura.h"
#include "cef/libcef/browser/views/view_util.h"
#include "content/browser/renderer_host/render_widget_host_view_aura.h"
@ -19,6 +20,42 @@ CefBrowserPlatformDelegateNativeAura::CefBrowserPlatformDelegateNativeAura(
SkColor background_color)
: CefBrowserPlatformDelegateNative(window_info, background_color) {}
void CefBrowserPlatformDelegateNativeAura::InstallRootWindowBoundsCallback() {
auto* host_view = GetHostView();
CHECK(host_view);
host_view->SetRootWindowBoundsCallback(base::BindRepeating(
[](base::WeakPtr<CefBrowserPlatformDelegateNativeAura> self) {
return self->RootWindowBoundsCallback();
},
weak_ptr_factory_.GetWeakPtr()));
}
std::optional<gfx::Rect>
CefBrowserPlatformDelegateNativeAura::RootWindowBoundsCallback() {
if (browser_) {
if (auto client = browser_->client()) {
if (auto handler = client->GetDisplayHandler()) {
CefRect rect;
if (handler->GetRootWindowScreenRect(browser_.get(), rect) &&
!rect.IsEmpty()) {
return gfx::Rect(rect.x, rect.y, rect.width, rect.height);
}
}
}
}
// Call the default platform implementation, if any.
return GetRootWindowBounds();
}
void CefBrowserPlatformDelegateNativeAura::RenderViewReady() {
CefBrowserPlatformDelegateNative::RenderViewReady();
// The RWHV should now exist for Alloy style browsers.
InstallRootWindowBoundsCallback();
}
void CefBrowserPlatformDelegateNativeAura::SendKeyEvent(
const CefKeyEvent& event) {
auto view = GetHostView();

View File

@ -5,19 +5,19 @@
#ifndef CEF_LIBCEF_BROWSER_NATIVE_BROWSER_PLATFORM_DELEGATE_NATIVE_AURA_H_
#define CEF_LIBCEF_BROWSER_NATIVE_BROWSER_PLATFORM_DELEGATE_NATIVE_AURA_H_
#include <optional>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "cef/libcef/browser/native/browser_platform_delegate_native.h"
#include "ui/events/event.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
namespace content {
class RenderWidgetHostViewAura;
}
namespace gfx {
class Vector2d;
}
// Windowed browser implementation for Aura platforms.
class CefBrowserPlatformDelegateNativeAura
: public CefBrowserPlatformDelegateNative {
@ -25,7 +25,10 @@ class CefBrowserPlatformDelegateNativeAura
CefBrowserPlatformDelegateNativeAura(const CefWindowInfo& window_info,
SkColor background_color);
void InstallRootWindowBoundsCallback();
// CefBrowserPlatformDelegate methods:
void RenderViewReady() override;
void SendKeyEvent(const CefKeyEvent& event) override;
void SendMouseClickEvent(const CefMouseEvent& event,
CefBrowserHost::MouseButtonType type,
@ -71,9 +74,16 @@ class CefBrowserPlatformDelegateNativeAura
int deltaY) const;
virtual gfx::Vector2d GetUiWheelEventOffset(int deltaX, int deltaY) const;
// Returns the root window bounds in screen DIP coordinates.
virtual std::optional<gfx::Rect> GetRootWindowBounds() {
return std::nullopt;
}
protected:
base::OnceClosure GetWidgetDeleteCallback();
std::optional<gfx::Rect> RootWindowBoundsCallback();
static base::TimeTicks GetEventTimeStamp();
static int TranslateUiEventModifiers(uint32_t cef_modifiers);
static int TranslateUiChangedButtonFlags(uint32_t cef_modifiers);

View File

@ -427,6 +427,27 @@ CefEventHandle CefBrowserPlatformDelegateNativeWin::GetEventHandle(
const_cast<CHROME_MSG*>(&event.os_event->native_event()));
}
std::optional<gfx::Rect>
CefBrowserPlatformDelegateNativeWin::GetRootWindowBounds() {
if (window_widget_) {
if (HWND hwnd = GetHostWindowHandle()) {
if (HWND root_hwnd = ::GetAncestor(hwnd, GA_ROOT)) {
RECT root_rect = {};
if (::GetWindowRect(root_hwnd, &root_rect)) {
auto* top_level =
window_widget_->GetNativeWindow()->GetToplevelWindow();
gfx::Rect bounds(root_rect);
bounds = display::Screen::GetScreen()->ScreenToDIPRectInWindow(
top_level, bounds);
return bounds;
}
}
}
}
return std::nullopt;
}
ui::KeyEvent CefBrowserPlatformDelegateNativeWin::TranslateUiKeyEvent(
const CefKeyEvent& key_event) const {
int flags = TranslateUiEventModifiers(key_event.modifiers);
@ -596,8 +617,8 @@ LRESULT CALLBACK CefBrowserPlatformDelegateNativeWin::WndProc(HWND hwnd,
case WM_MOVING:
case WM_MOVE:
if (browser) {
browser->NotifyMoveOrResizeStarted();
if (platform_delegate) {
platform_delegate->NotifyMoveOrResizeStarted();
}
return 0;

View File

@ -32,6 +32,7 @@ class CefBrowserPlatformDelegateNativeWin
bool HandleKeyboardEvent(const input::NativeWebKeyboardEvent& event) override;
CefEventHandle GetEventHandle(
const input::NativeWebKeyboardEvent& event) const override;
std::optional<gfx::Rect> GetRootWindowBounds() override;
// CefBrowserPlatformDelegateNativeAura methods:
ui::KeyEvent TranslateUiKeyEvent(const CefKeyEvent& key_event) const override;

View File

@ -393,7 +393,10 @@ patches = [
#
# Windows: Fix crash during window creation.
# https://bugs.chromium.org/p/chromium/issues/detail?id=761389
'name': 'rwh_background_color_1984',
#
# Add RWHVAura::SetRootWindowBoundsCallback.
# https://github.com/chromiumembedded/cef/issues/3920
'name': 'renderer_host_aura',
},
{
# Expose RFH via NavigationHandle for retrieval in DidFinishNavigation on

View File

@ -136,7 +136,7 @@ index 51c55cb054ae7..df9aa9bc45a67 100644
]
}
diff --git chrome/browser/ui/browser.cc chrome/browser/ui/browser.cc
index d45f8f1713c87..7d69f3d8bd4cc 100644
index d45f8f1713c87..de476f1ed4081 100644
--- chrome/browser/ui/browser.cc
+++ chrome/browser/ui/browser.cc
@@ -272,6 +272,25 @@
@ -285,7 +285,21 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
}
void Browser::CloseContents(WebContents* source) {
@@ -2177,6 +2246,8 @@ void Browser::SetContentsBounds(WebContents* source, const gfx::Rect& bounds) {
@@ -2157,6 +2226,13 @@ void Browser::CloseContents(WebContents* source) {
}
void Browser::SetContentsBounds(WebContents* source, const gfx::Rect& bounds) {
+#if BUILDFLAG(ENABLE_CEF)
+ if (cef_browser_delegate_ &&
+ cef_browser_delegate_->SetContentsBoundsEx(source, bounds)) {
+ return;
+ }
+#endif
+
if (is_type_normal()) {
return;
}
@@ -2177,6 +2253,8 @@ void Browser::SetContentsBounds(WebContents* source, const gfx::Rect& bounds) {
}
void Browser::UpdateTargetURL(WebContents* source, const GURL& url) {
@ -294,7 +308,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
std::vector<StatusBubble*> status_bubbles = GetStatusBubbles();
for (StatusBubble* status_bubble : status_bubbles) {
StatusBubbleViews* status_bubble_views =
@@ -2190,6 +2261,17 @@ void Browser::UpdateTargetURL(WebContents* source, const GURL& url) {
@@ -2190,6 +2268,17 @@ void Browser::UpdateTargetURL(WebContents* source, const GURL& url) {
}
}
@ -312,7 +326,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
void Browser::ContentsMouseEvent(WebContents* source, const ui::Event& event) {
const ui::EventType type = event.type();
const bool exited = type == ui::EventType::kMouseExited;
@@ -2223,9 +2305,23 @@ void Browser::ContentsZoomChange(bool zoom_in) {
@@ -2223,9 +2312,23 @@ void Browser::ContentsZoomChange(bool zoom_in) {
}
bool Browser::TakeFocus(content::WebContents* source, bool reverse) {
@ -336,7 +350,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
void Browser::BeforeUnloadFired(WebContents* web_contents,
bool proceed,
bool* proceed_to_fire_unload) {
@@ -2338,12 +2434,24 @@ void Browser::WebContentsCreated(WebContents* source_contents,
@@ -2338,12 +2441,24 @@ void Browser::WebContentsCreated(WebContents* source_contents,
// to track `new_contents` after it is added to its TabModel this override can
// be removed.
CreateSessionServiceTabHelper(new_contents);
@ -361,7 +375,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
// Don't show the page hung dialog when a HTML popup hangs because
// the dialog will take the focus and immediately close the popup.
RenderWidgetHostView* view = render_widget_host->GetView();
@@ -2356,6 +2464,13 @@ void Browser::RendererUnresponsive(
@@ -2356,6 +2471,13 @@ void Browser::RendererUnresponsive(
void Browser::RendererResponsive(
WebContents* source,
content::RenderWidgetHost* render_widget_host) {
@ -375,7 +389,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
RenderWidgetHostView* view = render_widget_host->GetView();
if (view && !render_widget_host->GetView()->IsHTMLFormPopup()) {
TabDialogs::FromWebContents(source)->HideHungRendererDialog(
@@ -2365,6 +2480,15 @@ void Browser::RendererResponsive(
@@ -2365,6 +2487,15 @@ void Browser::RendererResponsive(
content::JavaScriptDialogManager* Browser::GetJavaScriptDialogManager(
WebContents* source) {
@ -391,7 +405,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
return javascript_dialogs::TabModalDialogManager::FromWebContents(source);
}
@@ -2400,6 +2524,11 @@ void Browser::DraggableRegionsChanged(
@@ -2400,6 +2531,11 @@ void Browser::DraggableRegionsChanged(
if (app_controller_) {
app_controller_->DraggableRegionsChanged(regions, contents);
}
@ -403,7 +417,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
}
std::vector<blink::mojom::RelatedApplicationPtr>
@@ -2514,11 +2643,15 @@ void Browser::EnterFullscreenModeForTab(
@@ -2514,11 +2650,15 @@ void Browser::EnterFullscreenModeForTab(
const blink::mojom::FullscreenOptions& options) {
exclusive_access_manager_->fullscreen_controller()->EnterFullscreenModeForTab(
requesting_frame, options.display_id);
@ -419,7 +433,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
}
bool Browser::IsFullscreenForTabOrPending(const WebContents* web_contents) {
@@ -2728,6 +2861,16 @@ void Browser::RequestMediaAccessPermission(
@@ -2728,6 +2868,16 @@ void Browser::RequestMediaAccessPermission(
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback) {
@ -436,7 +450,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
const extensions::Extension* extension =
GetExtensionForOrigin(profile_, request.security_origin);
MediaCaptureDevicesDispatcher::GetInstance()->ProcessMediaAccessRequest(
@@ -3313,9 +3456,11 @@ void Browser::RemoveScheduledUpdatesFor(WebContents* contents) {
@@ -3313,9 +3463,11 @@ void Browser::RemoveScheduledUpdatesFor(WebContents* contents) {
// Browser, Getters for UI (private):
std::vector<StatusBubble*> Browser::GetStatusBubbles() {
@ -449,7 +463,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
}
// We hide the status bar for web apps windows as this matches native
@@ -3323,6 +3468,12 @@ std::vector<StatusBubble*> Browser::GetStatusBubbles() {
@@ -3323,6 +3475,12 @@ std::vector<StatusBubble*> Browser::GetStatusBubbles() {
// mode, as the minimal browser UI includes the status bar.
if (web_app::AppBrowserController::IsWebApp(this) &&
!app_controller()->HasMinimalUiButtons()) {
@ -462,7 +476,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
return {};
}
@@ -3476,6 +3627,8 @@ void Browser::SetAsDelegate(WebContents* web_contents, bool set_delegate) {
@@ -3476,6 +3634,8 @@ void Browser::SetAsDelegate(WebContents* web_contents, bool set_delegate) {
BookmarkTabHelper::FromWebContents(web_contents)->RemoveObserver(this);
web_contents_collection_.StopObserving(web_contents);
}
@ -471,7 +485,7 @@ index d45f8f1713c87..7d69f3d8bd4cc 100644
}
void Browser::TabDetachedAtImpl(content::WebContents* contents,
@@ -3637,6 +3790,14 @@ bool Browser::PictureInPictureBrowserSupportsWindowFeature(
@@ -3637,6 +3797,14 @@ bool Browser::PictureInPictureBrowserSupportsWindowFeature(
bool Browser::SupportsWindowFeatureImpl(WindowFeature feature,
bool check_can_support) const {

View File

@ -1,15 +1,15 @@
diff --git content/browser/renderer_host/render_widget_host_view_aura.cc content/browser/renderer_host/render_widget_host_view_aura.cc
index 5867fc3e77326..54ac130dea600 100644
index 5867fc3e77326..23656289851bc 100644
--- content/browser/renderer_host/render_widget_host_view_aura.cc
+++ content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -6,6 +6,7 @@
#include <limits>
@@ -8,6 +8,7 @@
#include <memory>
+#include <tuple>
#include <set>
#include <string_view>
+#include <tuple>
#include <utility>
#include "base/auto_reset.h"
@@ -52,6 +53,7 @@
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/device_service.h"
@ -28,7 +28,20 @@ index 5867fc3e77326..54ac130dea600 100644
SkColor color = *GetBackgroundColor();
window_->layer()->SetColor(color);
}
@@ -2664,6 +2669,16 @@ void RenderWidgetHostViewAura::CreateAuraWindow(aura::client::WindowType type) {
@@ -1102,6 +1107,12 @@ void RenderWidgetHostViewAura::TransformPointToRootSurface(gfx::PointF* point) {
}
gfx::Rect RenderWidgetHostViewAura::GetBoundsInRootWindow() {
+ if (!root_window_bounds_callback_.is_null()) {
+ if (auto bounds = root_window_bounds_callback_.Run()) {
+ return *bounds;
+ }
+ }
+
aura::Window* top_level = window_->GetToplevelWindow();
gfx::Rect bounds(top_level->GetBoundsInScreen());
@@ -2664,6 +2675,16 @@ void RenderWidgetHostViewAura::CreateAuraWindow(aura::client::WindowType type) {
window_->layer()->SetColor(GetBackgroundColor() ? *GetBackgroundColor()
: SK_ColorWHITE);
UpdateFrameSinkIdRegistration();
@ -45,3 +58,29 @@ index 5867fc3e77326..54ac130dea600 100644
}
void RenderWidgetHostViewAura::UpdateFrameSinkIdRegistration() {
diff --git content/browser/renderer_host/render_widget_host_view_aura.h content/browser/renderer_host/render_widget_host_view_aura.h
index 6f96b83c36ee0..52cc4b37f0bbe 100644
--- content/browser/renderer_host/render_widget_host_view_aura.h
+++ content/browser/renderer_host/render_widget_host_view_aura.h
@@ -451,6 +451,12 @@ class CONTENT_EXPORT RenderWidgetHostViewAura
}
#endif // BUILDFLAG(IS_WIN)
+ using RootWindowBoundsCallback =
+ base::RepeatingCallback<std::optional<gfx::Rect>()>;
+ void SetRootWindowBoundsCallback(const RootWindowBoundsCallback& callback) {
+ root_window_bounds_callback_ = callback;
+ }
+
protected:
~RenderWidgetHostViewAura() override;
@@ -874,6 +880,8 @@ class CONTENT_EXPORT RenderWidgetHostViewAura
std::optional<display::ScopedDisplayObserver> display_observer_;
+ RootWindowBoundsCallback root_window_bounds_callback_;
+
base::WeakPtrFactory<RenderWidgetHostViewAura> weak_ptr_factory_{this};
};

View File

@ -83,6 +83,16 @@ void BrowserWindow::OnAutoResize(const CefSize& new_size) {
delegate_->OnAutoResize(new_size);
}
void BrowserWindow::OnContentsBounds(const CefRect& new_bounds) {
REQUIRE_MAIN_THREAD();
delegate_->OnContentsBounds(new_bounds);
}
bool BrowserWindow::GetRootWindowScreenRect(CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
return delegate_->GetRootWindowScreenRect(rect);
}
void BrowserWindow::OnSetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) {

View File

@ -20,7 +20,8 @@ namespace client {
class BrowserWindow : public ClientHandler::Delegate {
public:
// This interface is implemented by the owner of the BrowserWindow. The
// methods of this class will be called on the main thread.
// methods of this class will be called on the main thread unless otherwise
// indicated.
class Delegate {
public:
// Returns true if the window should use Alloy style. Safe to call on any
@ -48,6 +49,9 @@ class BrowserWindow : public ClientHandler::Delegate {
// Auto-resize contents.
virtual void OnAutoResize(const CefSize& new_size) = 0;
// Set contents bounds.
virtual void OnContentsBounds(const CefRect& new_bounds) = 0;
// Set the loading state.
virtual void OnSetLoadingState(bool isLoading,
bool canGoBack,
@ -57,6 +61,9 @@ class BrowserWindow : public ClientHandler::Delegate {
virtual void OnSetDraggableRegions(
const std::vector<CefDraggableRegion>& regions) = 0;
// Called on the UI thread to retrieve root window bounds.
virtual bool GetRootWindowScreenRect(CefRect& rect) { return false; }
protected:
virtual ~Delegate() = default;
};
@ -132,13 +139,15 @@ class BrowserWindow : public ClientHandler::Delegate {
void OnSetTitle(const std::string& title) override;
void OnSetFullscreen(bool fullscreen) override;
void OnAutoResize(const CefSize& new_size) override;
void OnContentsBounds(const CefRect& new_bounds) override;
bool GetRootWindowScreenRect(CefRect& rect) override;
void OnSetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) override;
void OnSetDraggableRegions(
const std::vector<CefDraggableRegion>& regions) override;
Delegate* delegate_;
Delegate* const delegate_;
CefRefPtr<CefBrowser> browser_;
CefRefPtr<ClientHandler> client_handler_;
bool is_closing_ = false;

View File

@ -20,6 +20,7 @@
#include "include/base/cef_logging.h"
#include "include/base/cef_macros.h"
#include "include/views/cef_display.h"
#include "include/wrapper/cef_closure_task.h"
#include "tests/cefclient/browser/util_gtk.h"
#include "tests/shared/browser/geometry_util.h"
@ -1092,7 +1093,7 @@ void BrowserWindowOsrGtk::SetDeviceScaleFactor(float device_scale_factor) {
}
// Apply some sanity checks.
if (device_scale_factor < 1.0f || device_scale_factor > 4.0f) {
if (device_scale_factor < 0.5f || device_scale_factor > 4.0f) {
return;
}
@ -1142,15 +1143,36 @@ void BrowserWindowOsrGtk::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
bool BrowserWindowOsrGtk::GetRootScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
if (!renderer_.settings().real_screen_bounds) {
return false;
}
if (!glarea_) {
return false;
}
float device_scale_factor;
{
base::AutoLock lock_scope(lock_);
device_scale_factor = device_scale_factor_;
}
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWidget* toplevel = gtk_widget_get_toplevel(glarea_);
// Convert to DIP coordinates.
rect = DeviceToLogical(
GetWindowBounds(GTK_WINDOW(toplevel), /*include_frame=*/true),
device_scale_factor);
return true;
}
void BrowserWindowOsrGtk::GetViewRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
rect.x = rect.y = 0;
if (!glarea_) {
// Never return an empty rectangle.
rect.width = rect.height = 1;
@ -1163,18 +1185,24 @@ void BrowserWindowOsrGtk::GetViewRect(CefRefPtr<CefBrowser> browser,
device_scale_factor = device_scale_factor_;
}
// The simulated screen and view rectangle are the same. This is necessary
// for popup menus to be located and sized inside the view.
GtkAllocation allocation;
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkAllocation allocation = {};
gtk_widget_get_allocation(glarea_, &allocation);
rect.width = DeviceToLogical(allocation.width, device_scale_factor);
// Convert to DIP coordinates.
rect = DeviceToLogical(
{allocation.x, allocation.y, allocation.width, allocation.height},
device_scale_factor);
if (rect.width == 0) {
rect.width = 1;
}
rect.height = DeviceToLogical(allocation.height, device_scale_factor);
if (rect.height == 0) {
rect.height = 1;
}
if (!renderer_.settings().real_screen_bounds) {
rect.x = rect.y = 0;
}
}
bool BrowserWindowOsrGtk::GetScreenPoint(CefRefPtr<CefBrowser> browser,
@ -1204,9 +1232,6 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr<CefBrowser> browser,
CefScreenInfo& screen_info) {
CEF_REQUIRE_UI_THREAD();
CefRect view_rect;
GetViewRect(browser, view_rect);
float device_scale_factor;
{
base::AutoLock lock_scope(lock_);
@ -1215,10 +1240,23 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr<CefBrowser> browser,
screen_info.device_scale_factor = device_scale_factor;
// The screen info rectangles are used by the renderer to create and position
// popups. Keep popups inside the view rectangle.
if (renderer_.settings().real_screen_bounds) {
CefRect root_rect;
GetRootScreenRect(browser, root_rect);
auto display = CefDisplay::GetDisplayMatchingBounds(
root_rect, /*input_pixel_coords=*/false);
screen_info.rect = display->GetBounds();
screen_info.available_rect = display->GetWorkArea();
} else {
CefRect view_rect;
GetViewRect(browser, view_rect);
// Keep HTML select popups inside the view rectangle.
screen_info.rect = view_rect;
screen_info.available_rect = view_rect;
}
return true;
}

View File

@ -9,14 +9,18 @@
#include <OpenGL/gl.h>
#import <objc/runtime.h>
#include <optional>
#include "include/base/cef_logging.h"
#include "include/cef_parser.h"
#include "include/views/cef_display.h"
#include "include/wrapper/cef_closure_task.h"
#include "tests/cefclient/browser/bytes_write_handler.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
#include "tests/cefclient/browser/text_input_client_osr_mac.h"
#include "tests/cefclient/browser/util_mac.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
@ -784,7 +788,7 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
- (void)windowDidChangeBackingProperties:(NSNotification*)notification {
// This delegate method is only called on 10.7 and later, so don't worry about
// other backing changes calling it on 10.6 or earlier
[self resetDeviceScaleFactor];
[self resetDeviceScaleFactor:std::nullopt];
}
- (void)drawRect:(NSRect)dirtyRect {
@ -1210,12 +1214,14 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
return viewPoint;
}
- (void)resetDeviceScaleFactor {
float device_scale_factor = 1.0f;
- (void)resetDeviceScaleFactor:(std::optional<float>)requested_scale_factor {
float device_scale_factor = requested_scale_factor.value_or(1.0f);
if (!requested_scale_factor.has_value()) {
NSWindow* window = [self window];
if (window) {
device_scale_factor = [window backingScaleFactor];
}
}
[self setDeviceScaleFactor:device_scale_factor];
}
@ -1225,7 +1231,7 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) {
}
// Apply some sanity checks.
if (device_scale_factor < 1.0f || device_scale_factor > 4.0f) {
if (device_scale_factor < 0.5f || device_scale_factor > 4.0f) {
return;
}
@ -1417,6 +1423,7 @@ class BrowserWindowOsrMacImpl {
// The below members will only be accessed on the main thread which should be
// the same as the CEF UI thread.
OsrRenderer renderer_;
std::optional<float> initial_scale_factor_;
BrowserOpenGLView* native_browser_view_;
bool hidden_;
bool painting_popup_;
@ -1542,7 +1549,7 @@ void BrowserWindowOsrMacImpl::SetBounds(int x,
size_t width,
size_t height) {
REQUIRE_MAIN_THREAD();
// Nothing to do here. GTK will take care of positioning in the container.
// Nothing to do here. Cocoa will take care of positioning in the container.
}
void BrowserWindowOsrMacImpl::SetFocus(bool focus) {
@ -1556,6 +1563,8 @@ void BrowserWindowOsrMacImpl::SetDeviceScaleFactor(float device_scale_factor) {
REQUIRE_MAIN_THREAD();
if (native_browser_view_) {
[native_browser_view_ setDeviceScaleFactor:device_scale_factor];
} else {
initial_scale_factor_ = device_scale_factor;
}
}
@ -1564,7 +1573,7 @@ float BrowserWindowOsrMacImpl::GetDeviceScaleFactor() const {
if (native_browser_view_) {
return [native_browser_view_ getDeviceScaleFactor];
}
return 1.0f;
return initial_scale_factor_.value_or(1.0f);
}
ClientWindowHandle BrowserWindowOsrMacImpl::GetWindowHandle() const {
@ -1590,14 +1599,33 @@ void BrowserWindowOsrMacImpl::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
bool BrowserWindowOsrMacImpl::GetRootScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
if (!renderer_.settings().real_screen_bounds) {
return false;
}
if (!native_browser_view_) {
return false;
}
if (auto screen_rect =
GetWindowBoundsInScreen([native_browser_view_ window])) {
rect = *screen_rect;
}
if (rect.width == 0) {
rect.width = 1;
}
if (rect.height == 0) {
rect.height = 1;
}
return true;
}
void BrowserWindowOsrMacImpl::GetViewRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
// Keep (0,0) origin for proper layout on macOS.
rect.x = rect.y = 0;
if (!native_browser_view_) {
@ -1614,12 +1642,12 @@ void BrowserWindowOsrMacImpl::GetViewRect(CefRefPtr<CefBrowser> browser,
// Convert to device coordinates.
bounds = [native_browser_view_ convertRectToBackingInternal:bounds];
// Convert to browser view coordinates.
// Convert to DIP coordinates.
rect.width = DeviceToLogical(bounds.size.width, device_scale_factor);
rect.height = DeviceToLogical(bounds.size.height, device_scale_factor);
if (rect.width == 0) {
rect.width = 1;
}
rect.height = DeviceToLogical(bounds.size.height, device_scale_factor);
if (rect.height == 0) {
rect.height = 1;
}
@ -1639,7 +1667,7 @@ bool BrowserWindowOsrMacImpl::GetScreenPoint(CefRefPtr<CefBrowser> browser,
const float device_scale_factor = [native_browser_view_ getDeviceScaleFactor];
// (viewX, viewX) is in browser view coordinates.
// (viewX, viewX) is in browser DIP coordinates.
// Convert to device coordinates.
NSPoint view_pt = NSMakePoint(LogicalToDevice(viewX, device_scale_factor),
LogicalToDevice(viewY, device_scale_factor));
@ -1670,16 +1698,24 @@ bool BrowserWindowOsrMacImpl::GetScreenInfo(CefRefPtr<CefBrowser> browser,
return false;
}
screen_info.device_scale_factor = [native_browser_view_ getDeviceScaleFactor];
if (renderer_.settings().real_screen_bounds) {
CefRect root_rect;
GetRootScreenRect(browser, root_rect);
auto display = CefDisplay::GetDisplayMatchingBounds(
root_rect, /*input_pixel_coords=*/false);
screen_info.rect = display->GetBounds();
screen_info.available_rect = display->GetWorkArea();
} else {
CefRect view_rect;
GetViewRect(browser, view_rect);
screen_info.device_scale_factor = [native_browser_view_ getDeviceScaleFactor];
// The screen info rectangles are used by the renderer to create and position
// popups. Keep popups inside the view rectangle.
// Keep HTML select popups inside the view rectangle.
screen_info.rect = view_rect;
screen_info.available_rect = view_rect;
}
return true;
}
@ -1892,7 +1928,7 @@ void BrowserWindowOsrMacImpl::Create(ClientWindowHandle parent_handle,
addSubview:native_browser_view_];
// Determine the default scale factor.
[native_browser_view_ resetDeviceScaleFactor];
[native_browser_view_ resetDeviceScaleFactor:initial_scale_factor_];
[[NSNotificationCenter defaultCenter]
addObserver:native_browser_view_

View File

@ -103,7 +103,7 @@ void BrowserWindowOsrWin::SetDeviceScaleFactor(float device_scale_factor) {
}
// Apply some sanity checks.
if (device_scale_factor < 1.0f || device_scale_factor > 4.0f) {
if (device_scale_factor < 0.5f || device_scale_factor > 4.0f) {
return;
}

View File

@ -571,12 +571,7 @@ ClientHandler::ClientHandler(Delegate* delegate,
}
void ClientHandler::DetachDelegate() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
// Execute this method on the main thread.
MAIN_POST_CLOSURE(base::BindOnce(&ClientHandler::DetachDelegate, this));
return;
}
REQUIRE_MAIN_THREAD();
DCHECK(delegate_);
delegate_ = nullptr;
}
@ -869,6 +864,24 @@ bool ClientHandler::OnCursorChange(CefRefPtr<CefBrowser> browser,
return mouse_cursor_change_disabled_;
}
#if CEF_API_ADDED(CEF_NEXT)
bool ClientHandler::OnContentsBoundsChange(CefRefPtr<CefBrowser> browser,
const CefRect& new_bounds) {
CEF_REQUIRE_UI_THREAD();
NotifyContentsBounds(new_bounds);
return true;
}
bool ClientHandler::GetRootWindowScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
if (delegate_) {
return delegate_->GetRootWindowScreenRect(rect);
}
return false;
}
#endif
bool ClientHandler::CanDownload(CefRefPtr<CefBrowser> browser,
const CefString& url,
const CefString& request_method) {
@ -1465,6 +1478,19 @@ void ClientHandler::NotifyAutoResize(const CefSize& new_size) {
}
}
void ClientHandler::NotifyContentsBounds(const CefRect& new_bounds) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
// Execute this method on the main thread.
MAIN_POST_CLOSURE(
base::BindOnce(&ClientHandler::NotifyContentsBounds, this, new_bounds));
return;
}
if (delegate_) {
delegate_->OnContentsBounds(new_bounds);
}
}
void ClientHandler::NotifyLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) {

View File

@ -71,6 +71,9 @@ class ClientHandler : public BaseClientHandler,
// Auto-resize contents.
virtual void OnAutoResize(const CefSize& new_size) = 0;
// Set contents bounds.
virtual void OnContentsBounds(const CefRect& new_bounds) = 0;
// Set the loading state.
virtual void OnSetLoadingState(bool isLoading,
bool canGoBack,
@ -89,6 +92,9 @@ class ClientHandler : public BaseClientHandler,
// Called on the UI thread before a context menu is displayed.
virtual void OnBeforeContextMenu(CefRefPtr<CefMenuModel> model) {}
// Called on the UI thread to retrieve root window bounds.
virtual bool GetRootWindowScreenRect(CefRect& rect) { return false; }
protected:
virtual ~Delegate() = default;
};
@ -101,7 +107,8 @@ class ClientHandler : public BaseClientHandler,
const std::string& startup_url);
// This object may outlive the Delegate object so it's necessary for the
// Delegate to detach itself before destruction.
// Delegate to detach itself before destruction. Called on the main thread
// after the browser has closed.
void DetachDelegate();
// CefClient methods
@ -176,6 +183,12 @@ class ClientHandler : public BaseClientHandler,
CefCursorHandle cursor,
cef_cursor_type_t type,
const CefCursorInfo& custom_cursor_info) override;
#if CEF_API_ADDED(CEF_NEXT)
bool OnContentsBoundsChange(CefRefPtr<CefBrowser> browser,
const CefRect& new_bounds) override;
bool GetRootWindowScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) override;
#endif
// CefDownloadHandler methods
bool CanDownload(CefRefPtr<CefBrowser> browser,
@ -345,6 +358,7 @@ class ClientHandler : public BaseClientHandler,
void NotifyFavicon(CefRefPtr<CefImage> image);
void NotifyFullscreen(bool fullscreen);
void NotifyAutoResize(const CefSize& new_size);
void NotifyContentsBounds(const CefRect& new_bounds);
void NotifyLoadingState(bool isLoading, bool canGoBack, bool canGoForward);
void NotifyDraggableRegions(const std::vector<CefDraggableRegion>& regions);
void NotifyTakeFocus(bool next);
@ -396,11 +410,7 @@ class ClientHandler : public BaseClientHandler,
CefRefPtr<ClientPrintHandlerGtk> print_handler_;
#endif
// MAIN THREAD MEMBERS
// The following members will only be accessed on the main thread. This will
// be the same as the CEF UI thread except when using multi-threaded message
// loop mode on Windows.
// Safe to access from any thread during browser lifetime.
Delegate* delegate_;
// UI THREAD MEMBERS

View File

@ -239,6 +239,8 @@ void MainContextImpl::PopulateBrowserSettings(CefBrowserSettings* settings) {
void MainContextImpl::PopulateOsrSettings(OsrRendererSettings* settings) {
settings->show_update_rect =
command_line_->HasSwitch(switches::kShowUpdateRect);
settings->real_screen_bounds =
!command_line_->HasSwitch(switches::kFakeScreenBounds);
settings->shared_texture_enabled = shared_texture_enabled_;
settings->external_begin_frame_enabled = external_begin_frame_enabled_;

View File

@ -16,6 +16,14 @@ struct OsrRendererSettings {
// If true draw a border around update rectangles.
bool show_update_rect = false;
// If true return real screen bounds from GetRootScreenRect/GetScreenInfo.
// - Allows window.outerWidth/Height and window.screenX/Y to return correct
// values.
// - Allows JavaScript window.moveTo/By() and window.resizeTo/By() to provide
// bounds that include the window frame.
// - Causes HTML select popups to be cropped (limitation of cefclient impl).
bool real_screen_bounds = true;
// Background color. Enables transparency if the alpha component is 0.
cef_color_t background_color = 0;

View File

@ -13,6 +13,7 @@
#include <memory>
#include "include/base/cef_build.h"
#include "include/views/cef_display.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_accessibility_helper.h"
#include "tests/cefclient/browser/osr_accessibility_node.h"
@ -30,20 +31,6 @@ namespace {
const wchar_t kWndClass[] = L"Client_OsrWindow";
// Helper funtion to check if it is Windows8 or greater.
// https://msdn.microsoft.com/en-us/library/ms724833(v=vs.85).aspx
inline BOOL IsWindows_8_Or_Newer() {
OSVERSIONINFOEX osvi = {0};
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 2;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
return ::VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION,
dwlConditionMask);
}
// Helper function to detect mouse messages coming from emulation of touch
// events. These should be ignored.
bool IsMouseEventFromTouch(UINT message) {
@ -92,7 +79,7 @@ OsrWindowWin::OsrWindowWin(Delegate* delegate,
last_mouse_pos_(),
current_mouse_pos_() {
DCHECK(delegate_);
client_rect_ = {0};
client_rect_ = {};
}
OsrWindowWin::~OsrWindowWin() {
@ -870,17 +857,6 @@ bool OsrWindowWin::OnTouchEvent(UINT message, WPARAM wParam, LPARAM lParam) {
point.x = TOUCH_COORD_TO_PIXEL(input[i].x);
point.y = TOUCH_COORD_TO_PIXEL(input[i].y);
if (!IsWindows_8_Or_Newer()) {
// Windows 7 sends touch events for touches in the non-client area,
// whereas Windows 8 does not. In order to unify the behaviour, always
// ignore touch events in the non-client area.
LPARAM l_param_ht = MAKELPARAM(point.x, point.y);
LRESULT hittest = SendMessage(hwnd_, WM_NCHITTEST, 0, l_param_ht);
if (hittest != HTCLIENT) {
return false;
}
}
ScreenToClient(hwnd_, &point);
touch_event.x = DeviceToLogical(point.x, device_scale_factor_);
touch_event.y = DeviceToLogical(point.y, device_scale_factor_);
@ -969,24 +945,52 @@ void OsrWindowWin::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
bool OsrWindowWin::GetRootScreenRect(CefRefPtr<CefBrowser> browser,
CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
DCHECK_GT(device_scale_factor_, 0);
if (!settings_.real_screen_bounds) {
return false;
}
HWND root_hwnd = ::GetAncestor(hwnd_, GA_ROOT);
DCHECK(root_hwnd);
RECT root_rect = {};
::GetWindowRect(root_hwnd, &root_rect);
// Convert to DIP coordinates.
rect = DeviceToLogical(
{root_rect.left, root_rect.top, root_rect.right - root_rect.left,
root_rect.bottom - root_rect.top},
device_scale_factor_);
if (rect.width == 0) {
rect.width = 1;
}
if (rect.height == 0) {
rect.height = 1;
}
return true;
}
void OsrWindowWin::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
DCHECK_GT(device_scale_factor_, 0);
rect.x = rect.y = 0;
rect.width = DeviceToLogical(client_rect_.right - client_rect_.left,
RECT window_rect = {};
::GetWindowRect(hwnd_, &window_rect);
// Convert to DIP coordinates.
rect = DeviceToLogical(
{window_rect.left, window_rect.top, window_rect.right - window_rect.left,
window_rect.bottom - window_rect.top},
device_scale_factor_);
if (rect.width == 0) {
rect.width = 1;
}
rect.height = DeviceToLogical(client_rect_.bottom - client_rect_.top,
device_scale_factor_);
if (rect.height == 0) {
rect.height = 1;
}
if (!settings_.real_screen_bounds) {
rect.x = rect.y = 0;
}
}
bool OsrWindowWin::GetScreenPoint(CefRefPtr<CefBrowser> browser,
@ -1019,15 +1023,24 @@ bool OsrWindowWin::GetScreenInfo(CefRefPtr<CefBrowser> browser,
return false;
}
screen_info.device_scale_factor = device_scale_factor_;
if (settings_.real_screen_bounds) {
CefRect root_rect;
GetRootScreenRect(browser, root_rect);
auto display = CefDisplay::GetDisplayMatchingBounds(
root_rect, /*input_pixel_coords=*/false);
screen_info.rect = display->GetBounds();
screen_info.available_rect = display->GetWorkArea();
} else {
CefRect view_rect;
GetViewRect(browser, view_rect);
screen_info.device_scale_factor = device_scale_factor_;
// The screen info rectangles are used by the renderer to create and position
// popups. Keep popups inside the view rectangle.
// Keep HTML select popups inside the view rectangle.
screen_info.rect = view_rect;
screen_info.available_rect = view_rect;
}
return true;
}

View File

@ -7,6 +7,7 @@
#pragma once
#include <memory>
#include <optional>
#include <set>
#include <string>
@ -185,8 +186,23 @@ class RootWindow
// Hide the window.
virtual void Hide() = 0;
// Set the window bounds in screen coordinates.
virtual void SetBounds(int x, int y, size_t width, size_t height) = 0;
// Set bounds in DIP screen coordinates. If |content_bounds| is true then the
// specified bounds are for the browser's content area and will be expanded to
// appropriate containing window bounds. Otherwise, the specified bounds are
// for the containing window directly. Bounds will be constrained to the
// containing display work area. Specific behavioral expectations depend on
// platform and run mode. See the https://tests/window example for details.
virtual void SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) = 0;
void SetBounds(const CefRect& bounds, bool content_bounds) {
SetBounds(bounds.x, bounds.y, bounds.width, bounds.height, content_bounds);
}
// Returns true if this RootWindow should default to sizing by content bounds.
virtual bool DefaultToContentBounds() const = 0;
// Close the window. If |force| is true onunload handlers will not be
// executed.
@ -198,7 +214,7 @@ class RootWindow
// Returns the device scale factor. Only used in combination with off-screen
// rendering.
virtual float GetDeviceScaleFactor() const = 0;
virtual std::optional<float> GetDeviceScaleFactor() const = 0;
// Returns the browser that this window contains, if any.
virtual CefRefPtr<CefBrowser> GetBrowser() const = 0;

View File

@ -14,11 +14,14 @@
#include "include/cef_app.h"
#include "tests/cefclient/browser/browser_window_osr_gtk.h"
#include "tests/cefclient/browser/browser_window_std_gtk.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/root_window_manager.h"
#include "tests/cefclient/browser/temp_window.h"
#include "tests/cefclient/browser/util_gtk.h"
#include "tests/cefclient/browser/window_test_runner_gtk.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/common/client_switches.h"
@ -59,23 +62,139 @@ void UseDefaultX11VisualForGtk(GtkWidget* widget) {
#endif
}
bool IsWindowMaximized(GtkWindow* window) {
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
gint state = gdk_window_get_state(gdk_window);
return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false;
// Keep the bounds inside the closest display work area.
CefRect ClampBoundsToDisplay(const CefRect& pixel_bounds) {
auto display = CefDisplay::GetDisplayMatchingBounds(
pixel_bounds, /*input_pixel_coords=*/true);
CefRect work_area =
LogicalToDevice(display->GetWorkArea(), display->GetDeviceScaleFactor());
CefRect bounds = pixel_bounds;
ConstrainWindowBounds(work_area, bounds);
return bounds;
}
void MinimizeWindow(GtkWindow* window) {
// Unmaximize the window before minimizing so restore behaves correctly.
if (IsWindowMaximized(window)) {
gtk_window_unmaximize(window);
float GetScaleFactor(const CefRect& bounds,
const std::optional<float>& device_scale_factor,
bool pixel_bounds) {
if (device_scale_factor.has_value()) {
return *device_scale_factor;
}
auto display = CefDisplay::GetDisplayMatchingBounds(
bounds, /*input_pixel_coords=*/pixel_bounds);
return display->GetDeviceScaleFactor();
}
gtk_window_iconify(window);
CefRect GetScreenPixelBounds(const CefRect& dip_bounds,
const std::optional<float>& device_scale_factor) {
const auto scale_factor =
GetScaleFactor(dip_bounds, device_scale_factor, /*pixel_bounds=*/false);
return LogicalToDevice(dip_bounds, scale_factor);
}
void MaximizeWindow(GtkWindow* window) {
gtk_window_maximize(window);
CefRect GetScreenDIPBounds(const CefRect& pixel_bounds,
const std::optional<float>& device_scale_factor) {
const auto scale_factor =
GetScaleFactor(pixel_bounds, device_scale_factor, /*pixel_bounds=*/true);
return DeviceToLogical(pixel_bounds, scale_factor);
}
// Existing window measurements in root window (pixel) coordinates.
struct BoundsInfo {
CefRect frame;
CefRect window;
CefRect browser;
};
// |content_bounds| is the browser content area bounds in DIP screen
// coordinates. Convert to root window (pixel) coordinates and then expand to
// frame bounds. Keep the resulting bounds inside the closest display work area.
// |device_scale_factor| will be specified with off-screen rendering.
CefRect GetFrameBoundsInDisplay(
const CefRect& content_bounds,
const BoundsInfo& bounds_info,
const std::optional<float>& device_scale_factor) {
CefRect pixel_bounds =
GetScreenPixelBounds(content_bounds, device_scale_factor);
// Expand the new bounds based on relative offsets for the current bounds.
// - Position includes the frame.
pixel_bounds.x -=
bounds_info.window.x + bounds_info.browser.x - bounds_info.frame.x;
pixel_bounds.y -=
bounds_info.window.y + bounds_info.browser.y - bounds_info.frame.y;
// - Size does not include the frame.
pixel_bounds.width += bounds_info.window.width - bounds_info.browser.width;
pixel_bounds.height += bounds_info.window.height - bounds_info.browser.height;
return ClampBoundsToDisplay(pixel_bounds);
}
// Execute calls on the required threads.
void GetPixelBoundsAndContinue(const CefRect& dip_bounds,
const std::optional<BoundsInfo>& bounds_info,
const std::optional<float>& device_scale_factor,
base::OnceCallback<void(const CefRect&)> next) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&GetPixelBoundsAndContinue, dip_bounds,
bounds_info, device_scale_factor,
std::move(next)));
return;
}
CefRect pixel_bounds;
if (bounds_info.has_value()) {
pixel_bounds =
GetFrameBoundsInDisplay(dip_bounds, *bounds_info, device_scale_factor);
} else {
pixel_bounds = ClampBoundsToDisplay(
GetScreenPixelBounds(dip_bounds, device_scale_factor));
}
if (CURRENTLY_ON_MAIN_THREAD()) {
std::move(next).Run(pixel_bounds);
} else {
MAIN_POST_CLOSURE(base::BindOnce(std::move(next), pixel_bounds));
}
}
void SaveWindowRestoreContinue(
cef_show_state_t show_state,
const CefRect& pixel_bounds,
const std::optional<float>& device_scale_factor) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&SaveWindowRestoreContinue, show_state,
pixel_bounds, device_scale_factor));
return;
}
CefRect dip_bounds;
if (show_state == CEF_SHOW_STATE_NORMAL) {
dip_bounds = GetScreenDIPBounds(pixel_bounds, device_scale_factor);
}
prefs::SaveWindowRestorePreferences(show_state, dip_bounds);
}
void SaveWindowRestore(GtkWidget* widget,
const std::optional<float>& device_scale_factor) {
REQUIRE_MAIN_THREAD();
GtkWindow* window = GTK_WINDOW(widget);
cef_show_state_t show_state = CEF_SHOW_STATE_NORMAL;
CefRect pixel_bounds;
if (!gtk_widget_get_visible(widget)) {
show_state = CEF_SHOW_STATE_MINIMIZED;
} else if (IsWindowMaximized(window)) {
show_state = CEF_SHOW_STATE_MAXIMIZED;
} else {
pixel_bounds = GetWindowBounds(window, /*include_frame=*/true);
}
SaveWindowRestoreContinue(show_state, pixel_bounds, device_scale_factor);
}
} // namespace
@ -117,10 +236,54 @@ void RootWindowGtk::Init(RootWindow::Delegate* delegate,
with_controls_ = config->with_controls;
always_on_top_ = config->always_on_top;
with_osr_ = config->with_osr;
start_rect_ = config->bounds;
CreateBrowserWindow(config->url);
if (CefCurrentlyOn(TID_UI)) {
ContinueInitOnUIThread(std::move(config), settings);
} else {
CefPostTask(TID_UI, base::BindOnce(&RootWindowGtk::ContinueInitOnUIThread,
this, std::move(config), settings));
}
}
void RootWindowGtk::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.
start_rect_ = 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) {
start_rect_ = GetScreenPixelBounds(*bounds, std::nullopt);
}
}
if (with_osr_) {
initial_scale_factor_ =
GetScaleFactor(start_rect_, std::nullopt, /*pixel_bounds=*/true);
}
if (CURRENTLY_ON_MAIN_THREAD()) {
ContinueInitOnMainThread(std::move(config), settings);
} else {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::ContinueInitOnMainThread,
this, std::move(config), settings));
}
}
void RootWindowGtk::ContinueInitOnMainThread(
std::unique_ptr<RootWindowConfig> config,
const CefBrowserSettings& settings) {
REQUIRE_MAIN_THREAD();
initialized_ = true;
// Always post asynchronously to avoid reentrancy of the GDK lock.
@ -135,6 +298,8 @@ void RootWindowGtk::InitAsPopup(RootWindow::Delegate* delegate,
CefWindowInfo& windowInfo,
CefRefPtr<CefClient>& client,
CefBrowserSettings& settings) {
CEF_REQUIRE_UI_THREAD();
DCHECK(delegate);
DCHECK(!initialized_);
@ -143,6 +308,7 @@ void RootWindowGtk::InitAsPopup(RootWindow::Delegate* delegate,
with_osr_ = with_osr;
is_popup_ = true;
// NOTE: This will be the size for the whole window including frame.
if (popupFeatures.xSet) {
start_rect_.x = popupFeatures.x;
}
@ -155,6 +321,13 @@ void RootWindowGtk::InitAsPopup(RootWindow::Delegate* delegate,
if (popupFeatures.heightSet) {
start_rect_.height = popupFeatures.height;
}
start_rect_ =
ClampBoundsToDisplay(GetScreenPixelBounds(start_rect_, std::nullopt));
if (with_osr_) {
initial_scale_factor_ =
GetScaleFactor(start_rect_, std::nullopt, /*pixel_bounds=*/true);
}
CreateBrowserWindow(std::string());
@ -203,26 +376,74 @@ void RootWindowGtk::Hide() {
}
}
void RootWindowGtk::SetBounds(int x, int y, size_t width, size_t height) {
void RootWindowGtk::SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) {
REQUIRE_MAIN_THREAD();
if (!window_) {
return;
}
CefRect dip_bounds{x, y, static_cast<int>(width), static_cast<int>(height)};
GetWindowBoundsAndContinue(
dip_bounds, content_bounds,
base::BindOnce(
[](GtkWidget* window, const CefRect& pixel_bounds) {
ScopedGdkThreadsEnter scoped_gdk_threads;
GdkWindow* gdk_window = gtk_widget_get_window(window);
gdk_window_move_resize(gdk_window, pixel_bounds.x, pixel_bounds.y,
pixel_bounds.width, pixel_bounds.height);
},
base::Unretained(window_)));
}
bool RootWindowGtk::DefaultToContentBounds() const {
if (!WithWindowlessRendering()) {
// Root GtkWindow bounds are provided via GetRootWindowScreenRect.
return false;
}
if (osr_settings_.real_screen_bounds) {
// Root GtkWindow bounds are provided via GetRootScreenRect.
return false;
}
// The root GtkWindow will not be queried by default.
return true;
}
void RootWindowGtk::GetWindowBoundsAndContinue(
const CefRect& dip_bounds,
bool content_bounds,
base::OnceCallback<void(const CefRect& /*pixel_bounds*/)> next) {
REQUIRE_MAIN_THREAD();
DCHECK(window_);
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GTK_WINDOW(window_);
GdkWindow* gdk_window = gtk_widget_get_window(window_);
// Make sure the window isn't minimized or maximized.
if (IsWindowMaximized(window)) {
gtk_window_unmaximize(window);
} else {
gtk_window_present(window);
// Make sure the window isn't minimized or maximized. It must also be
// presented before we can retrieve bounds information.
RestoreWindow(window);
std::optional<BoundsInfo> bounds_info;
if (content_bounds) {
// Existing measurements in root window (pixel) coordinates.
GdkWindow* gdk_window = gtk_widget_get_window(window_);
GdkRectangle frame_rect = {};
gdk_window_get_frame_extents(gdk_window, &frame_rect);
bounds_info = {
{frame_rect.x, frame_rect.y, frame_rect.width, frame_rect.height},
GetWindowBounds(window, /*include_frame=*/false),
browser_bounds_};
}
gdk_window_move_resize(gdk_window, x, y, width, height);
GetPixelBoundsAndContinue(dip_bounds, bounds_info, GetDeviceScaleFactor(),
std::move(next));
}
void RootWindowGtk::Close(bool force) {
@ -246,15 +467,14 @@ void RootWindowGtk::SetDeviceScaleFactor(float device_scale_factor) {
}
}
float RootWindowGtk::GetDeviceScaleFactor() const {
std::optional<float> RootWindowGtk::GetDeviceScaleFactor() const {
REQUIRE_MAIN_THREAD();
if (browser_window_ && with_osr_) {
return browser_window_->GetDeviceScaleFactor();
}
NOTREACHED();
return 0.0f;
return std::nullopt;
}
CefRefPtr<CefBrowser> RootWindowGtk::GetBrowser() const {
@ -273,15 +493,15 @@ ClientWindowHandle RootWindowGtk::GetWindowHandle() const {
bool RootWindowGtk::WithWindowlessRendering() const {
REQUIRE_MAIN_THREAD();
DCHECK(initialized_);
return with_osr_;
}
void RootWindowGtk::CreateBrowserWindow(const std::string& startup_url) {
if (with_osr_) {
OsrRendererSettings settings = {};
MainContext::Get()->PopulateOsrSettings(&settings);
browser_window_.reset(
new BrowserWindowOsrGtk(this, with_controls_, startup_url, settings));
MainContext::Get()->PopulateOsrSettings(&osr_settings_);
browser_window_.reset(new BrowserWindowOsrGtk(this, with_controls_,
startup_url, osr_settings_));
} else {
browser_window_.reset(
new BrowserWindowStdGtk(this, with_controls_, startup_url));
@ -329,7 +549,7 @@ void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings,
G_CALLBACK(&RootWindowGtk::WindowDelete), this);
const cef_color_t background_color = MainContext::Get()->GetBackgroundColor();
GdkRGBA rgba = {0};
GdkRGBA rgba = {};
rgba.red = CefColorGetR(background_color) * 65535 / 255;
rgba.green = CefColorGetG(background_color) * 65535 / 255;
rgba.blue = CefColorGetB(background_color) * 65535 / 255;
@ -432,6 +652,20 @@ void RootWindowGtk::CreateRootWindow(const CefBrowserSettings& settings,
->set_xdisplay(xdisplay);
}
if (with_osr_) {
std::optional<float> parent_scale_factor;
if (is_popup_) {
if (auto parent_window =
MainContext::Get()->GetRootWindowManager()->GetWindowForBrowser(
opener_browser_id())) {
parent_scale_factor = parent_window->GetDeviceScaleFactor();
}
}
browser_window_->SetDeviceScaleFactor(
parent_scale_factor.value_or(initial_scale_factor_));
}
if (!is_popup_) {
// Create the browser window.
browser_window_->CreateBrowser(parent, browser_bounds_, settings, nullptr,
@ -525,19 +759,18 @@ void RootWindowGtk::OnAutoResize(const CefSize& new_size) {
return;
}
CefRect dip_bounds{0, 0, new_size.width, new_size.height};
GetWindowBoundsAndContinue(
dip_bounds, /*content_bounds=*/true,
base::BindOnce(
[](GtkWidget* window, const CefRect& pixel_bounds) {
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GTK_WINDOW(window_);
GdkWindow* gdk_window = gtk_widget_get_window(window_);
// Make sure the window isn't minimized or maximized.
if (IsWindowMaximized(window)) {
gtk_window_unmaximize(window);
} else {
gtk_window_present(window);
}
gdk_window_resize(gdk_window, new_size.width, new_size.height);
GdkWindow* gdk_window = gtk_widget_get_window(window);
gdk_window_resize(gdk_window, pixel_bounds.width,
pixel_bounds.height);
},
base::Unretained(window_)));
}
void RootWindowGtk::OnSetLoadingState(bool isLoading,
@ -561,29 +794,46 @@ void RootWindowGtk::OnSetDraggableRegions(
// TODO(cef): Implement support for draggable regions on this platform.
}
void RootWindowGtk::NotifyMoveOrResizeStarted() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::BindOnce(&RootWindowGtk::NotifyMoveOrResizeStarted, this));
return;
bool RootWindowGtk::GetRootWindowScreenRect(CefRect& rect) {
CEF_REQUIRE_UI_THREAD();
if (!window_) {
return false;
}
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GTK_WINDOW(window_);
CefRect pixel_bounds = GetWindowBounds(window, /*include_frame=*/true);
rect = GetScreenDIPBounds(pixel_bounds, std::nullopt);
return true;
}
void RootWindowGtk::NotifyMoveOrResizeStarted() {
REQUIRE_MAIN_THREAD();
// Called when size, position or stack order changes.
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser.get()) {
if (auto browser = GetBrowser()) {
// Notify the browser of move/resize events so that:
// - Popup windows are displayed in the correct location and dismissed
// when the window moves.
// - Drag&drop areas are updated accordingly.
browser->GetHost()->NotifyMoveOrResizeStarted();
}
MaybeNotifyScreenInfoChanged();
}
void RootWindowGtk::MaybeNotifyScreenInfoChanged() {
if (!DefaultToContentBounds()) {
// Send the new root window bounds to the renderer.
if (auto browser = GetBrowser()) {
browser->GetHost()->NotifyScreenInfoChanged();
}
}
}
void RootWindowGtk::NotifySetFocus() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifySetFocus, this));
return;
}
REQUIRE_MAIN_THREAD();
if (!browser_window_.get()) {
return;
@ -594,11 +844,7 @@ void RootWindowGtk::NotifySetFocus() {
}
void RootWindowGtk::NotifyVisibilityChange(bool show) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::BindOnce(&RootWindowGtk::NotifyVisibilityChange, this, show));
return;
}
REQUIRE_MAIN_THREAD();
if (!browser_window_.get()) {
return;
@ -646,10 +892,7 @@ void RootWindowGtk::NotifyContentBounds(int x, int y, int width, int height) {
}
void RootWindowGtk::NotifyLoadURL(const std::string& url) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyLoadURL, this, url));
return;
}
REQUIRE_MAIN_THREAD();
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser.get()) {
@ -658,11 +901,7 @@ void RootWindowGtk::NotifyLoadURL(const std::string& url) {
}
void RootWindowGtk::NotifyButtonClicked(int id) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(
base::BindOnce(&RootWindowGtk::NotifyButtonClicked, this, id));
return;
}
REQUIRE_MAIN_THREAD();
CefRefPtr<CefBrowser> browser = GetBrowser();
if (!browser.get()) {
@ -688,10 +927,7 @@ void RootWindowGtk::NotifyButtonClicked(int id) {
}
void RootWindowGtk::NotifyMenuItem(int id) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyMenuItem, this, id));
return;
}
REQUIRE_MAIN_THREAD();
// Run the test.
if (delegate_) {
@ -700,19 +936,13 @@ void RootWindowGtk::NotifyMenuItem(int id) {
}
void RootWindowGtk::NotifyForceClose() {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&RootWindowGtk::NotifyForceClose, this));
return;
}
REQUIRE_MAIN_THREAD();
force_close_ = true;
}
void RootWindowGtk::NotifyCloseBrowser() {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowGtk::NotifyCloseBrowser, this));
return;
}
REQUIRE_MAIN_THREAD();
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser) {
@ -798,15 +1028,18 @@ gboolean RootWindowGtk::WindowDelete(GtkWidget* widget,
RootWindowGtk* self) {
REQUIRE_MAIN_THREAD();
SaveWindowRestore(widget, self->GetDeviceScaleFactor());
// Called to query whether the root window should be closed.
if (self->force_close_) {
return FALSE; // Allow the close.
}
if (!self->is_closing_) {
// 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.
// Notify the browser window that we would like to close it. With Alloy
// style this will result in a call to ClientHandler::DoClose() if the
// JavaScript 'onbeforeunload' event handler allows it. With Chrome style
// this will close the window indirectly via browser destruction.
self->NotifyCloseBrowser();
// Cancel the close.

View File

@ -12,6 +12,7 @@
#include <string>
#include "tests/cefclient/browser/browser_window.h"
#include "tests/cefclient/browser/osr_renderer_settings.h"
#include "tests/cefclient/browser/root_window.h"
namespace client {
@ -38,15 +39,25 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
CefBrowserSettings& settings) override;
void Show(ShowMode mode) override;
void Hide() override;
void SetBounds(int x, int y, size_t width, size_t height) override;
void SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) override;
bool DefaultToContentBounds() const override;
void Close(bool force) override;
void SetDeviceScaleFactor(float device_scale_factor) override;
float GetDeviceScaleFactor() const override;
std::optional<float> GetDeviceScaleFactor() const override;
CefRefPtr<CefBrowser> GetBrowser() const override;
ClientWindowHandle GetWindowHandle() const override;
bool WithWindowlessRendering() 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);
@ -60,11 +71,21 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
void OnSetTitle(const std::string& title) override;
void OnSetFullscreen(bool fullscreen) override;
void OnAutoResize(const CefSize& new_size) override;
void OnContentsBounds(const CefRect& new_bounds) override {
RootWindow::SetBounds(new_bounds,
/*content_bounds=*/DefaultToContentBounds());
}
void OnSetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) override;
void OnSetDraggableRegions(
const std::vector<CefDraggableRegion>& regions) override;
bool GetRootWindowScreenRect(CefRect& rect) override;
void GetWindowBoundsAndContinue(
const CefRect& dip_bounds,
bool content_bounds,
base::OnceCallback<void(const CefRect& /*pixel_bounds*/)> next);
void NotifyMoveOrResizeStarted();
void NotifySetFocus();
@ -78,6 +99,8 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
void NotifyCloseBrowser();
void NotifyDestroyedIfDone(bool window_destroyed, bool browser_destroyed);
void MaybeNotifyScreenInfoChanged();
GtkWidget* CreateMenuBar();
GtkWidget* CreateMenu(GtkWidget* menu_bar, const char* text);
GtkWidget* AddMenuEntry(GtkWidget* menu_widget, const char* text, int id);
@ -128,8 +151,11 @@ class RootWindowGtk : public RootWindow, public BrowserWindow::Delegate {
bool with_controls_;
bool always_on_top_;
bool with_osr_;
OsrRendererSettings osr_settings_;
bool is_popup_;
CefRect start_rect_;
cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL;
float initial_scale_factor_ = 1.0;
std::unique_ptr<BrowserWindow> browser_window_;
// Main window.

View File

@ -14,6 +14,7 @@
namespace client {
struct OsrRendererSettings;
class RootWindowMacImpl;
// OS X implementation of a top-level native window in the browser process.
@ -27,6 +28,7 @@ class RootWindowMac : public RootWindow, public BrowserWindow::Delegate {
BrowserWindow* browser_window() const;
RootWindow::Delegate* delegate() const;
const OsrRendererSettings* osr_settings() const;
// RootWindow methods.
void Init(RootWindow::Delegate* delegate,
@ -41,10 +43,15 @@ class RootWindowMac : public RootWindow, public BrowserWindow::Delegate {
CefBrowserSettings& settings) override;
void Show(ShowMode mode) override;
void Hide() override;
void SetBounds(int x, int y, size_t width, size_t height) override;
void SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) override;
bool DefaultToContentBounds() const override;
void Close(bool force) override;
void SetDeviceScaleFactor(float device_scale_factor) override;
float GetDeviceScaleFactor() const override;
std::optional<float> GetDeviceScaleFactor() const override;
CefRefPtr<CefBrowser> GetBrowser() const override;
ClientWindowHandle GetWindowHandle() const override;
bool WithWindowlessRendering() const override;
@ -57,6 +64,10 @@ class RootWindowMac : public RootWindow, public BrowserWindow::Delegate {
void OnSetTitle(const std::string& title) override;
void OnSetFullscreen(bool fullscreen) override;
void OnAutoResize(const CefSize& new_size) override;
void OnContentsBounds(const CefRect& new_bounds) override {
RootWindow::SetBounds(new_bounds,
/*content_bounds=*/DefaultToContentBounds());
}
void OnSetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) override;

View File

@ -14,7 +14,10 @@
#include "tests/cefclient/browser/browser_window_std_mac.h"
#include "tests/cefclient/browser/client_prefs.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/osr_renderer_settings.h"
#include "tests/cefclient/browser/root_window_manager.h"
#include "tests/cefclient/browser/temp_window.h"
#include "tests/cefclient/browser/util_mac.h"
#include "tests/cefclient/browser/window_test_runner_mac.h"
#include "tests/shared/browser/main_message_loop.h"
#include "tests/shared/common/client_switches.h"
@ -65,40 +68,6 @@ NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) {
return button;
}
// Returns the current DIP screen bounds for a visible window in the
// restored position, or nullopt if the window is currently minimized or
// fullscreen.
std::optional<CefRect> GetWindowBoundsInScreen(NSWindow* window) {
if ([window isMiniaturized] or [window isZoomed]) {
return std::nullopt;
}
auto screen = [window screen];
if (screen == nil) {
screen = [NSScreen mainScreen];
}
const auto bounds = [window frame];
const auto screen_bounds = [screen frame];
if (NSEqualRects(bounds, screen_bounds)) {
// Don't include windows that are transitioning to fullscreen.
return std::nullopt;
}
CefRect dip_bounds{static_cast<int>(bounds.origin.x),
static_cast<int>(bounds.origin.y),
static_cast<int>(bounds.size.width),
static_cast<int>(bounds.size.height)};
// Convert from macOS coordinates (bottom-left origin) to DIP coordinates
// (top-left origin).
dip_bounds.y = static_cast<int>(screen_bounds.size.height) -
dip_bounds.height - dip_bounds.y;
return dip_bounds;
}
// Keep the frame bounds inside the display work area.
NSRect ClampNSBoundsToWorkArea(const NSRect& frame_bounds,
const CefRect& display_bounds,
@ -142,6 +111,7 @@ NSRect ClampNSBoundsToWorkArea(const NSRect& frame_bounds,
void GetNSBoundsInDisplay(const CefRect& dip_bounds,
bool input_content_bounds,
NSWindowStyleMask style_mask,
bool add_controls,
NSRect& frame_rect,
NSRect& content_rect) {
// Identify the closest display.
@ -158,18 +128,24 @@ void GetNSBoundsInDisplay(const CefRect& dip_bounds,
requested_rect.origin.y = display_bounds.height - requested_rect.size.height -
requested_rect.origin.y;
bool changed_content_bounds = false;
// Calculate the equivalent frame and content bounds.
if (input_content_bounds) {
// Compute frame rect from content rect. Keep the requested origin.
content_rect = requested_rect;
frame_rect = [NSWindow frameRectForContentRect:content_rect
styleMask:style_mask];
if (add_controls) {
frame_rect.size.height += URLBAR_HEIGHT;
}
frame_rect.origin = requested_rect.origin;
} else {
// Compute content rect from frame rect.
frame_rect = requested_rect;
content_rect = [NSWindow contentRectForFrameRect:frame_rect
styleMask:style_mask];
changed_content_bounds = true;
}
// Keep the frame inside the display work area.
@ -179,6 +155,12 @@ void GetNSBoundsInDisplay(const CefRect& dip_bounds,
frame_rect = new_frame_rect;
content_rect = [NSWindow contentRectForFrameRect:frame_rect
styleMask:style_mask];
changed_content_bounds = true;
}
if (changed_content_bounds && add_controls) {
content_rect.origin.y -= URLBAR_HEIGHT;
content_rect.size.height -= URLBAR_HEIGHT;
}
}
@ -211,10 +193,15 @@ class RootWindowMacImpl
CefBrowserSettings& settings);
void Show(RootWindow::ShowMode mode);
void Hide();
void SetBounds(int x, int y, size_t width, size_t height);
void SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds);
bool DefaultToContentBounds() const;
void Close(bool force);
void SetDeviceScaleFactor(float device_scale_factor);
float GetDeviceScaleFactor() const;
std::optional<float> GetDeviceScaleFactor() const;
CefRefPtr<CefBrowser> GetBrowser() const;
ClientWindowHandle GetWindowHandle() const;
bool WithWindowlessRendering() const;
@ -236,6 +223,7 @@ class RootWindowMacImpl
RootWindowMac& root_window_;
bool with_controls_ = false;
bool with_osr_ = false;
OsrRendererSettings osr_settings_;
bool is_popup_ = false;
CefRect initial_bounds_;
cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL;
@ -390,7 +378,11 @@ void RootWindowMacImpl::Hide() {
[window_ orderOut:nil];
}
void RootWindowMacImpl::SetBounds(int x, int y, size_t width, size_t height) {
void RootWindowMacImpl::SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) {
REQUIRE_MAIN_THREAD();
if (!window_) {
@ -399,15 +391,29 @@ void RootWindowMacImpl::SetBounds(int x, int y, size_t width, size_t height) {
const CefRect dip_bounds(x, y, static_cast<int>(width),
static_cast<int>(height));
const bool add_controls = WithWindowlessRendering() || with_controls_;
// Calculate the equivalent frame and content area bounds.
NSRect frame_rect, content_rect;
GetNSBoundsInDisplay(dip_bounds, /*input_content_bounds=*/true,
[window_ styleMask], frame_rect, content_rect);
GetNSBoundsInDisplay(dip_bounds, content_bounds, [window_ styleMask],
add_controls, frame_rect, content_rect);
[window_ setFrame:frame_rect display:YES];
}
bool RootWindowMacImpl::DefaultToContentBounds() const {
if (!WithWindowlessRendering()) {
// The root NSWindow will be queried by default.
return false;
}
if (osr_settings_.real_screen_bounds) {
// Root NSWindow bounds are provided via GetRootWindowRect.
return false;
}
// The root NSWindow will not be queried by default.
return true;
}
void RootWindowMacImpl::Close(bool force) {
REQUIRE_MAIN_THREAD();
@ -426,15 +432,14 @@ void RootWindowMacImpl::SetDeviceScaleFactor(float device_scale_factor) {
}
}
float RootWindowMacImpl::GetDeviceScaleFactor() const {
std::optional<float> RootWindowMacImpl::GetDeviceScaleFactor() const {
REQUIRE_MAIN_THREAD();
if (browser_window_ && with_osr_) {
return browser_window_->GetDeviceScaleFactor();
}
NOTREACHED();
return 0.0f;
return std::nullopt;
}
CefRefPtr<CefBrowser> RootWindowMacImpl::GetBrowser() const {
@ -453,6 +458,7 @@ ClientWindowHandle RootWindowMacImpl::GetWindowHandle() const {
bool RootWindowMacImpl::WithWindowlessRendering() const {
REQUIRE_MAIN_THREAD();
DCHECK(root_window_.initialized_);
return with_osr_;
}
@ -464,10 +470,9 @@ void RootWindowMacImpl::OnNativeWindowClosed() {
void RootWindowMacImpl::CreateBrowserWindow(const std::string& startup_url) {
if (with_osr_) {
OsrRendererSettings settings = {};
MainContext::Get()->PopulateOsrSettings(&settings);
MainContext::Get()->PopulateOsrSettings(&osr_settings_);
browser_window_.reset(new BrowserWindowOsrMac(&root_window_, with_controls_,
startup_url, settings));
startup_url, osr_settings_));
} else {
browser_window_.reset(
new BrowserWindowStdMac(&root_window_, with_controls_, startup_url));
@ -501,10 +506,12 @@ void RootWindowMacImpl::CreateRootWindow(const CefBrowserSettings& settings,
(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable);
// Calculate the equivalent frame and content area bounds.
// Calculate the equivalent frame and content area bounds. Controls, if any,
// are already included in the desired size.
NSRect frame_rect, content_rect;
GetNSBoundsInDisplay(dip_bounds, /*input_content_bounds=*/is_popup_,
style_mask, frame_rect, content_rect);
style_mask, /*add_controls=*/false, frame_rect,
content_rect);
// Create the main window.
window_ = [[NSWindow alloc] initWithContentRect:content_rect
@ -606,6 +613,25 @@ void RootWindowMacImpl::CreateRootWindow(const CefBrowserSettings& settings,
// if the point is on a secondary display.
[window_ setFrameOrigin:frame_rect.origin];
if (with_osr_) {
std::optional<float> parent_scale_factor;
if (is_popup_) {
if (auto parent_window =
MainContext::Get()->GetRootWindowManager()->GetWindowForBrowser(
root_window_.opener_browser_id())) {
parent_scale_factor = parent_window->GetDeviceScaleFactor();
}
}
if (parent_scale_factor) {
browser_window_->SetDeviceScaleFactor(*parent_scale_factor);
} else {
auto display = CefDisplay::GetDisplayMatchingBounds(
dip_bounds, /*input_pixel_coords=*/false);
browser_window_->SetDeviceScaleFactor(display->GetDeviceScaleFactor());
}
}
if (!is_popup_) {
// Create the browser window.
browser_window_->CreateBrowser(
@ -709,14 +735,20 @@ void RootWindowMacImpl::OnAutoResize(const CefSize& new_size) {
return;
}
// Desired content rectangle.
NSRect content_rect;
content_rect.size.width = static_cast<int>(new_size.width);
content_rect.size.height =
static_cast<int>(new_size.height) + (with_controls_ ? URLBAR_HEIGHT : 0);
CefRect dip_bounds(0, 0, static_cast<int>(new_size.width),
static_cast<int>(new_size.height));
if (auto screen_bounds = GetWindowBoundsInScreen(window_)) {
dip_bounds.x = (*screen_bounds).x;
dip_bounds.y = (*screen_bounds).y;
}
// Calculate the equivalent frame and content area bounds.
NSRect frame_rect, content_rect;
GetNSBoundsInDisplay(dip_bounds, /*input_content_bounds=*/true,
[window_ styleMask], with_controls_, frame_rect,
content_rect);
// Convert to a frame rectangle.
NSRect frame_rect = [window_ frameRectForContentRect:content_rect];
// Don't change the origin.
frame_rect.origin = window_.frame.origin;
@ -775,6 +807,10 @@ RootWindow::Delegate* RootWindowMac::delegate() const {
return delegate_;
}
const OsrRendererSettings* RootWindowMac::osr_settings() const {
return &impl_->osr_settings_;
}
void RootWindowMac::Init(RootWindow::Delegate* delegate,
std::unique_ptr<RootWindowConfig> config,
const CefBrowserSettings& settings) {
@ -804,8 +840,16 @@ void RootWindowMac::Hide() {
impl_->Hide();
}
void RootWindowMac::SetBounds(int x, int y, size_t width, size_t height) {
impl_->SetBounds(x, y, width, height);
void RootWindowMac::SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) {
impl_->SetBounds(x, y, width, height, content_bounds);
}
bool RootWindowMac::DefaultToContentBounds() const {
return impl_->DefaultToContentBounds();
}
void RootWindowMac::Close(bool force) {
@ -816,7 +860,7 @@ void RootWindowMac::SetDeviceScaleFactor(float device_scale_factor) {
impl_->SetDeviceScaleFactor(device_scale_factor);
}
float RootWindowMac::GetDeviceScaleFactor() const {
std::optional<float> RootWindowMac::GetDeviceScaleFactor() const {
return impl_->GetDeviceScaleFactor();
}
@ -1005,6 +1049,16 @@ void RootWindowMac::OnNativeWindowClosed() {
if (dip_bounds) {
last_visible_bounds_ = dip_bounds;
}
if (root_window_->WithWindowlessRendering() &&
root_window_->osr_settings()->real_screen_bounds) {
// Send the new root window bounds to the renderer.
if (auto* browser_window = root_window_->browser_window()) {
if (auto browser = browser_window->GetBrowser()) {
browser->GetHost()->NotifyScreenInfoChanged();
}
}
}
}
// Called when the application has been hidden.

View File

@ -137,11 +137,18 @@ void RootWindowViews::Hide() {
}
}
void RootWindowViews::SetBounds(int x, int y, size_t width, size_t height) {
void RootWindowViews::SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) {
// We always expect Window bounds with Views-hosted browsers.
DCHECK(!content_bounds);
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
CefPostTask(TID_UI, base::BindOnce(&RootWindowViews::SetBounds, this, x, y,
width, height));
width, height, content_bounds));
return;
}
@ -151,6 +158,11 @@ void RootWindowViews::SetBounds(int x, int y, size_t width, size_t height) {
}
}
bool RootWindowViews::DefaultToContentBounds() const {
// Views-hosted browsers always receive CefWindow bounds.
return false;
}
void RootWindowViews::Close(bool force) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute this method on the UI thread.
@ -169,11 +181,11 @@ void RootWindowViews::SetDeviceScaleFactor(float device_scale_factor) {
NOTREACHED();
}
float RootWindowViews::GetDeviceScaleFactor() const {
std::optional<float> RootWindowViews::GetDeviceScaleFactor() const {
REQUIRE_MAIN_THREAD();
// Windowless rendering is not supported.
NOTREACHED();
return 0.0;
return std::nullopt;
}
CefRefPtr<CefBrowser> RootWindowViews::GetBrowser() const {

View File

@ -44,10 +44,15 @@ class RootWindowViews : public RootWindow,
CefBrowserSettings& settings) override;
void Show(ShowMode mode) override;
void Hide() override;
void SetBounds(int x, int y, size_t width, size_t height) override;
void SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) override;
bool DefaultToContentBounds() const override;
void Close(bool force) override;
void SetDeviceScaleFactor(float device_scale_factor) override;
float GetDeviceScaleFactor() const override;
std::optional<float> GetDeviceScaleFactor() const override;
CefRefPtr<CefBrowser> GetBrowser() const override;
ClientWindowHandle GetWindowHandle() const override;
bool WithWindowlessRendering() const override { return false; }
@ -80,6 +85,10 @@ class RootWindowViews : public RootWindow,
void OnSetFavicon(CefRefPtr<CefImage> image) override;
void OnSetFullscreen(bool fullscreen) override;
void OnAutoResize(const CefSize& new_size) override;
void OnContentsBounds(const CefRect& new_bounds) override {
RootWindow::SetBounds(new_bounds,
/*content_bounds=*/DefaultToContentBounds());
}
void OnSetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) override;

View File

@ -18,6 +18,7 @@
#include "tests/cefclient/browser/client_prefs.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/cefclient/browser/root_window_manager.h"
#include "tests/cefclient/browser/temp_window.h"
#include "tests/cefclient/browser/window_test_runner_win.h"
#include "tests/shared/browser/geometry_util.h"
@ -85,6 +86,8 @@ bool IsProcessPerMonitorDpiAware() {
// DPI value for 1x scale factor.
#define DPI_1X 96.0f
// WARNING: Only use this value for scaling native controls. DIP coordinates
// originating from the browser should be converted using GetScreenPixelBounds.
float GetWindowScaleFactor(HWND hwnd) {
if (hwnd && IsProcessPerMonitorDpiAware()) {
typedef UINT(WINAPI * GetDpiForWindowPtr)(HWND);
@ -106,6 +109,104 @@ int GetURLBarHeight(HWND hwnd) {
return LogicalToDevice(URLBAR_HEIGHT, GetWindowScaleFactor(hwnd));
}
float GetScaleFactor(const CefRect& bounds,
const std::optional<float>& device_scale_factor,
bool pixel_bounds) {
if (device_scale_factor.has_value()) {
return *device_scale_factor;
}
auto display = CefDisplay::GetDisplayMatchingBounds(
bounds, /*input_pixel_coords=*/pixel_bounds);
return display->GetDeviceScaleFactor();
}
// Keep the bounds inside the closest display work area.
CefRect ClampBoundsToDisplay(const CefRect& pixel_bounds) {
auto display = CefDisplay::GetDisplayMatchingBounds(
pixel_bounds, /*input_pixel_coords=*/true);
CefRect work_area =
CefDisplay::ConvertScreenRectToPixels(display->GetWorkArea());
CefRect bounds = pixel_bounds;
ConstrainWindowBounds(work_area, bounds);
return bounds;
}
// Convert DIP screen coordinates originating from the browser to device screen
// (pixel) coordinates. |device_scale_factor| will be specified with off-screen
// rendering.
CefRect GetScreenPixelBounds(const CefRect& dip_bounds,
const std::optional<float>& device_scale_factor) {
if (device_scale_factor.has_value()) {
return LogicalToDevice(dip_bounds, *device_scale_factor);
}
return CefDisplay::ConvertScreenRectToPixels(dip_bounds);
}
// |content_bounds| is the browser content area bounds in DIP screen
// coordinates. Convert to device screen (pixel) coordinates and then expand to
// frame bounds. Keep the resulting bounds inside the closest display work area.
// |device_scale_factor| will be specified with off-screen rendering.
CefRect GetFrameBoundsInDisplay(
HWND hwnd,
const CefRect& content_bounds,
bool with_controls,
const std::optional<float>& device_scale_factor) {
CefRect pixel_bounds =
GetScreenPixelBounds(content_bounds, device_scale_factor);
if (with_controls) {
// Expand the bounds to include native controls.
const int urlbar_height = GetURLBarHeight(hwnd);
pixel_bounds.y -= urlbar_height;
pixel_bounds.height += urlbar_height;
}
RECT rect = {pixel_bounds.x, pixel_bounds.y,
pixel_bounds.x + pixel_bounds.width,
pixel_bounds.y + pixel_bounds.height};
DWORD style = GetWindowLong(hwnd, GWL_STYLE);
DWORD ex_style = GetWindowLong(hwnd, GWL_EXSTYLE);
bool has_menu = !(style & WS_CHILD) && (GetMenu(hwnd) != nullptr);
// Calculate the frame size based on the current style.
AdjustWindowRectEx(&rect, style, has_menu, ex_style);
return ClampBoundsToDisplay(
{rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top});
}
// Execute calls on the required threads.
void GetPixelBoundsAndContinue(HWND hwnd,
const CefRect& dip_bounds,
bool content_bounds,
bool with_controls,
const std::optional<float>& device_scale_factor,
base::OnceCallback<void(const CefRect&)> next) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI,
base::BindOnce(&GetPixelBoundsAndContinue, hwnd, dip_bounds,
content_bounds, with_controls,
device_scale_factor, std::move(next)));
return;
}
CefRect pixel_bounds;
if (content_bounds) {
pixel_bounds = GetFrameBoundsInDisplay(hwnd, dip_bounds, with_controls,
device_scale_factor);
} else {
pixel_bounds = ClampBoundsToDisplay(
GetScreenPixelBounds(dip_bounds, device_scale_factor));
}
if (CURRENTLY_ON_MAIN_THREAD()) {
std::move(next).Run(pixel_bounds);
} else {
MAIN_POST_CLOSURE(base::BindOnce(std::move(next), pixel_bounds));
}
}
} // namespace
RootWindowWin::RootWindowWin(bool use_alloy_style)
@ -165,9 +266,18 @@ void RootWindowWin::ContinueInitOnUIThread(
}
}
if (with_osr_) {
initial_scale_factor_ =
GetScaleFactor(initial_bounds_, std::nullopt, /*pixel_bounds=*/true);
}
if (CURRENTLY_ON_MAIN_THREAD()) {
ContinueInitOnMainThread(std::move(config), settings);
} else {
MAIN_POST_CLOSURE(base::BindOnce(&RootWindowWin::ContinueInitOnMainThread,
this, std::move(config), settings));
}
}
void RootWindowWin::ContinueInitOnMainThread(
std::unique_ptr<RootWindowConfig> config,
@ -196,6 +306,7 @@ void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate,
with_osr_ = with_osr;
is_popup_ = true;
// NOTE: This will be the size for the whole window including frame.
if (popupFeatures.xSet) {
initial_bounds_.x = popupFeatures.x;
}
@ -208,6 +319,13 @@ void RootWindowWin::InitAsPopup(RootWindow::Delegate* delegate,
if (popupFeatures.heightSet) {
initial_bounds_.height = popupFeatures.height;
}
initial_bounds_ = ClampBoundsToDisplay(
CefDisplay::ConvertScreenRectToPixels(initial_bounds_));
if (with_osr_) {
initial_scale_factor_ =
GetScaleFactor(initial_bounds_, std::nullopt, /*pixel_bounds=*/true);
}
CreateBrowserWindow(std::string());
@ -256,13 +374,51 @@ void RootWindowWin::Hide() {
}
}
void RootWindowWin::SetBounds(int x, int y, size_t width, size_t height) {
void RootWindowWin::SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) {
REQUIRE_MAIN_THREAD();
if (hwnd_) {
SetWindowPos(hwnd_, nullptr, x, y, static_cast<int>(width),
static_cast<int>(height), SWP_NOZORDER);
if (!hwnd_) {
return;
}
CefRect dip_bounds = {x, y, static_cast<int>(width),
static_cast<int>(height)};
GetWindowBoundsAndContinue(
dip_bounds, content_bounds,
base::BindOnce(
[](HWND hwnd, const CefRect& pixel_bounds) {
SetWindowPos(hwnd, nullptr, pixel_bounds.x, pixel_bounds.y,
pixel_bounds.width, pixel_bounds.height, SWP_NOZORDER);
},
hwnd_));
}
bool RootWindowWin::DefaultToContentBounds() const {
if (!WithWindowlessRendering()) {
// The root HWND will be queried by default.
return false;
}
if (osr_settings_.real_screen_bounds) {
// Root HWND bounds are provided via GetRootWindowRect.
return false;
}
// The root HWND will not be queried by default.
return true;
}
void RootWindowWin::GetWindowBoundsAndContinue(
const CefRect& dip_bounds,
bool content_bounds,
base::OnceCallback<void(const CefRect&)> next) {
REQUIRE_MAIN_THREAD();
DCHECK(hwnd_);
GetPixelBoundsAndContinue(hwnd_, dip_bounds, content_bounds, with_controls_,
GetDeviceScaleFactor(), std::move(next));
}
void RootWindowWin::Close(bool force) {
@ -285,15 +441,14 @@ void RootWindowWin::SetDeviceScaleFactor(float device_scale_factor) {
}
}
float RootWindowWin::GetDeviceScaleFactor() const {
std::optional<float> RootWindowWin::GetDeviceScaleFactor() const {
REQUIRE_MAIN_THREAD();
if (browser_window_ && with_osr_) {
return browser_window_->GetDeviceScaleFactor();
}
NOTREACHED();
return 0.0f;
return std::nullopt;
}
CefRefPtr<CefBrowser> RootWindowWin::GetBrowser() const {
@ -312,15 +467,15 @@ ClientWindowHandle RootWindowWin::GetWindowHandle() const {
bool RootWindowWin::WithWindowlessRendering() const {
REQUIRE_MAIN_THREAD();
DCHECK(initialized_);
return with_osr_;
}
void RootWindowWin::CreateBrowserWindow(const std::string& startup_url) {
if (with_osr_) {
OsrRendererSettings settings = {};
MainContext::Get()->PopulateOsrSettings(&settings);
MainContext::Get()->PopulateOsrSettings(&osr_settings_);
browser_window_ = std::make_unique<BrowserWindowOsrWin>(
this, with_controls_, startup_url, settings);
this, with_controls_, startup_url, osr_settings_);
} else {
browser_window_ = std::make_unique<BrowserWindowStdWin>(
this, with_controls_, startup_url);
@ -771,15 +926,27 @@ void RootWindowWin::OnSize(bool minimized) {
// Size the browser window to the whole client area.
browser_window_->SetBounds(0, 0, rect.right, rect.bottom);
}
MaybeNotifyScreenInfoChanged();
}
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) {
if (auto browser = GetBrowser()) {
browser->GetHost()->NotifyMoveOrResizeStarted();
}
MaybeNotifyScreenInfoChanged();
}
void RootWindowWin::MaybeNotifyScreenInfoChanged() {
if (!DefaultToContentBounds()) {
// Send the new root window bounds to the renderer.
if (auto browser = GetBrowser()) {
browser->GetHost()->NotifyScreenInfoChanged();
}
}
}
void RootWindowWin::OnDpiChanged(WPARAM wParam, LPARAM lParam) {
@ -788,6 +955,10 @@ void RootWindowWin::OnDpiChanged(WPARAM wParam, LPARAM lParam) {
return;
}
if (!hwnd_) {
return;
}
if (browser_window_ && with_osr_) {
// Scale factor for the new display.
const float display_scale_factor =
@ -797,8 +968,8 @@ void RootWindowWin::OnDpiChanged(WPARAM wParam, LPARAM lParam) {
// 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);
SetWindowPos(hwnd_, nullptr, rect->left, rect->top, rect->right - rect->left,
rect->bottom - rect->top, SWP_NOZORDER);
}
bool RootWindowWin::OnEraseBkgnd() {
@ -999,42 +1170,37 @@ void RootWindowWin::OnCreate(LPCREATESTRUCT lpCreateStruct) {
::SetMenu(hwnd_, nullptr);
}
const float device_scale_factor = GetWindowScaleFactor(hwnd_);
if (with_osr_) {
browser_window_->SetDeviceScaleFactor(device_scale_factor);
std::optional<float> parent_scale_factor;
if (is_popup_) {
if (auto parent_window =
MainContext::Get()->GetRootWindowManager()->GetWindowForBrowser(
opener_browser_id())) {
parent_scale_factor = parent_window->GetDeviceScaleFactor();
}
}
browser_window_->SetDeviceScaleFactor(
parent_scale_factor.value_or(initial_scale_factor_));
}
CefRect bounds(rect.left, rect.top, rect.right - rect.left,
rect.bottom - rect.top);
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,
browser_window_->CreateBrowser(hwnd_, bounds, browser_settings_, nullptr,
delegate_->GetRequestContext());
} 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);
browser_window_->ShowPopup(hwnd_, bounds.x, bounds.y, bounds.width,
bounds.height);
}
window_created_ = true;
}
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);
@ -1047,6 +1213,20 @@ bool RootWindowWin::OnClose() {
base::BindOnce(&RootWindowWin::SaveWindowRestoreOnUIThread, placement));
}
if (browser_window_ && !browser_window_->IsClosing()) {
CefRefPtr<CefBrowser> browser = GetBrowser();
if (browser) {
// Notify the browser window that we would like to close it. With Alloy
// style this will result in a call to ClientHandler::DoClose() if the
// JavaScript 'onbeforeunload' event handler allows it. With Chrome style
// this will close the window indirectly via browser destruction.
browser->GetHost()->CloseBrowser(false);
// Cancel the close.
return true;
}
}
// Allow the close.
return false;
}
@ -1124,29 +1304,18 @@ void RootWindowWin::OnAutoResize(const CefSize& new_size) {
return;
}
int new_width = new_size.width;
CefRect dip_bounds = {0, 0, new_size.width, new_size.height};
// 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,
GetWindowBoundsAndContinue(
dip_bounds, /*content_bounds=*/true,
base::BindOnce(
[](HWND hwnd, const CefRect& pixel_bounds) {
// Size the window and show if it's not currently visible.
SetWindowPos(
hwnd, nullptr, 0, 0, pixel_bounds.width, pixel_bounds.height,
SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
},
hwnd_));
}
void RootWindowWin::OnSetLoadingState(bool isLoading,

View File

@ -14,6 +14,7 @@
#include <string>
#include "tests/cefclient/browser/browser_window.h"
#include "tests/cefclient/browser/osr_renderer_settings.h"
#include "tests/cefclient/browser/root_window.h"
namespace client {
@ -40,10 +41,15 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
CefBrowserSettings& settings) override;
void Show(ShowMode mode) override;
void Hide() override;
void SetBounds(int x, int y, size_t width, size_t height) override;
void SetBounds(int x,
int y,
size_t width,
size_t height,
bool content_bounds) override;
bool DefaultToContentBounds() const override;
void Close(bool force) override;
void SetDeviceScaleFactor(float device_scale_factor) override;
float GetDeviceScaleFactor() const override;
std::optional<float> GetDeviceScaleFactor() const override;
CefRefPtr<CefBrowser> GetBrowser() const override;
ClientWindowHandle GetWindowHandle() const override;
bool WithWindowlessRendering() const override;
@ -58,6 +64,11 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
void CreateRootWindow(const CefBrowserSettings& settings,
bool initially_hidden);
void GetWindowBoundsAndContinue(
const CefRect& dip_bounds,
bool content_bounds,
base::OnceCallback<void(const CefRect& /*pixel_bounds*/)> next);
// Register the root window class.
static void RegisterRootClass(HINSTANCE hInstance,
const std::wstring& window_class,
@ -106,6 +117,10 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
void OnSetTitle(const std::string& title) override;
void OnSetFullscreen(bool fullscreen) override;
void OnAutoResize(const CefSize& new_size) override;
void OnContentsBounds(const CefRect& new_bounds) override {
RootWindow::SetBounds(new_bounds,
/*content_bounds=*/DefaultToContentBounds());
}
void OnSetLoadingState(bool isLoading,
bool canGoBack,
bool canGoForward) override;
@ -114,6 +129,8 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
void NotifyDestroyedIfDone();
void MaybeNotifyScreenInfoChanged();
static void SaveWindowRestoreOnUIThread(const WINDOWPLACEMENT& placement);
// After initialization all members are only accessed on the main thread.
@ -121,9 +138,11 @@ class RootWindowWin : public RootWindow, public BrowserWindow::Delegate {
bool with_controls_ = false;
bool always_on_top_ = false;
bool with_osr_ = false;
OsrRendererSettings osr_settings_;
bool is_popup_ = false;
CefRect initial_bounds_;
cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL;
float initial_scale_factor_ = 1.0;
std::unique_ptr<BrowserWindow> browser_window_;
CefBrowserSettings browser_settings_;

View File

@ -146,7 +146,9 @@ void RunNewWindowTest(CefRefPtr<CefBrowser> browser) {
void RunPopupWindowTest(CefRefPtr<CefBrowser> browser) {
browser->GetMainFrame()->ExecuteJavaScript(
"window.open('https://www.google.com');", "about:blank", 0);
"window.open('https://www.google.com', 'google', "
"'left=100,top=100,width=600,height=400');",
"about:blank", 0);
}
void RunDialogWindowTest(CefRefPtr<CefBrowser> browser) {
@ -269,7 +271,7 @@ void PromptDSF(CefRefPtr<CefBrowser> browser) {
// Format the default value string.
std::stringstream ss;
ss << RootWindow::GetForBrowser(browser->GetIdentifier())
ss << *RootWindow::GetForBrowser(browser->GetIdentifier())
->GetDeviceScaleFactor();
Prompt(browser, kPromptDSF, "Enter Device Scale Factor", ss.str());

View File

@ -30,4 +30,54 @@ ScopedGdkThreadsEnter::~ScopedGdkThreadsEnter() {
}
}
CefRect GetWindowBounds(GtkWindow* window, bool include_frame) {
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
gint x = 0;
gint y = 0;
gdk_window_get_origin(gdk_window, &x, &y);
gint width = 0;
gint height = 0;
gdk_window_get_geometry(gdk_window, nullptr, nullptr, &width, &height);
if (include_frame) {
GdkRectangle frame_rect = {};
gdk_window_get_frame_extents(gdk_window, &frame_rect);
// This calculation assumes that all added frame height is at the top of the
// window, which may be incorrect for some window managers.
y -= frame_rect.height - height;
}
return {x, y, width, height};
}
bool IsWindowMaximized(GtkWindow* window) {
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
gint state = gdk_window_get_state(gdk_window);
return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false;
}
void MinimizeWindow(GtkWindow* window) {
// Unmaximize the window before minimizing so restore behaves correctly.
if (IsWindowMaximized(window)) {
gtk_window_unmaximize(window);
}
gtk_window_iconify(window);
}
void MaximizeWindow(GtkWindow* window) {
gtk_window_maximize(window);
}
void RestoreWindow(GtkWindow* window) {
if (IsWindowMaximized(window)) {
gtk_window_unmaximize(window);
} else {
gtk_window_present(window);
}
}
} // namespace client

View File

@ -6,8 +6,11 @@
#define CEF_TESTS_CEFCLIENT_BROWSER_UTIL_GTK_H_
#pragma once
#include <gtk/gtk.h>
#include "include/base/cef_macros.h"
#include "include/base/cef_platform_thread.h"
#include "include/internal/cef_types_wrappers.h"
namespace client {
@ -36,6 +39,16 @@ class ScopedGdkThreadsEnter {
DISALLOW_COPY_AND_ASSIGN(ScopedGdkThreadsEnter);
};
// Returns the window bounds in root window (pixel) coordinates.
CefRect GetWindowBounds(GtkWindow* window, bool include_frame);
bool IsWindowMaximized(GtkWindow* window);
void MinimizeWindow(GtkWindow* window);
void MaximizeWindow(GtkWindow* window);
// Make sure the window isn't minimized or maximized.
void RestoreWindow(GtkWindow* window);
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_UTIL_GTK_H_

View File

@ -0,0 +1,23 @@
// Copyright (c) 2025 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_UTIL_MAC_H_
#define CEF_TESTS_CEFCLIENT_BROWSER_UTIL_MAC_H_
#pragma once
#include <optional>
#include <Cocoa/Cocoa.h>
#include "include/internal/cef_types_wrappers.h"
namespace client {
// Returns the current DIP screen bounds for a visible window in the restored
// position, or nullopt if the window is currently minimized or fullscreen.
std::optional<CefRect> GetWindowBoundsInScreen(NSWindow* window);
} // namespace client
#endif // CEF_TESTS_CEFCLIENT_BROWSER_UTIL_MAC_H_

View File

@ -0,0 +1,40 @@
// Copyright (c) 2025 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/util_mac.h"
namespace client {
std::optional<CefRect> GetWindowBoundsInScreen(NSWindow* window) {
if ([window isMiniaturized] or [window isZoomed]) {
return std::nullopt;
}
auto screen = [window screen];
if (screen == nil) {
screen = [NSScreen mainScreen];
}
const auto bounds = [window frame];
const auto screen_bounds = [screen frame];
if (NSEqualRects(bounds, screen_bounds)) {
// Don't include windows that are transitioning to fullscreen.
return std::nullopt;
}
CefRect dip_bounds{static_cast<int>(bounds.origin.x),
static_cast<int>(bounds.origin.y),
static_cast<int>(bounds.size.width),
static_cast<int>(bounds.size.height)};
// Convert from macOS coordinates (bottom-left origin) to DIP coordinates
// (top-left origin).
dip_bounds.y = static_cast<int>(screen_bounds.size.height) -
dip_bounds.height - dip_bounds.y;
return dip_bounds;
}
} // namespace client

View File

@ -17,6 +17,7 @@
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/resource.h"
#include "tests/cefclient/browser/views_style.h"
#include "tests/shared/browser/geometry_util.h"
#include "tests/shared/common/client_switches.h"
#if !defined(OS_WIN)
@ -229,7 +230,9 @@ void ViewsWindow::Maximize() {
void ViewsWindow::SetBounds(const CefRect& bounds) {
CEF_REQUIRE_UI_THREAD();
if (window_) {
window_->SetBounds(bounds);
auto window_bounds = bounds;
ConstrainWindowBounds(window_->GetDisplay()->GetWorkArea(), window_bounds);
window_->SetBounds(window_bounds);
}
}

View File

@ -13,6 +13,7 @@
#include "include/views/cef_browser_view.h"
#include "include/wrapper/cef_stream_resource_handler.h"
#include "tests/cefclient/browser/main_context.h"
#include "tests/cefclient/browser/root_window.h"
#include "tests/cefclient/browser/test_runner.h"
#include "tests/cefclient/browser/window_test_runner.h"
#include "tests/cefclient/browser/window_test_runner_views.h"
@ -40,7 +41,8 @@ const char kMessageTitlebarHeightName[] = "WindowTest.TitlebarHeight";
// Create the appropriate platform test runner object.
std::unique_ptr<WindowTestRunner> CreateWindowTestRunner(
CefRefPtr<CefBrowser> browser) {
if (CefBrowserView::GetForBrowser(browser)) {
auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier());
if (root_window->IsViewsHosted()) {
// Browser is Views-hosted.
return std::make_unique<WindowTestRunnerViews>();
}
@ -99,6 +101,20 @@ class Handler : public CefMessageRouterBrowserSide::Handler {
return false;
}
RunOnMainThread(browser, request, callback);
return true;
}
private:
static void RunOnMainThread(CefRefPtr<CefBrowser> browser,
const CefString& request,
CefRefPtr<Callback> callback) {
if (!CURRENTLY_ON_MAIN_THREAD()) {
MAIN_POST_CLOSURE(base::BindOnce(&Handler::RunOnMainThread, browser,
request, callback));
return;
}
auto runner = CreateWindowTestRunner(browser);
const std::string& message_name = request;
@ -123,7 +139,6 @@ class Handler : public CefMessageRouterBrowserSide::Handler {
}
callback->Success("");
return true;
}
};

View File

@ -4,35 +4,21 @@
#include "tests/cefclient/browser/window_test_runner.h"
#include "tests/cefclient/browser/root_window.h"
namespace client::window_test {
// static
void WindowTestRunner::ModifyBounds(const CefRect& display, CefRect& window) {
window.x += display.x;
window.y += display.y;
void WindowTestRunner::SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
REQUIRE_MAIN_THREAD();
if (window.x < display.x) {
window.x = display.x;
}
if (window.y < display.y) {
window.y = display.y;
}
if (window.width < 100) {
window.width = 100;
} else if (window.width >= display.width) {
window.width = display.width;
}
if (window.height < 100) {
window.height = 100;
} else if (window.height >= display.height) {
window.height = display.height;
}
if (window.x + window.width >= display.x + display.width) {
window.x = display.x + display.width - window.width;
}
if (window.y + window.height >= display.y + display.height) {
window.y = display.y + display.height - window.height;
}
auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier());
root_window->SetBounds(
x, y, width, height,
/*content_bounds=*/root_window->DefaultToContentBounds());
}
void WindowTestRunner::Fullscreen(CefRefPtr<CefBrowser> browser) {

View File

@ -13,25 +13,21 @@
namespace client::window_test {
// Implement this interface for different platforms. Methods will be called on
// the browser process UI thread unless otherwise indicated.
// the browser process main thread unless otherwise indicated.
class WindowTestRunner {
public:
virtual ~WindowTestRunner() = default;
virtual void SetPos(CefRefPtr<CefBrowser> browser,
void SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) = 0;
int height);
virtual void Minimize(CefRefPtr<CefBrowser> browser) = 0;
virtual void Maximize(CefRefPtr<CefBrowser> browser) = 0;
virtual void Restore(CefRefPtr<CefBrowser> browser) = 0;
virtual void Fullscreen(CefRefPtr<CefBrowser> browser);
// Fit |window| inside |display|. Coordinates are relative to the upper-left
// corner of the display.
static void ModifyBounds(const CefRect& display, CefRect& window);
virtual void SetTitleBarHeight(CefRefPtr<CefBrowser> browser,
const std::optional<float>& height);
};

View File

@ -29,113 +29,32 @@ GtkWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
return nullptr;
}
bool IsMaximized(GtkWindow* window) {
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
gint state = gdk_window_get_state(gdk_window);
return (state & GDK_WINDOW_STATE_MAXIMIZED) ? true : false;
}
void SetPosImpl(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser);
if (!window) {
return;
}
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
// Make sure the window isn't minimized or maximized.
if (IsMaximized(window)) {
gtk_window_unmaximize(window);
} else {
gtk_window_present(window);
}
// Retrieve information about the display that contains the window.
GdkScreen* screen = gdk_screen_get_default();
gint monitor = gdk_screen_get_monitor_at_window(screen, gdk_window);
GdkRectangle rect;
gdk_screen_get_monitor_geometry(screen, monitor, &rect);
// Make sure the window is inside the display.
CefRect display_rect(rect.x, rect.y, rect.width, rect.height);
CefRect window_rect(x, y, width, height);
WindowTestRunner::ModifyBounds(display_rect, window_rect);
gdk_window_move_resize(gdk_window, window_rect.x, window_rect.y,
window_rect.width, window_rect.height);
}
void MinimizeImpl(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser);
if (!window) {
return;
}
// Unmaximize the window before minimizing so restore behaves correctly.
if (IsMaximized(window)) {
gtk_window_unmaximize(window);
}
gtk_window_iconify(window);
}
void MaximizeImpl(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser);
if (!window) {
return;
}
gtk_window_maximize(window);
}
void RestoreImpl(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
GtkWindow* window = GetWindow(browser);
if (!window) {
return;
}
if (IsMaximized(window)) {
gtk_window_unmaximize(window);
} else {
gtk_window_present(window);
}
}
} // namespace
WindowTestRunnerGtk::WindowTestRunnerGtk() {}
void WindowTestRunnerGtk::SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
MAIN_POST_CLOSURE(base::BindOnce(SetPosImpl, browser, x, y, width, height));
}
WindowTestRunnerGtk::WindowTestRunnerGtk() = default;
void WindowTestRunnerGtk::Minimize(CefRefPtr<CefBrowser> browser) {
MAIN_POST_CLOSURE(base::BindOnce(MinimizeImpl, browser));
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
if (auto* window = GetWindow(browser)) {
MinimizeWindow(window);
}
}
void WindowTestRunnerGtk::Maximize(CefRefPtr<CefBrowser> browser) {
MAIN_POST_CLOSURE(base::BindOnce(MaximizeImpl, browser));
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
if (auto* window = GetWindow(browser)) {
MaximizeWindow(window);
}
}
void WindowTestRunnerGtk::Restore(CefRefPtr<CefBrowser> browser) {
MAIN_POST_CLOSURE(base::BindOnce(RestoreImpl, browser));
REQUIRE_MAIN_THREAD();
ScopedGdkThreadsEnter scoped_gdk_threads;
if (auto* window = GetWindow(browser)) {
RestoreWindow(window);
}
}
} // namespace window_test

View File

@ -16,11 +16,6 @@ class WindowTestRunnerGtk : public WindowTestRunner {
public:
WindowTestRunnerGtk();
void SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) override;
void Minimize(CefRefPtr<CefBrowser> browser) override;
void Maximize(CefRefPtr<CefBrowser> browser) override;
void Restore(CefRefPtr<CefBrowser> browser) override;

View File

@ -16,11 +16,6 @@ class WindowTestRunnerMac : public WindowTestRunner {
public:
WindowTestRunnerMac();
void SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) override;
void Minimize(CefRefPtr<CefBrowser> browser) override;
void Maximize(CefRefPtr<CefBrowser> browser) override;
void Restore(CefRefPtr<CefBrowser> browser) override;

View File

@ -22,48 +22,7 @@ NSWindow* GetWindow(CefRefPtr<CefBrowser> browser) {
} // namespace
WindowTestRunnerMac::WindowTestRunnerMac() {}
void WindowTestRunnerMac::SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
CEF_REQUIRE_UI_THREAD();
REQUIRE_MAIN_THREAD();
NSWindow* window = GetWindow(browser);
// Make sure the window isn't minimized or maximized.
if ([window isMiniaturized]) {
[window deminiaturize:nil];
} else if ([window isZoomed]) {
[window performZoom:nil];
}
// Retrieve information for the display that contains the window.
NSScreen* screen = [window screen];
if (screen == nil) {
screen = [NSScreen mainScreen];
}
NSRect frame = [screen frame];
NSRect visibleFrame = [screen visibleFrame];
// Make sure the window is inside the display.
CefRect display_rect(
visibleFrame.origin.x,
frame.size.height - visibleFrame.size.height - visibleFrame.origin.y,
visibleFrame.size.width, visibleFrame.size.height);
CefRect window_rect(x, y, width, height);
ModifyBounds(display_rect, window_rect);
NSRect newRect;
newRect.origin.x = window_rect.x;
newRect.origin.y = frame.size.height - window_rect.height - window_rect.y;
newRect.size.width = window_rect.width;
newRect.size.height = window_rect.height;
[window setFrame:newRect display:YES];
}
WindowTestRunnerMac::WindowTestRunnerMac() = default;
void WindowTestRunnerMac::Minimize(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();

View File

@ -10,6 +10,7 @@
#include "include/wrapper/cef_helpers.h"
#include "tests/cefclient/browser/root_window_views.h"
#include "tests/cefclient/browser/views_window.h"
#include "tests/shared/browser/geometry_util.h"
namespace client::window_test {
@ -28,46 +29,39 @@ CefRefPtr<CefWindow> GetWindow(const CefRefPtr<CefBrowser>& browser) {
return window;
}
void SetTitlebarHeight(const CefRefPtr<CefBrowser>& browser,
const std::optional<float>& height) {
CEF_REQUIRE_UI_THREAD();
auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier());
DCHECK(root_window.get());
auto root_window_views = static_cast<RootWindowViews*>(root_window.get());
root_window_views->SetTitlebarHeight(height);
void MinimizeImpl(CefRefPtr<CefBrowser> browser) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&MinimizeImpl, browser));
return;
}
} // namespace
WindowTestRunnerViews::WindowTestRunnerViews() = default;
void WindowTestRunnerViews::SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
CefRefPtr<CefWindow> window = GetWindow(browser);
CefRect window_bounds(x, y, width, height);
ModifyBounds(window->GetDisplay()->GetWorkArea(), window_bounds);
window->SetBounds(window_bounds);
}
void WindowTestRunnerViews::Minimize(CefRefPtr<CefBrowser> browser) {
GetWindow(browser)->Minimize();
}
void WindowTestRunnerViews::Maximize(CefRefPtr<CefBrowser> browser) {
void MaximizeImpl(CefRefPtr<CefBrowser> browser) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&MaximizeImpl, browser));
return;
}
GetWindow(browser)->Maximize();
}
void WindowTestRunnerViews::Restore(CefRefPtr<CefBrowser> browser) {
void RestoreImpl(CefRefPtr<CefBrowser> browser) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&RestoreImpl, browser));
return;
}
GetWindow(browser)->Restore();
}
void WindowTestRunnerViews::Fullscreen(CefRefPtr<CefBrowser> browser) {
void FullscreenImpl(CefRefPtr<CefBrowser> browser) {
if (!CefCurrentlyOn(TID_UI)) {
CefPostTask(TID_UI, base::BindOnce(&FullscreenImpl, browser));
return;
}
auto window = GetWindow(browser);
// Results in a call to ViewsWindow::OnWindowFullscreenTransition().
@ -78,10 +72,34 @@ void WindowTestRunnerViews::Fullscreen(CefRefPtr<CefBrowser> browser) {
}
}
} // namespace
WindowTestRunnerViews::WindowTestRunnerViews() = default;
void WindowTestRunnerViews::Minimize(CefRefPtr<CefBrowser> browser) {
MinimizeImpl(browser);
}
void WindowTestRunnerViews::Maximize(CefRefPtr<CefBrowser> browser) {
MaximizeImpl(browser);
}
void WindowTestRunnerViews::Restore(CefRefPtr<CefBrowser> browser) {
RestoreImpl(browser);
}
void WindowTestRunnerViews::Fullscreen(CefRefPtr<CefBrowser> browser) {
FullscreenImpl(browser);
}
void WindowTestRunnerViews::SetTitleBarHeight(
CefRefPtr<CefBrowser> browser,
const std::optional<float>& height) {
SetTitlebarHeight(browser, height);
REQUIRE_MAIN_THREAD();
auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier());
auto root_window_views = static_cast<RootWindowViews*>(root_window.get());
root_window_views->SetTitlebarHeight(height);
}
} // namespace client::window_test

View File

@ -15,11 +15,6 @@ class WindowTestRunnerViews : public WindowTestRunner {
public:
WindowTestRunnerViews();
void SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) override;
void Minimize(CefRefPtr<CefBrowser> browser) override;
void Maximize(CefRefPtr<CefBrowser> browser) override;
void Restore(CefRefPtr<CefBrowser> browser) override;

View File

@ -27,52 +27,13 @@ void Toggle(HWND root_hwnd, UINT nCmdShow) {
}
}
void SetPosImpl(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
HWND root_hwnd = GetRootHwnd(browser);
if (!root_hwnd) {
return;
}
} // namespace
// Retrieve current window placement information.
WINDOWPLACEMENT placement;
::GetWindowPlacement(root_hwnd, &placement);
WindowTestRunnerWin::WindowTestRunnerWin() = default;
// Retrieve information about the display that contains the window.
HMONITOR monitor =
MonitorFromRect(&placement.rcNormalPosition, MONITOR_DEFAULTTONEAREST);
MONITORINFO info;
info.cbSize = sizeof(info);
GetMonitorInfo(monitor, &info);
void WindowTestRunnerWin::Minimize(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
// Make sure the window is inside the display.
CefRect display_rect(info.rcWork.left, info.rcWork.top,
info.rcWork.right - info.rcWork.left,
info.rcWork.bottom - info.rcWork.top);
CefRect window_rect(x, y, width, height);
WindowTestRunner::ModifyBounds(display_rect, window_rect);
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;
placement.rcNormalPosition.right = window_rect.x + window_rect.width;
placement.rcNormalPosition.top = window_rect.y;
placement.rcNormalPosition.bottom = window_rect.y + window_rect.height;
::SetWindowPlacement(root_hwnd, &placement);
::ShowWindow(root_hwnd, SW_RESTORE);
} else {
// Set the window position.
::SetWindowPos(root_hwnd, nullptr, window_rect.x, window_rect.y,
window_rect.width, window_rect.height, SWP_NOZORDER);
}
}
void MinimizeImpl(CefRefPtr<CefBrowser> browser) {
HWND root_hwnd = GetRootHwnd(browser);
if (!root_hwnd) {
return;
@ -80,7 +41,9 @@ void MinimizeImpl(CefRefPtr<CefBrowser> browser) {
Toggle(root_hwnd, SW_MINIMIZE);
}
void MaximizeImpl(CefRefPtr<CefBrowser> browser) {
void WindowTestRunnerWin::Maximize(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
HWND root_hwnd = GetRootHwnd(browser);
if (!root_hwnd) {
return;
@ -88,7 +51,9 @@ void MaximizeImpl(CefRefPtr<CefBrowser> browser) {
Toggle(root_hwnd, SW_MAXIMIZE);
}
void RestoreImpl(CefRefPtr<CefBrowser> browser) {
void WindowTestRunnerWin::Restore(CefRefPtr<CefBrowser> browser) {
REQUIRE_MAIN_THREAD();
HWND root_hwnd = GetRootHwnd(browser);
if (!root_hwnd) {
return;
@ -96,48 +61,4 @@ void RestoreImpl(CefRefPtr<CefBrowser> browser) {
::ShowWindow(root_hwnd, SW_RESTORE);
}
} // namespace
WindowTestRunnerWin::WindowTestRunnerWin() = default;
void WindowTestRunnerWin::SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) {
if (CURRENTLY_ON_MAIN_THREAD()) {
SetPosImpl(browser, x, y, width, height);
} else {
// Execute on the main application thread.
MAIN_POST_CLOSURE(base::BindOnce(SetPosImpl, browser, x, y, width, height));
}
}
void WindowTestRunnerWin::Minimize(CefRefPtr<CefBrowser> browser) {
if (CURRENTLY_ON_MAIN_THREAD()) {
MinimizeImpl(browser);
} else {
// Execute on the main application thread.
MAIN_POST_CLOSURE(base::BindOnce(MinimizeImpl, browser));
}
}
void WindowTestRunnerWin::Maximize(CefRefPtr<CefBrowser> browser) {
if (CURRENTLY_ON_MAIN_THREAD()) {
MaximizeImpl(browser);
} else {
// Execute on the main application thread.
MAIN_POST_CLOSURE(base::BindOnce(MaximizeImpl, browser));
}
}
void WindowTestRunnerWin::Restore(CefRefPtr<CefBrowser> browser) {
if (CURRENTLY_ON_MAIN_THREAD()) {
RestoreImpl(browser);
} else {
// Execute on the main application thread.
MAIN_POST_CLOSURE(base::BindOnce(RestoreImpl, browser));
}
}
} // namespace client::window_test

View File

@ -16,11 +16,6 @@ class WindowTestRunnerWin : public WindowTestRunner {
public:
WindowTestRunnerWin();
void SetPos(CefRefPtr<CefBrowser> browser,
int x,
int y,
int width,
int height) override;
void Minimize(CefRefPtr<CefBrowser> browser) override;
void Maximize(CefRefPtr<CefBrowser> browser) override;
void Restore(CefRefPtr<CefBrowser> browser) override;

View File

@ -7,6 +7,12 @@
:fullscreen {
background: pink;
}
.bold {
font-weight: bold;
}
.mono {
font-family: monospace;
}
</style>
<script>
function setup() {
@ -74,11 +80,66 @@ function setTitlebarHeight() {
else
send_message('TitlebarHeight', height);
}
function execWindowAction() {
const dx = parseInt(document.getElementById('window_dx').value);
const dy = parseInt(document.getElementById('window_dy').value);
if (isNaN(dx) || isNaN(dy)) {
alert('Please specify a valid numeric value.');
return;
}
const e = document.getElementById("window_action");
const option = e.options[e.selectedIndex].text;
switch (option) {
case "moveTo":
return window.moveTo(dx, dy);
case "moveBy":
return window.moveBy(dx, dy);
case "resizeTo":
return window.resizeTo(dx, dy);
case "resizeBy":
return window.resizeBy(dx, dy);
}
}
</script>
</head>
<body bgcolor="white" onload="setup()">
<form id="form">
Click a button to perform the associated window action.
<p class="bold">Click a button to perform the associated window action.</p>
<p>Input values are in DIP coordinates. See <a href="https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-coordinate-systems" target="_new">here</a> for important details about coordinate systems and window behavior.</p>
<details>
<summary>Implementation details for cefclient (click to expand).</summary>
<ul>
<li>The below actions will configure window size (<span class="mono">window.outerWidth/Height</span>) and position (<span class="mono">window.screenX/Y</span>) as reported by browser content (see "Current window settings" below).</li>
<li>Whether this calculation includes the window frame will depend on what information is known/provided to CEF.</li>
<ul>
<li>Views-hosted windows are sized/positioned relative to the CefWindow frame.</li>
<li>Native windows (<span class="mono">--use-native</span>) are sized/positioned relative to the cefclient window frame.</li>
<ul>
<li>MacOS windows are sized/positioned relative to the parent NSWindow frame (default behavior).</li>
<li>Windows windows are sized/positioned relative to the root HWND frame (default behavior).</li>
<li>Linux windows are sized/positioned relative to CefDisplayHandler::GetRootWindowScreenRect.</li>
</ul>
<li>Windowless (off-screen) windows (<span class="mono">--off-screen-rendering-enabled</span>) are sized/positioned relative to CefRenderHandler::GetRootScreenRect.</li>
<ul>
<li>Pass <span class="mono">--fake-screen-bounds</span> to instead size/position relative to the browser content area.
This will also report (0,0) for position.</li>
</ul>
</ul>
<li>Exiting and relaunching cefclient will save/restore window bounds as DIP coordinates in all modes.</li>
<li>The <span class="mono">window.devicePixelRatio</span> value is a combination of device scale factor and browser zoom (if not 100%).</li>
<ul>
<li>Default device scale factor will match the associated display (e.g. user/system-level display settings).</li>
<li>Device scale factor can be configured via command-line (<span class="mono">--force-device-scale-factor=[float]</span>) or the "Tests &gt; Set Scale Factor" menu option with windowless rendering.</li>
<ul>
<li>MacOS custom device scale factor will impact rendering quality only. DIP coordinates are not impacted.</li>
<li>Windows/Linux custom device scale factor will impact rendering quality and DIP coordinates.</li>
</ul>
<li>Browser zoom can be configured via the Chrome menu or "Tests &gt; Zoom In/Out/Reset" menu options.
</ul>
</ul>
</details>
<br/><input type="button" onclick="minimize();" value="Minimize">
<br/><input type="button" onclick="maximize();" value="Maximize">
<br/><input type="button" onclick="restore();" value="Restore"> (minimizes and then restores the window as topmost)
@ -86,11 +147,55 @@ Click a button to perform the associated window action.
<br/><input type="button" onclick="fullscreenBrowser();" value="Toggle Browser Fullscreen"> (uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API" target="_new">Fullscreen API</a>; background turns pink)
<br/><input type="button" onclick="position();" value="Set Position">
X: <input type="text" size="4" id="x" value="200">
Y: <input type="text" size="4" id="y" value="100">
Y: <input type="text" size="4" id="y" value="200">
Width: <input type="text" size="4" id="width" value="800">
Height: <input type="text" size="4" id="height" value="600">
<br/><input type="button" onclick="setTitlebarHeight();" value="Set Titlebar Height">
<input type="number" min="0" max="100" id="title_bar_height" value="50"> (works on macOS with Views)
<br/><input type="button" onclick="execWindowAction();" value="Execute JavaScript"> window.<select id="window_action">
<option>moveTo</option>
<option>moveBy</option>
<option>resizeTo</option>
<option>resizeBy</option>
</select>(<input type="text" size="4" id="window_dx" value="200">,<input type="text" size="4" id="window_dy" value="200">) (calls CefDisplayHandler::OnContentsBoundsChange)
</form>
<p class="bold">Current window settings:</p>
<div id="size"></div>
<script>
function reportDimensions() {
document.getElementById('size').innerHTML =
`DIP coordinates:` +
`<br/>window.screen.width/height: ${window.screen.width} x ${window.screen.height}` +
`<br/>window.outerWidth/Height: ${window.outerWidth} x ${window.outerHeight}` +
`<br/>window.screenX/Y: x=${window.screenX}, y=${window.screenY}` +
`<br/><br/>CSS pixels:` +
`<br/>window.innerWidth/Height: ${window.innerWidth} x ${window.innerHeight}` +
`<br/>window.devicePixelRatio: ${window.devicePixelRatio.toFixed(2)}`;
}
// Observe window resize events.
window.addEventListener('resize', function() { console.log('resize'); reportDimensions(); });
if (window.onmove) {
// Observe window move events.
// Only available with --enable-experimental-web-platform-features.
window.addEventListener('move', reportDimensions);
} else {
// Poll for window movement.
var last_x = window.screenX;
var last_y = window.screenY;
setInterval(function() {
const x = window.screenX;
const y = window.screenY;
if (x != last_x || y != last_y) {
last_x = x;
last_y = y;
reportDimensions();
}
}, 500);
}
reportDimensions();
</script>
</body>
</html>

View File

@ -42,4 +42,21 @@ void DeviceToLogical(CefTouchEvent& value, float device_scale_factor) {
value.y = DeviceToLogical(value.y, device_scale_factor);
}
void ConstrainWindowBounds(const CefRect& display, CefRect& window) {
if (window.x < display.x) {
window.x = display.x;
}
if (window.y < display.y) {
window.y = display.y;
}
window.width = std::clamp(window.width, 100, display.width);
window.height = std::clamp(window.height, 100, display.height);
if (window.x + window.width >= display.x + display.width) {
window.x = display.x + display.width - window.width;
}
if (window.y + window.height >= display.y + display.height) {
window.y = display.y + display.height - window.height;
}
}
} // namespace client

View File

@ -20,6 +20,10 @@ CefRect DeviceToLogical(const CefRect& value, float device_scale_factor);
void DeviceToLogical(CefMouseEvent& value, float device_scale_factor);
void DeviceToLogical(CefTouchEvent& value, float device_scale_factor);
// Fit |window| inside |display|. Coordinates are relative to the upper-left
// corner of the display.
void ConstrainWindowBounds(const CefRect& display, CefRect& window);
} // namespace client
#endif // CEF_TESTS_SHARED_BROWSER_GEOMETRY_UTIL_H_

View File

@ -24,6 +24,7 @@ const char kOffScreenRenderingEnabled[] = "off-screen-rendering-enabled";
const char kOffScreenFrameRate[] = "off-screen-frame-rate";
const char kTransparentPaintingEnabled[] = "transparent-painting-enabled";
const char kShowUpdateRect[] = "show-update-rect";
const char kFakeScreenBounds[] = "fake-screen-bounds";
const char kSharedTextureEnabled[] = "shared-texture-enabled";
const char kExternalBeginFrameEnabled[] = "external-begin-frame-enabled";
const char kMouseCursorChangeDisabled[] = "mouse-cursor-change-disabled";

View File

@ -18,6 +18,7 @@ extern const char kOffScreenRenderingEnabled[];
extern const char kOffScreenFrameRate[];
extern const char kTransparentPaintingEnabled[];
extern const char kShowUpdateRect[];
extern const char kFakeScreenBounds[];
extern const char kSharedTextureEnabled[];
extern const char kExternalBeginFrameEnabled[];
extern const char kMouseCursorChangeDisabled[];