diff --git a/cef_paths2.gypi b/cef_paths2.gypi index 41be58245..8306e352b 100644 --- a/cef_paths2.gypi +++ b/cef_paths2.gypi @@ -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', diff --git a/include/cef_browser.h b/include/cef_browser.h index 3301d51a8..d349e8620 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -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; diff --git a/include/cef_display_handler.h b/include/cef_display_handler.h index 79617b77f..d3981d3d3 100644 --- a/include/cef_display_handler.h +++ b/include/cef_display_handler.h @@ -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 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 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 browser, + CefRect& rect) { + return false; + } +#endif }; #endif // CEF_INCLUDE_CEF_DISPLAY_HANDLER_H_ diff --git a/include/views/cef_display.h b/include/views/cef_display.h index 4010cae96..311a8dfa9 100644 --- a/include/views/cef_display.h +++ b/include/views/cef_display.h @@ -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; diff --git a/libcef/browser/alloy/alloy_browser_host_impl.cc b/libcef/browser/alloy/alloy_browser_host_impl.cc index 2019d0cbf..dba2d9a68 100644 --- a/libcef/browser/alloy/alloy_browser_host_impl.cc +++ b/libcef/browser/alloy/alloy_browser_host_impl.cc @@ -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); diff --git a/libcef/browser/alloy/alloy_browser_host_impl.h b/libcef/browser/alloy/alloy_browser_host_impl.h index 04be12c2d..c208f08ed 100644 --- a/libcef/browser/alloy/alloy_browser_host_impl.h +++ b/libcef/browser/alloy/alloy_browser_host_impl.h @@ -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, diff --git a/libcef/browser/browser_contents_delegate.cc b/libcef/browser/browser_contents_delegate.cc index 7815b2985..6b0525917 100644 --- a/libcef/browser/browser_contents_delegate.cc +++ b/libcef/browser/browser_contents_delegate.cc @@ -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) { diff --git a/libcef/browser/browser_contents_delegate.h b/libcef/browser/browser_contents_delegate.h index 7bd3a7122..9cc5822db 100644 --- a/libcef/browser/browser_contents_delegate.h +++ b/libcef/browser/browser_contents_delegate.h @@ -87,6 +87,10 @@ class CefBrowserContentsDelegate : public content::WebContentsDelegate, base::OnceCallback& 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; diff --git a/libcef/browser/browser_host_base.cc b/libcef/browser/browser_host_base.cc index a9220e3dd..634446fc8 100644 --- a/libcef/browser/browser_host_base.cc +++ b/libcef/browser/browser_host_base.cc @@ -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"; diff --git a/libcef/browser/browser_host_base.h b/libcef/browser/browser_host_base.h index a0e1c184f..66d1cc4fd 100644 --- a/libcef/browser/browser_host_base.h +++ b/libcef/browser/browser_host_base.h @@ -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; diff --git a/libcef/browser/browser_platform_delegate.cc b/libcef/browser/browser_platform_delegate.cc index 3f940c250..f502f44e8 100644 --- a/libcef/browser/browser_platform_delegate.cc +++ b/libcef/browser/browser_platform_delegate.cc @@ -244,8 +244,7 @@ void CefBrowserPlatformDelegate::PopupBrowserCreated( return; } - CefRefPtr 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); diff --git a/libcef/browser/browser_platform_delegate.h b/libcef/browser/browser_platform_delegate.h index 6b3f6cde3..86563f59c 100644 --- a/libcef/browser/browser_platform_delegate.h +++ b/libcef/browser/browser_platform_delegate.h @@ -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. diff --git a/libcef/browser/chrome/browser_delegate.h b/libcef/browser/chrome/browser_delegate.h index c441a141c..50477dd10 100644 --- a/libcef/browser/chrome/browser_delegate.h +++ b/libcef/browser/chrome/browser_delegate.h @@ -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 diff --git a/libcef/browser/chrome/chrome_browser_delegate.cc b/libcef/browser/chrome/chrome_browser_delegate.cc index 4ba866483..34b32ec14 100644 --- a/libcef/browser/chrome/chrome_browser_delegate.cc +++ b/libcef/browser/chrome/chrome_browser_delegate.cc @@ -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)) { diff --git a/libcef/browser/chrome/chrome_browser_delegate.h b/libcef/browser/chrome/chrome_browser_delegate.h index 229f644a5..cf6a29d69 100644 --- a/libcef/browser/chrome/chrome_browser_delegate.h +++ b/libcef/browser/chrome/chrome_browser_delegate.h @@ -94,6 +94,8 @@ class ChromeBrowserDelegate : public cef::BrowserDelegate { const content::OpenURLParams& params, base::OnceCallback& navigation_handle_callback) override; + bool SetContentsBoundsEx(content::WebContents* source, + const gfx::Rect& bounds) override; // WebContentsDelegate methods: void WebContentsCreated(content::WebContents* source_contents, diff --git a/libcef/browser/chrome/chrome_browser_host_impl.cc b/libcef/browser/chrome/chrome_browser_host_impl.cc index bea3c3fd4..da22e6033 100644 --- a/libcef/browser/chrome/chrome_browser_host_impl.cc +++ b/libcef/browser/chrome/chrome_browser_host_impl.cc @@ -250,10 +250,6 @@ void ChromeBrowserHostImpl::WasHidden(bool hidden) { NOTIMPLEMENTED(); } -void ChromeBrowserHostImpl::NotifyScreenInfoChanged() { - NOTIMPLEMENTED(); -} - void ChromeBrowserHostImpl::Invalidate(PaintElementType type) { NOTIMPLEMENTED(); } diff --git a/libcef/browser/chrome/chrome_browser_host_impl.h b/libcef/browser/chrome/chrome_browser_host_impl.h index 211eea8bc..00178f9ba 100644 --- a/libcef/browser/chrome/chrome_browser_host_impl.h +++ b/libcef/browser/chrome/chrome_browser_host_impl.h @@ -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; diff --git a/libcef/browser/chrome/views/chrome_child_window.cc b/libcef/browser/chrome/views/chrome_child_window.cc index 360f85f56..0daaa35a2 100644 --- a/libcef/browser/chrome/views/chrome_child_window.cc +++ b/libcef/browser/chrome/views/chrome_child_window.cc @@ -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 window) override { browser_view_ = nullptr; window_ = nullptr; +#if BUILDFLAG(IS_WIN) + native_delegate_ = nullptr; +#endif } CefRect GetInitialBounds(CefRefPtr window) override { @@ -71,22 +78,38 @@ class ChildWindowDelegate : public CefWindowDelegate { return initial_bounds; } +#if defined(USE_AURA) + void OnWindowBoundsChanged(CefRefPtr 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(platform_delegate); + native_delegate_ = static_cast( + chrome_delegate->native_delegate()); + native_delegate_->InstallRootWindowBoundsCallback(); + #if BUILDFLAG(IS_WIN) auto widget = static_cast(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(platform_delegate); - auto native_delegate = static_cast( - 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(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 browser_view, const CefWindowInfo& window_info) : browser_view_(browser_view), window_info_(window_info) {} @@ -122,6 +145,11 @@ class ChildWindowDelegate : public CefWindowDelegate { CefRefPtr window_; +#if defined(USE_AURA) + base::raw_ptr native_delegate_ = + nullptr; +#endif + IMPLEMENT_REFCOUNTING(ChildWindowDelegate); }; diff --git a/libcef/browser/native/browser_platform_delegate_native.cc b/libcef/browser/native/browser_platform_delegate_native.cc index 2a7baaf6b..0705366c4 100644 --- a/libcef/browser/native/browser_platform_delegate_native.cc +++ b/libcef/browser/native/browser_platform_delegate_native.cc @@ -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(); +} diff --git a/libcef/browser/native/browser_platform_delegate_native.h b/libcef/browser/native/browser_platform_delegate_native.h index 645b943f6..0c5a71399 100644 --- a/libcef/browser/native/browser_platform_delegate_native.h +++ b/libcef/browser/native/browser_platform_delegate_native.h @@ -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( diff --git a/libcef/browser/native/browser_platform_delegate_native_aura.cc b/libcef/browser/native/browser_platform_delegate_native_aura.cc index 6b1253987..c500a813d 100644 --- a/libcef/browser/native/browser_platform_delegate_native_aura.cc +++ b/libcef/browser/native/browser_platform_delegate_native_aura.cc @@ -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 self) { + return self->RootWindowBoundsCallback(); + }, + weak_ptr_factory_.GetWeakPtr())); +} + +std::optional +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(); diff --git a/libcef/browser/native/browser_platform_delegate_native_aura.h b/libcef/browser/native/browser_platform_delegate_native_aura.h index 3bab3b688..af6074ac9 100644 --- a/libcef/browser/native/browser_platform_delegate_native_aura.h +++ b/libcef/browser/native/browser_platform_delegate_native_aura.h @@ -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 + #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 GetRootWindowBounds() { + return std::nullopt; + } + protected: base::OnceClosure GetWidgetDeleteCallback(); + std::optional RootWindowBoundsCallback(); + static base::TimeTicks GetEventTimeStamp(); static int TranslateUiEventModifiers(uint32_t cef_modifiers); static int TranslateUiChangedButtonFlags(uint32_t cef_modifiers); diff --git a/libcef/browser/native/browser_platform_delegate_native_win.cc b/libcef/browser/native/browser_platform_delegate_native_win.cc index 133033808..81e6b6351 100644 --- a/libcef/browser/native/browser_platform_delegate_native_win.cc +++ b/libcef/browser/native/browser_platform_delegate_native_win.cc @@ -427,6 +427,27 @@ CefEventHandle CefBrowserPlatformDelegateNativeWin::GetEventHandle( const_cast(&event.os_event->native_event())); } +std::optional +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; diff --git a/libcef/browser/native/browser_platform_delegate_native_win.h b/libcef/browser/native/browser_platform_delegate_native_win.h index bf59d1e62..79eaa334b 100644 --- a/libcef/browser/native/browser_platform_delegate_native_win.h +++ b/libcef/browser/native/browser_platform_delegate_native_win.h @@ -32,6 +32,7 @@ class CefBrowserPlatformDelegateNativeWin bool HandleKeyboardEvent(const input::NativeWebKeyboardEvent& event) override; CefEventHandle GetEventHandle( const input::NativeWebKeyboardEvent& event) const override; + std::optional GetRootWindowBounds() override; // CefBrowserPlatformDelegateNativeAura methods: ui::KeyEvent TranslateUiKeyEvent(const CefKeyEvent& key_event) const override; diff --git a/patch/patch.cfg b/patch/patch.cfg index e2a4aca58..125c9fd5e 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -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 diff --git a/patch/patches/chrome_browser_browser.patch b/patch/patches/chrome_browser_browser.patch index a251fd95a..eed2479f0 100644 --- a/patch/patches/chrome_browser_browser.patch +++ b/patch/patches/chrome_browser_browser.patch @@ -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 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 -@@ -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 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 Browser::GetStatusBubbles() { +@@ -3323,6 +3475,12 @@ std::vector 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 { diff --git a/patch/patches/rwh_background_color_1984.patch b/patch/patches/renderer_host_aura.patch similarity index 53% rename from patch/patches/rwh_background_color_1984.patch rename to patch/patches/renderer_host_aura.patch index a6302fbee..0a11024d5 100644 --- a/patch/patches/rwh_background_color_1984.patch +++ b/patch/patches/renderer_host_aura.patch @@ -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 +@@ -8,6 +8,7 @@ #include -+#include #include #include ++#include #include + + #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()>; ++ void SetRootWindowBoundsCallback(const RootWindowBoundsCallback& callback) { ++ root_window_bounds_callback_ = callback; ++ } ++ + protected: + ~RenderWidgetHostViewAura() override; + +@@ -874,6 +880,8 @@ class CONTENT_EXPORT RenderWidgetHostViewAura + + std::optional display_observer_; + ++ RootWindowBoundsCallback root_window_bounds_callback_; ++ + base::WeakPtrFactory weak_ptr_factory_{this}; + }; + diff --git a/tests/cefclient/browser/browser_window.cc b/tests/cefclient/browser/browser_window.cc index 2a7b79c9d..3a895c2ee 100644 --- a/tests/cefclient/browser/browser_window.cc +++ b/tests/cefclient/browser/browser_window.cc @@ -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) { diff --git a/tests/cefclient/browser/browser_window.h b/tests/cefclient/browser/browser_window.h index 0dff46688..7db13017a 100644 --- a/tests/cefclient/browser/browser_window.h +++ b/tests/cefclient/browser/browser_window.h @@ -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& 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& regions) override; - Delegate* delegate_; + Delegate* const delegate_; CefRefPtr browser_; CefRefPtr client_handler_; bool is_closing_ = false; diff --git a/tests/cefclient/browser/browser_window_osr_gtk.cc b/tests/cefclient/browser/browser_window_osr_gtk.cc index 1183799c0..0a25b8829 100644 --- a/tests/cefclient/browser/browser_window_osr_gtk.cc +++ b/tests/cefclient/browser/browser_window_osr_gtk.cc @@ -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 browser) { bool BrowserWindowOsrGtk::GetRootScreenRect(CefRefPtr browser, CefRect& rect) { CEF_REQUIRE_UI_THREAD(); - return false; + + 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 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 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 browser, @@ -1204,9 +1232,6 @@ bool BrowserWindowOsrGtk::GetScreenInfo(CefRefPtr 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 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. - screen_info.rect = view_rect; - screen_info.available_rect = view_rect; + 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; } diff --git a/tests/cefclient/browser/browser_window_osr_mac.mm b/tests/cefclient/browser/browser_window_osr_mac.mm index dc0b7ac79..0566c0c50 100644 --- a/tests/cefclient/browser/browser_window_osr_mac.mm +++ b/tests/cefclient/browser/browser_window_osr_mac.mm @@ -9,14 +9,18 @@ #include #import +#include + #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,11 +1214,13 @@ NSPoint ConvertPointFromWindowToScreen(NSWindow* window, NSPoint point) { return viewPoint; } -- (void)resetDeviceScaleFactor { - float device_scale_factor = 1.0f; - NSWindow* window = [self window]; - if (window) { - device_scale_factor = [window backingScaleFactor]; +- (void)resetDeviceScaleFactor:(std::optional)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 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,7 +1599,25 @@ void BrowserWindowOsrMacImpl::OnBeforeClose(CefRefPtr browser) { bool BrowserWindowOsrMacImpl::GetRootScreenRect(CefRefPtr browser, CefRect& rect) { CEF_REQUIRE_UI_THREAD(); - return false; + 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 browser, @@ -1598,6 +1625,7 @@ void BrowserWindowOsrMacImpl::GetViewRect(CefRefPtr browser, 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 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 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 browser, return false; } - 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. - screen_info.rect = view_rect; - screen_info.available_rect = view_rect; + 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; } @@ -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_ diff --git a/tests/cefclient/browser/browser_window_osr_win.cc b/tests/cefclient/browser/browser_window_osr_win.cc index 023084975..1078bee6a 100644 --- a/tests/cefclient/browser/browser_window_osr_win.cc +++ b/tests/cefclient/browser/browser_window_osr_win.cc @@ -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; } diff --git a/tests/cefclient/browser/client_handler.cc b/tests/cefclient/browser/client_handler.cc index 8bf45f700..e4748aa14 100644 --- a/tests/cefclient/browser/client_handler.cc +++ b/tests/cefclient/browser/client_handler.cc @@ -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 browser, return mouse_cursor_change_disabled_; } +#if CEF_API_ADDED(CEF_NEXT) +bool ClientHandler::OnContentsBoundsChange(CefRefPtr browser, + const CefRect& new_bounds) { + CEF_REQUIRE_UI_THREAD(); + NotifyContentsBounds(new_bounds); + return true; +} + +bool ClientHandler::GetRootWindowScreenRect(CefRefPtr browser, + CefRect& rect) { + CEF_REQUIRE_UI_THREAD(); + if (delegate_) { + return delegate_->GetRootWindowScreenRect(rect); + } + return false; +} +#endif + bool ClientHandler::CanDownload(CefRefPtr 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) { diff --git a/tests/cefclient/browser/client_handler.h b/tests/cefclient/browser/client_handler.h index fc50cd81f..daad2aa7c 100644 --- a/tests/cefclient/browser/client_handler.h +++ b/tests/cefclient/browser/client_handler.h @@ -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 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 browser, + const CefRect& new_bounds) override; + bool GetRootWindowScreenRect(CefRefPtr browser, + CefRect& rect) override; +#endif // CefDownloadHandler methods bool CanDownload(CefRefPtr browser, @@ -345,6 +358,7 @@ class ClientHandler : public BaseClientHandler, void NotifyFavicon(CefRefPtr 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& regions); void NotifyTakeFocus(bool next); @@ -396,11 +410,7 @@ class ClientHandler : public BaseClientHandler, CefRefPtr 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 diff --git a/tests/cefclient/browser/main_context_impl.cc b/tests/cefclient/browser/main_context_impl.cc index af6a956ff..2294deecc 100644 --- a/tests/cefclient/browser/main_context_impl.cc +++ b/tests/cefclient/browser/main_context_impl.cc @@ -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_; diff --git a/tests/cefclient/browser/osr_renderer_settings.h b/tests/cefclient/browser/osr_renderer_settings.h index 4b5b94add..8d18f0108 100644 --- a/tests/cefclient/browser/osr_renderer_settings.h +++ b/tests/cefclient/browser/osr_renderer_settings.h @@ -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; diff --git a/tests/cefclient/browser/osr_window_win.cc b/tests/cefclient/browser/osr_window_win.cc index 8e38a505f..db6f6b590 100644 --- a/tests/cefclient/browser/osr_window_win.cc +++ b/tests/cefclient/browser/osr_window_win.cc @@ -13,6 +13,7 @@ #include #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 browser) { bool OsrWindowWin::GetRootScreenRect(CefRefPtr browser, CefRect& rect) { CEF_REQUIRE_UI_THREAD(); - return false; + 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 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, - device_scale_factor_); + 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 browser, @@ -1019,15 +1023,24 @@ bool OsrWindowWin::GetScreenInfo(CefRefPtr browser, return false; } - 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. - screen_info.rect = view_rect; - screen_info.available_rect = view_rect; + 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); + + // Keep HTML select popups inside the view rectangle. + screen_info.rect = view_rect; + screen_info.available_rect = view_rect; + } return true; } diff --git a/tests/cefclient/browser/root_window.h b/tests/cefclient/browser/root_window.h index 6a1a5e00a..775863b3f 100644 --- a/tests/cefclient/browser/root_window.h +++ b/tests/cefclient/browser/root_window.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -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 GetDeviceScaleFactor() const = 0; // Returns the browser that this window contains, if any. virtual CefRefPtr GetBrowser() const = 0; diff --git a/tests/cefclient/browser/root_window_gtk.cc b/tests/cefclient/browser/root_window_gtk.cc index 2a61deb76..24df340a0 100644 --- a/tests/cefclient/browser/root_window_gtk.cc +++ b/tests/cefclient/browser/root_window_gtk.cc @@ -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& 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(); +} + +CefRect GetScreenPixelBounds(const CefRect& dip_bounds, + const std::optional& device_scale_factor) { + const auto scale_factor = + GetScaleFactor(dip_bounds, device_scale_factor, /*pixel_bounds=*/false); + return LogicalToDevice(dip_bounds, scale_factor); +} + +CefRect GetScreenDIPBounds(const CefRect& pixel_bounds, + const std::optional& 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& 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& bounds_info, + const std::optional& device_scale_factor, + base::OnceCallback next) { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&GetPixelBoundsAndContinue, dip_bounds, + bounds_info, device_scale_factor, + std::move(next))); + return; } - gtk_window_iconify(window); + 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 MaximizeWindow(GtkWindow* window) { - gtk_window_maximize(window); +void SaveWindowRestoreContinue( + cef_show_state_t show_state, + const CefRect& pixel_bounds, + const std::optional& 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& 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 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 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 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& 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(width), static_cast(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 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 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 RootWindowGtk::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) { return browser_window_->GetDeviceScaleFactor(); } - NOTREACHED(); - return 0.0f; + return std::nullopt; } CefRefPtr 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 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; } - ScopedGdkThreadsEnter scoped_gdk_threads; + CefRect dip_bounds{0, 0, new_size.width, new_size.height}; - 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); + GetWindowBoundsAndContinue( + dip_bounds, /*content_bounds=*/true, + base::BindOnce( + [](GtkWidget* window, const CefRect& pixel_bounds) { + ScopedGdkThreadsEnter scoped_gdk_threads; + 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 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 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 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 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. diff --git a/tests/cefclient/browser/root_window_gtk.h b/tests/cefclient/browser/root_window_gtk.h index 568c2088b..87dc9afc5 100644 --- a/tests/cefclient/browser/root_window_gtk.h +++ b/tests/cefclient/browser/root_window_gtk.h @@ -12,6 +12,7 @@ #include #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 GetDeviceScaleFactor() const override; CefRefPtr GetBrowser() const override; ClientWindowHandle GetWindowHandle() const override; bool WithWindowlessRendering() const override; private: + void ContinueInitOnUIThread(std::unique_ptr config, + const CefBrowserSettings& settings); + void ContinueInitOnMainThread(std::unique_ptr 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& regions) override; + bool GetRootWindowScreenRect(CefRect& rect) override; + + void GetWindowBoundsAndContinue( + const CefRect& dip_bounds, + bool content_bounds, + base::OnceCallback 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 browser_window_; // Main window. diff --git a/tests/cefclient/browser/root_window_mac.h b/tests/cefclient/browser/root_window_mac.h index 62d9a0fef..03c8d0489 100644 --- a/tests/cefclient/browser/root_window_mac.h +++ b/tests/cefclient/browser/root_window_mac.h @@ -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 GetDeviceScaleFactor() const override; CefRefPtr 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; diff --git a/tests/cefclient/browser/root_window_mac.mm b/tests/cefclient/browser/root_window_mac.mm index 1f7b98d22..59d8e7499 100644 --- a/tests/cefclient/browser/root_window_mac.mm +++ b/tests/cefclient/browser/root_window_mac.mm @@ -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 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(bounds.origin.x), - static_cast(bounds.origin.y), - static_cast(bounds.size.width), - static_cast(bounds.size.height)}; - - // Convert from macOS coordinates (bottom-left origin) to DIP coordinates - // (top-left origin). - dip_bounds.y = static_cast(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 GetDeviceScaleFactor() const; CefRefPtr 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(width), static_cast(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 RootWindowMacImpl::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) { return browser_window_->GetDeviceScaleFactor(); } - NOTREACHED(); - return 0.0f; + return std::nullopt; } CefRefPtr 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 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(new_size.width); - content_rect.size.height = - static_cast(new_size.height) + (with_controls_ ? URLBAR_HEIGHT : 0); + CefRect dip_bounds(0, 0, static_cast(new_size.width), + static_cast(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 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 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. diff --git a/tests/cefclient/browser/root_window_views.cc b/tests/cefclient/browser/root_window_views.cc index 69f378ac9..a58c0ac93 100644 --- a/tests/cefclient/browser/root_window_views.cc +++ b/tests/cefclient/browser/root_window_views.cc @@ -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 RootWindowViews::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); // Windowless rendering is not supported. NOTREACHED(); - return 0.0; + return std::nullopt; } CefRefPtr RootWindowViews::GetBrowser() const { diff --git a/tests/cefclient/browser/root_window_views.h b/tests/cefclient/browser/root_window_views.h index 43dc7ea17..a14ff621f 100644 --- a/tests/cefclient/browser/root_window_views.h +++ b/tests/cefclient/browser/root_window_views.h @@ -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 GetDeviceScaleFactor() const override; CefRefPtr GetBrowser() const override; ClientWindowHandle GetWindowHandle() const override; bool WithWindowlessRendering() const override { return false; } @@ -80,6 +85,10 @@ class RootWindowViews : public RootWindow, void OnSetFavicon(CefRefPtr 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; diff --git a/tests/cefclient/browser/root_window_win.cc b/tests/cefclient/browser/root_window_win.cc index 48c877624..e7f5615aa 100644 --- a/tests/cefclient/browser/root_window_win.cc +++ b/tests/cefclient/browser/root_window_win.cc @@ -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& 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& 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& 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& device_scale_factor, + base::OnceCallback 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,8 +266,17 @@ void RootWindowWin::ContinueInitOnUIThread( } } - MAIN_POST_CLOSURE(base::BindOnce(&RootWindowWin::ContinueInitOnMainThread, - this, std::move(config), settings)); + 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( @@ -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(width), - static_cast(height), SWP_NOZORDER); + if (!hwnd_) { + return; } + + CefRect dip_bounds = {x, y, static_cast(width), + static_cast(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 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 RootWindowWin::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) { return browser_window_->GetDeviceScaleFactor(); } - NOTREACHED(); - return 0.0f; + return std::nullopt; } CefRefPtr 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( - this, with_controls_, startup_url, settings); + this, with_controls_, startup_url, osr_settings_); } else { browser_window_ = std::make_unique( 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 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(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 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 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 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, - SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_SHOWWINDOW); + 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, diff --git a/tests/cefclient/browser/root_window_win.h b/tests/cefclient/browser/root_window_win.h index b1745442d..91c1e9eb5 100644 --- a/tests/cefclient/browser/root_window_win.h +++ b/tests/cefclient/browser/root_window_win.h @@ -14,6 +14,7 @@ #include #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 GetDeviceScaleFactor() const override; CefRefPtr 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 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 browser_window_; CefBrowserSettings browser_settings_; diff --git a/tests/cefclient/browser/test_runner.cc b/tests/cefclient/browser/test_runner.cc index 501e03754..0103cd880 100644 --- a/tests/cefclient/browser/test_runner.cc +++ b/tests/cefclient/browser/test_runner.cc @@ -146,7 +146,9 @@ void RunNewWindowTest(CefRefPtr browser) { void RunPopupWindowTest(CefRefPtr 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 browser) { @@ -269,8 +271,8 @@ void PromptDSF(CefRefPtr browser) { // Format the default value string. std::stringstream ss; - ss << RootWindow::GetForBrowser(browser->GetIdentifier()) - ->GetDeviceScaleFactor(); + ss << *RootWindow::GetForBrowser(browser->GetIdentifier()) + ->GetDeviceScaleFactor(); Prompt(browser, kPromptDSF, "Enter Device Scale Factor", ss.str()); } diff --git a/tests/cefclient/browser/util_gtk.cc b/tests/cefclient/browser/util_gtk.cc index be5e30b67..0848ab3fb 100644 --- a/tests/cefclient/browser/util_gtk.cc +++ b/tests/cefclient/browser/util_gtk.cc @@ -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 diff --git a/tests/cefclient/browser/util_gtk.h b/tests/cefclient/browser/util_gtk.h index c8836fefb..2a5d758f7 100644 --- a/tests/cefclient/browser/util_gtk.h +++ b/tests/cefclient/browser/util_gtk.h @@ -6,8 +6,11 @@ #define CEF_TESTS_CEFCLIENT_BROWSER_UTIL_GTK_H_ #pragma once +#include + #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_ diff --git a/tests/cefclient/browser/util_mac.h b/tests/cefclient/browser/util_mac.h new file mode 100644 index 000000000..a055d62c6 --- /dev/null +++ b/tests/cefclient/browser/util_mac.h @@ -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 + +#include + +#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 GetWindowBoundsInScreen(NSWindow* window); + +} // namespace client + +#endif // CEF_TESTS_CEFCLIENT_BROWSER_UTIL_MAC_H_ diff --git a/tests/cefclient/browser/util_mac.mm b/tests/cefclient/browser/util_mac.mm new file mode 100644 index 000000000..94756a196 --- /dev/null +++ b/tests/cefclient/browser/util_mac.mm @@ -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 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(bounds.origin.x), + static_cast(bounds.origin.y), + static_cast(bounds.size.width), + static_cast(bounds.size.height)}; + + // Convert from macOS coordinates (bottom-left origin) to DIP coordinates + // (top-left origin). + dip_bounds.y = static_cast(screen_bounds.size.height) - + dip_bounds.height - dip_bounds.y; + + return dip_bounds; +} + +} // namespace client diff --git a/tests/cefclient/browser/views_window.cc b/tests/cefclient/browser/views_window.cc index 9cdd21d15..f7873e607 100644 --- a/tests/cefclient/browser/views_window.cc +++ b/tests/cefclient/browser/views_window.cc @@ -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); } } diff --git a/tests/cefclient/browser/window_test.cc b/tests/cefclient/browser/window_test.cc index ed75da706..6ac14ebbe 100644 --- a/tests/cefclient/browser/window_test.cc +++ b/tests/cefclient/browser/window_test.cc @@ -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 CreateWindowTestRunner( CefRefPtr browser) { - if (CefBrowserView::GetForBrowser(browser)) { + auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier()); + if (root_window->IsViewsHosted()) { // Browser is Views-hosted. return std::make_unique(); } @@ -99,6 +101,20 @@ class Handler : public CefMessageRouterBrowserSide::Handler { return false; } + RunOnMainThread(browser, request, callback); + return true; + } + + private: + static void RunOnMainThread(CefRefPtr browser, + const CefString& request, + CefRefPtr 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; } }; diff --git a/tests/cefclient/browser/window_test_runner.cc b/tests/cefclient/browser/window_test_runner.cc index 137487334..7549e6da7 100644 --- a/tests/cefclient/browser/window_test_runner.cc +++ b/tests/cefclient/browser/window_test_runner.cc @@ -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 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 browser) { diff --git a/tests/cefclient/browser/window_test_runner.h b/tests/cefclient/browser/window_test_runner.h index eaf71a252..2a5c3ba95 100644 --- a/tests/cefclient/browser/window_test_runner.h +++ b/tests/cefclient/browser/window_test_runner.h @@ -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 browser, - int x, - int y, - int width, - int height) = 0; + void SetPos(CefRefPtr browser, + int x, + int y, + int width, + int height); virtual void Minimize(CefRefPtr browser) = 0; virtual void Maximize(CefRefPtr browser) = 0; virtual void Restore(CefRefPtr browser) = 0; virtual void Fullscreen(CefRefPtr 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 browser, const std::optional& height); }; diff --git a/tests/cefclient/browser/window_test_runner_gtk.cc b/tests/cefclient/browser/window_test_runner_gtk.cc index a5093f165..b590f9e8b 100644 --- a/tests/cefclient/browser/window_test_runner_gtk.cc +++ b/tests/cefclient/browser/window_test_runner_gtk.cc @@ -29,113 +29,32 @@ GtkWindow* GetWindow(CefRefPtr 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 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 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 browser) { - REQUIRE_MAIN_THREAD(); - ScopedGdkThreadsEnter scoped_gdk_threads; - - GtkWindow* window = GetWindow(browser); - if (!window) { - return; - } - gtk_window_maximize(window); -} - -void RestoreImpl(CefRefPtr 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 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 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 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 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 diff --git a/tests/cefclient/browser/window_test_runner_gtk.h b/tests/cefclient/browser/window_test_runner_gtk.h index c699039c3..032b95fa5 100644 --- a/tests/cefclient/browser/window_test_runner_gtk.h +++ b/tests/cefclient/browser/window_test_runner_gtk.h @@ -16,11 +16,6 @@ class WindowTestRunnerGtk : public WindowTestRunner { public: WindowTestRunnerGtk(); - void SetPos(CefRefPtr browser, - int x, - int y, - int width, - int height) override; void Minimize(CefRefPtr browser) override; void Maximize(CefRefPtr browser) override; void Restore(CefRefPtr browser) override; diff --git a/tests/cefclient/browser/window_test_runner_mac.h b/tests/cefclient/browser/window_test_runner_mac.h index aebae0e36..f82f9c92a 100644 --- a/tests/cefclient/browser/window_test_runner_mac.h +++ b/tests/cefclient/browser/window_test_runner_mac.h @@ -16,11 +16,6 @@ class WindowTestRunnerMac : public WindowTestRunner { public: WindowTestRunnerMac(); - void SetPos(CefRefPtr browser, - int x, - int y, - int width, - int height) override; void Minimize(CefRefPtr browser) override; void Maximize(CefRefPtr browser) override; void Restore(CefRefPtr browser) override; diff --git a/tests/cefclient/browser/window_test_runner_mac.mm b/tests/cefclient/browser/window_test_runner_mac.mm index 1f1814461..680dcfa06 100644 --- a/tests/cefclient/browser/window_test_runner_mac.mm +++ b/tests/cefclient/browser/window_test_runner_mac.mm @@ -22,48 +22,7 @@ NSWindow* GetWindow(CefRefPtr browser) { } // namespace -WindowTestRunnerMac::WindowTestRunnerMac() {} - -void WindowTestRunnerMac::SetPos(CefRefPtr 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 browser) { CEF_REQUIRE_UI_THREAD(); diff --git a/tests/cefclient/browser/window_test_runner_views.cc b/tests/cefclient/browser/window_test_runner_views.cc index 350e9cf74..ccbfba13d 100644 --- a/tests/cefclient/browser/window_test_runner_views.cc +++ b/tests/cefclient/browser/window_test_runner_views.cc @@ -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 GetWindow(const CefRefPtr& browser) { return window; } -void SetTitlebarHeight(const CefRefPtr& browser, - const std::optional& height) { - CEF_REQUIRE_UI_THREAD(); - auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier()); - DCHECK(root_window.get()); +void MinimizeImpl(CefRefPtr browser) { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&MinimizeImpl, browser)); + return; + } - auto root_window_views = static_cast(root_window.get()); - root_window_views->SetTitlebarHeight(height); -} - -} // namespace - -WindowTestRunnerViews::WindowTestRunnerViews() = default; - -void WindowTestRunnerViews::SetPos(CefRefPtr browser, - int x, - int y, - int width, - int height) { - CefRefPtr window = GetWindow(browser); - - CefRect window_bounds(x, y, width, height); - ModifyBounds(window->GetDisplay()->GetWorkArea(), window_bounds); - - window->SetBounds(window_bounds); -} - -void WindowTestRunnerViews::Minimize(CefRefPtr browser) { GetWindow(browser)->Minimize(); } -void WindowTestRunnerViews::Maximize(CefRefPtr browser) { +void MaximizeImpl(CefRefPtr browser) { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&MaximizeImpl, browser)); + return; + } + GetWindow(browser)->Maximize(); } -void WindowTestRunnerViews::Restore(CefRefPtr browser) { +void RestoreImpl(CefRefPtr browser) { + if (!CefCurrentlyOn(TID_UI)) { + CefPostTask(TID_UI, base::BindOnce(&RestoreImpl, browser)); + return; + } + GetWindow(browser)->Restore(); } -void WindowTestRunnerViews::Fullscreen(CefRefPtr browser) { +void FullscreenImpl(CefRefPtr 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 browser) { } } +} // namespace + +WindowTestRunnerViews::WindowTestRunnerViews() = default; + +void WindowTestRunnerViews::Minimize(CefRefPtr browser) { + MinimizeImpl(browser); +} + +void WindowTestRunnerViews::Maximize(CefRefPtr browser) { + MaximizeImpl(browser); +} + +void WindowTestRunnerViews::Restore(CefRefPtr browser) { + RestoreImpl(browser); +} + +void WindowTestRunnerViews::Fullscreen(CefRefPtr browser) { + FullscreenImpl(browser); +} + void WindowTestRunnerViews::SetTitleBarHeight( CefRefPtr browser, const std::optional& height) { - SetTitlebarHeight(browser, height); + REQUIRE_MAIN_THREAD(); + + auto root_window = RootWindow::GetForBrowser(browser->GetIdentifier()); + auto root_window_views = static_cast(root_window.get()); + root_window_views->SetTitlebarHeight(height); } } // namespace client::window_test diff --git a/tests/cefclient/browser/window_test_runner_views.h b/tests/cefclient/browser/window_test_runner_views.h index cb075dc9f..c1893d340 100644 --- a/tests/cefclient/browser/window_test_runner_views.h +++ b/tests/cefclient/browser/window_test_runner_views.h @@ -15,11 +15,6 @@ class WindowTestRunnerViews : public WindowTestRunner { public: WindowTestRunnerViews(); - void SetPos(CefRefPtr browser, - int x, - int y, - int width, - int height) override; void Minimize(CefRefPtr browser) override; void Maximize(CefRefPtr browser) override; void Restore(CefRefPtr browser) override; diff --git a/tests/cefclient/browser/window_test_runner_win.cc b/tests/cefclient/browser/window_test_runner_win.cc index ae40542f1..df36c1423 100644 --- a/tests/cefclient/browser/window_test_runner_win.cc +++ b/tests/cefclient/browser/window_test_runner_win.cc @@ -27,52 +27,13 @@ void Toggle(HWND root_hwnd, UINT nCmdShow) { } } -void SetPosImpl(CefRefPtr 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 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 browser) { HWND root_hwnd = GetRootHwnd(browser); if (!root_hwnd) { return; @@ -80,7 +41,9 @@ void MinimizeImpl(CefRefPtr browser) { Toggle(root_hwnd, SW_MINIMIZE); } -void MaximizeImpl(CefRefPtr browser) { +void WindowTestRunnerWin::Maximize(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + HWND root_hwnd = GetRootHwnd(browser); if (!root_hwnd) { return; @@ -88,7 +51,9 @@ void MaximizeImpl(CefRefPtr browser) { Toggle(root_hwnd, SW_MAXIMIZE); } -void RestoreImpl(CefRefPtr browser) { +void WindowTestRunnerWin::Restore(CefRefPtr browser) { + REQUIRE_MAIN_THREAD(); + HWND root_hwnd = GetRootHwnd(browser); if (!root_hwnd) { return; @@ -96,48 +61,4 @@ void RestoreImpl(CefRefPtr browser) { ::ShowWindow(root_hwnd, SW_RESTORE); } -} // namespace - -WindowTestRunnerWin::WindowTestRunnerWin() = default; - -void WindowTestRunnerWin::SetPos(CefRefPtr 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 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 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 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 diff --git a/tests/cefclient/browser/window_test_runner_win.h b/tests/cefclient/browser/window_test_runner_win.h index 1891c7a6e..c924487f3 100644 --- a/tests/cefclient/browser/window_test_runner_win.h +++ b/tests/cefclient/browser/window_test_runner_win.h @@ -16,11 +16,6 @@ class WindowTestRunnerWin : public WindowTestRunner { public: WindowTestRunnerWin(); - void SetPos(CefRefPtr browser, - int x, - int y, - int width, - int height) override; void Minimize(CefRefPtr browser) override; void Maximize(CefRefPtr browser) override; void Restore(CefRefPtr browser) override; diff --git a/tests/cefclient/resources/window.html b/tests/cefclient/resources/window.html index 17f53d23a..a31c49a93 100644 --- a/tests/cefclient/resources/window.html +++ b/tests/cefclient/resources/window.html @@ -7,6 +7,12 @@ :fullscreen { background: pink; } +.bold { + font-weight: bold; +} +.mono { + font-family: monospace; +}
-Click a button to perform the associated window action. +

