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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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