Click a button to perform the associated window action.

+

Input values are in DIP coordinates. See here for important details about coordinate systems and window behavior.

+
+Implementation details for cefclient (click to expand). +
    +
  • The below actions will configure window size (window.outerWidth/Height) and position (window.screenX/Y) as reported by browser content (see "Current window settings" below).
  • +
  • Whether this calculation includes the window frame will depend on what information is known/provided to CEF.
  • +
      +
    • Views-hosted windows are sized/positioned relative to the CefWindow frame.
    • +
    • Native windows (--use-native) are sized/positioned relative to the cefclient window frame.
    • +
        +
      • MacOS windows are sized/positioned relative to the parent NSWindow frame (default behavior).
      • +
      • Windows windows are sized/positioned relative to the root HWND frame (default behavior).
      • +
      • Linux windows are sized/positioned relative to CefDisplayHandler::GetRootWindowScreenRect.
      • +
      +
    • Windowless (off-screen) windows (--off-screen-rendering-enabled) are sized/positioned relative to CefRenderHandler::GetRootScreenRect.
    • +
        +
      • Pass --fake-screen-bounds to instead size/position relative to the browser content area. +This will also report (0,0) for position.
      • +
      +
    +
  • Exiting and relaunching cefclient will save/restore window bounds as DIP coordinates in all modes.
  • +
  • The window.devicePixelRatio value is a combination of device scale factor and browser zoom (if not 100%).
  • +
      +
    • Default device scale factor will match the associated display (e.g. user/system-level display settings).
    • +
    • Device scale factor can be configured via command-line (--force-device-scale-factor=[float]) or the "Tests > Set Scale Factor" menu option with windowless rendering.
    • +
        +
      • MacOS custom device scale factor will impact rendering quality only. DIP coordinates are not impacted.
      • +
      • Windows/Linux custom device scale factor will impact rendering quality and DIP coordinates.
      • +
      +
    • Browser zoom can be configured via the Chrome menu or "Tests > Zoom In/Out/Reset" menu options. +
    +
+



(minimizes and then restores the window as topmost) @@ -86,11 +147,55 @@ Click a button to perform the associated window action.
(uses Fullscreen API; background turns pink)
X: -Y: +Y: Width: Height:
(works on macOS with Views) +
window.(,) (calls CefDisplayHandler::OnContentsBoundsChange)
+

Current window settings:

+
+ diff --git a/tests/shared/browser/geometry_util.cc b/tests/shared/browser/geometry_util.cc index 60962b2b4..4234d12fe 100644 --- a/tests/shared/browser/geometry_util.cc +++ b/tests/shared/browser/geometry_util.cc @@ -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 diff --git a/tests/shared/browser/geometry_util.h b/tests/shared/browser/geometry_util.h index 1853aa04f..737169d79 100644 --- a/tests/shared/browser/geometry_util.h +++ b/tests/shared/browser/geometry_util.h @@ -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_ diff --git a/tests/shared/common/client_switches.cc b/tests/shared/common/client_switches.cc index dcc69e5bb..611934015 100644 --- a/tests/shared/common/client_switches.cc +++ b/tests/shared/common/client_switches.cc @@ -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"; diff --git a/tests/shared/common/client_switches.h b/tests/shared/common/client_switches.h index 52ad26a6c..ffff0c895 100644 --- a/tests/shared/common/client_switches.h +++ b/tests/shared/common/client_switches.h @@ -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[];