diff --git a/BUILD.gn b/BUILD.gn index 3082460f0..e018c4c2d 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -801,6 +801,7 @@ source_set("libcef_static") { "libcef/browser/views/view_util.cc", "libcef/browser/views/view_util.h", "libcef/browser/views/view_view.h", + "libcef/browser/views/widget_destruction_observer.h", "libcef/browser/views/window_impl.cc", "libcef/browser/views/window_impl.h", "libcef/browser/views/window_view.cc", diff --git a/include/capi/views/cef_window_capi.h b/include/capi/views/cef_window_capi.h index ac03e10c9..4aae64845 100644 --- a/include/capi/views/cef_window_capi.h +++ b/include/capi/views/cef_window_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=e09c33a3604cb7a80ef7fdea72c838619e26dc8c$ +// $hash=d53c4a0a7e731a56a0edcb9d705c76b0a2770155$ // #ifndef CEF_INCLUDE_CAPI_VIEWS_CEF_WINDOW_CAPI_H_ @@ -51,6 +51,8 @@ extern "C" { #endif +struct _cef_browser_view_t; + /// /// A Window is a top-level Window/widget in the Views hierarchy. By default it /// will have a non-client area with title bar, icon and buttons that supports @@ -69,6 +71,21 @@ typedef struct _cef_window_t { /// void(CEF_CALLBACK* show)(struct _cef_window_t* self); + /// + /// Show the Window as a browser modal dialog relative to |browser_view|. A + /// parent Window must be returned via + /// cef_window_delegate_t::get_parent_window() and |browser_view| must belong + /// to that parent Window. While this Window is visible, |browser_view| will + /// be disabled while other controls in the parent Window remain enabled. + /// Navigating or destroying the |browser_view| will close this Window + /// automatically. Alternately, use show() and return true (1) from + /// cef_window_delegate_t::is_window_modal_dialog() for a window modal dialog + /// where all controls in the parent Window are disabled. + /// + void(CEF_CALLBACK* show_as_browser_modal_dialog)( + struct _cef_window_t* self, + struct _cef_browser_view_t* browser_view); + /// /// Hide the Window. /// diff --git a/include/capi/views/cef_window_delegate_capi.h b/include/capi/views/cef_window_delegate_capi.h index 8a6b638cc..3f68aa8ff 100644 --- a/include/capi/views/cef_window_delegate_capi.h +++ b/include/capi/views/cef_window_delegate_capi.h @@ -33,7 +33,7 @@ // by hand. See the translator.README.txt file in the tools directory for // more information. // -// $hash=7201d268e16fc89f255b6ccd00d043f34fe77584$ +// $hash=61099a1ba8b16a5e46f5a80d326d1f9bfc99317d$ // #ifndef CEF_INCLUDE_CAPI_VIEWS_CEF_WINDOW_DELEGATE_CAPI_H_ @@ -110,6 +110,18 @@ typedef struct _cef_window_delegate_t { int* is_menu, int* can_activate_menu); + /// + /// Return true (1) if |window| should be created as a window modal dialog. + /// Only called when a Window is returned via get_parent_window() with + /// |is_menu| set to false (0). All controls in the parent Window will be + /// disabled while |window| is visible. This functionality is not supported by + /// all Linux window managers. Alternately, use + /// cef_window_t::show_as_browser_modal_dialog() for a browser modal dialog + /// that works on all platforms. + /// + int(CEF_CALLBACK* is_window_modal_dialog)(struct _cef_window_delegate_t* self, + struct _cef_window_t* window); + /// /// Return the initial bounds for |window| in density independent pixel (DIP) /// coordinates. If this function returns an NULL CefRect then diff --git a/include/cef_api_hash.h b/include/cef_api_hash.h index 4c1089674..67695a5e4 100644 --- a/include/cef_api_hash.h +++ b/include/cef_api_hash.h @@ -42,13 +42,13 @@ // way that may cause binary incompatibility with other builds. The universal // hash value will change if any platform is affected whereas the platform hash // values will change only if that particular platform is affected. -#define CEF_API_HASH_UNIVERSAL "5ee382b5a4b7a02d0285f9af00e9205e03b456d0" +#define CEF_API_HASH_UNIVERSAL "9c9fbc9d59a544c8e0c2f0cbed4b6622f2786f1c" #if defined(OS_WIN) -#define CEF_API_HASH_PLATFORM "f4db8c7c65b7a95af22e0adf0c6b86c087d988f1" +#define CEF_API_HASH_PLATFORM "240e06747f2ea2d7d4a3071042d45cf80d170420" #elif defined(OS_MAC) -#define CEF_API_HASH_PLATFORM "932566b79377996c56a3d97128404491f94699cf" +#define CEF_API_HASH_PLATFORM "cd4a815153a919ad30b95c659688b564823d92fc" #elif defined(OS_LINUX) -#define CEF_API_HASH_PLATFORM "ae753fb624eec885a3dc64c815360f9bca58f17e" +#define CEF_API_HASH_PLATFORM "4c372cb13e3446b2a54aaaea51b0d47ba7766152" #endif #ifdef __cplusplus diff --git a/include/views/cef_window.h b/include/views/cef_window.h index 5cb3c4d66..4c8ad09b0 100644 --- a/include/views/cef_window.h +++ b/include/views/cef_window.h @@ -45,6 +45,8 @@ #include "include/views/cef_panel.h" #include "include/views/cef_window_delegate.h" +class CefBrowserView; + /// /// A Window is a top-level Window/widget in the Views hierarchy. By default it /// will have a non-client area with title bar, icon and buttons that supports @@ -68,6 +70,20 @@ class CefWindow : public CefPanel { /*--cef()--*/ virtual void Show() = 0; + /// + /// Show the Window as a browser modal dialog relative to |browser_view|. A + /// parent Window must be returned via CefWindowDelegate::GetParentWindow() + /// and |browser_view| must belong to that parent Window. While this Window is + /// visible, |browser_view| will be disabled while other controls in the + /// parent Window remain enabled. Navigating or destroying the |browser_view| + /// will close this Window automatically. Alternately, use Show() and return + /// true from CefWindowDelegate::IsWindowModalDialog() for a window modal + /// dialog where all controls in the parent Window are disabled. + /// + /*--cef()--*/ + virtual void ShowAsBrowserModalDialog( + CefRefPtr browser_view) = 0; + /// /// Hide the Window. /// diff --git a/include/views/cef_window_delegate.h b/include/views/cef_window_delegate.h index 8779000ed..3b4e80f9e 100644 --- a/include/views/cef_window_delegate.h +++ b/include/views/cef_window_delegate.h @@ -99,6 +99,19 @@ class CefWindowDelegate : public CefPanelDelegate { return nullptr; } + /// + /// Return true if |window| should be created as a window modal dialog. Only + /// called when a Window is returned via GetParentWindow() with |is_menu| set + /// to false. All controls in the parent Window will be disabled while + /// |window| is visible. This functionality is not supported by all Linux + /// window managers. Alternately, use CefWindow::ShowAsBrowserModalDialog() + /// for a browser modal dialog that works on all platforms. + /// + /*--cef()--*/ + virtual bool IsWindowModalDialog(CefRefPtr window) { + return false; + } + /// /// Return the initial bounds for |window| in density independent pixel (DIP) /// coordinates. If this method returns an empty CefRect then diff --git a/libcef/browser/views/view_util_aura.cc b/libcef/browser/views/view_util_aura.cc index f72e053f5..24be3311b 100644 --- a/libcef/browser/views/view_util_aura.cc +++ b/libcef/browser/views/view_util_aura.cc @@ -15,16 +15,16 @@ namespace view_util { gfx::NativeWindow GetNativeWindow(views::Widget* widget) { if (widget) { - aura::Window* window = widget->GetNativeWindow(); - if (window) { - return window->GetRootWindow(); - } + return widget->GetNativeWindow(); } return nullptr; } gfx::NativeView GetNativeView(views::Widget* widget) { - return GetNativeWindow(widget); + if (widget) { + return widget->GetNativeView(); + } + return nullptr; } CefWindowHandle GetWindowHandle(views::Widget* widget) { diff --git a/libcef/browser/views/widget_destruction_observer.h b/libcef/browser/views/widget_destruction_observer.h new file mode 100644 index 000000000..c9c41eda8 --- /dev/null +++ b/libcef/browser/views/widget_destruction_observer.h @@ -0,0 +1,34 @@ +// Copyright 2023 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 "base/scoped_observation.h" +#include "ui/views/widget/widget_observer.h" + +// Tracks if a given widget has been destroyed. +class WidgetDestructionObserver : public views::WidgetObserver { + public: + explicit WidgetDestructionObserver(views::Widget* widget) : widget_(widget) { + DCHECK(widget); + observation_.Observe(widget); + } + WidgetDestructionObserver(const WidgetDestructionObserver&) = delete; + WidgetDestructionObserver& operator=(const WidgetDestructionObserver&) = + delete; + ~WidgetDestructionObserver() override = default; + + // views::WidgetObserver: + void OnWidgetDestroyed(views::Widget* widget) override { + DCHECK(widget_); + widget_ = nullptr; + observation_.Reset(); + } + + views::Widget* widget() const { return widget_; } + + private: + views::Widget* widget_; + + base::ScopedObservation observation_{ + this}; +}; diff --git a/libcef/browser/views/window_impl.cc b/libcef/browser/views/window_impl.cc index e8b36d22a..e562f0d01 100644 --- a/libcef/browser/views/window_impl.cc +++ b/libcef/browser/views/window_impl.cc @@ -6,6 +6,7 @@ #include "libcef/browser/browser_util.h" #include "libcef/browser/thread_util.h" +#include "libcef/browser/views/browser_view_impl.h" #include "libcef/browser/views/display_impl.h" #include "libcef/browser/views/fill_layout_impl.h" #include "libcef/browser/views/layout_util.h" @@ -13,11 +14,13 @@ #include "libcef/browser/views/window_view.h" #include "base/i18n/rtl.h" +#include "components/constrained_window/constrained_window_views.h" #include "ui/base/test/ui_controls.h" #include "ui/compositor/compositor.h" #include "ui/gfx/geometry/rect.h" #include "ui/views/controls/button/menu_button.h" #include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/controls/webview/webview.h" #if defined(USE_AURA) #include "ui/aura/window.h" @@ -122,10 +125,38 @@ CefRefPtr CefWindowImpl::Create( void CefWindowImpl::Show() { CEF_REQUIRE_VALID_RETURN_VOID(); if (widget_) { + shown_as_browser_modal_ = false; widget_->Show(); } } +void CefWindowImpl::ShowAsBrowserModalDialog( + CefRefPtr browser_view) { + CEF_REQUIRE_VALID_RETURN_VOID(); + if (widget_) { + auto* browser_view_impl = + static_cast(browser_view.get()); + + // |browser_view| must belong to the host widget. + auto* host_widget = static_cast(root_view())->host_widget(); + CHECK(host_widget && + browser_view_impl->root_view()->GetWidget() == host_widget); + + if (auto web_view = browser_view_impl->web_view()) { + if (auto web_contents = web_view->web_contents()) { + shown_as_browser_modal_ = true; + constrained_window::ShowModalDialog(widget_->GetNativeWindow(), + web_contents); + + // NativeWebContentsModalDialogManagerViews::ManageDialog() disables + // movement. That has no impact on native frames but interferes with + // draggable regions. + widget_->set_movement_disabled(false); + } + } + } +} + void CefWindowImpl::Hide() { CEF_REQUIRE_VALID_RETURN_VOID(); if (widget_) { @@ -403,6 +434,11 @@ void CefWindowImpl::SetBackgroundColor(cef_color_t color) { } bool CefWindowImpl::CanWidgetClose() { + if (shown_as_browser_modal_) { + // Always allow the close for browser modal dialogs to avoid an infinite + // loop in WebContentsModalDialogManager::CloseAllDialogs(). + return true; + } if (delegate()) { return delegate()->CanClose(this); } diff --git a/libcef/browser/views/window_impl.h b/libcef/browser/views/window_impl.h index 2ddf03917..ea71c9500 100644 --- a/libcef/browser/views/window_impl.h +++ b/libcef/browser/views/window_impl.h @@ -40,6 +40,8 @@ class CefWindowImpl // CefWindow methods: void Show() override; + void ShowAsBrowserModalDialog( + CefRefPtr browser_view) override; void Hide() override; void CenterWindow(const CefSize& size) override; void Close() override; @@ -162,6 +164,9 @@ class CefWindowImpl std::unique_ptr unhandled_key_event_handler_; #endif + // True if this window was shown using ShowAsBrowserModalDialog(). + bool shown_as_browser_modal_ = false; + IMPLEMENT_REFCOUNTING_DELETE_ON_UIT(CefWindowImpl); }; diff --git a/libcef/browser/views/window_view.cc b/libcef/browser/views/window_view.cc index 8d5794cf0..ee7d84a84 100644 --- a/libcef/browser/views/window_view.cc +++ b/libcef/browser/views/window_view.cc @@ -11,6 +11,7 @@ #include "libcef/features/runtime.h" #include "ui/base/hit_test.h" +#include "ui/display/screen.h" #include "ui/views/widget/widget.h" #include "ui/views/window/native_frame_view.h" @@ -18,12 +19,20 @@ #include "ui/ozone/buildflags.h" #if BUILDFLAG(OZONE_PLATFORM_X11) #include "ui/base/x/x11_util.h" +#include "ui/gfx/x/x11_atom_cache.h" +#include "ui/gfx/x/xproto_util.h" +#include "ui/linux/linux_ui_delegate.h" #endif #endif +#if BUILDFLAG(IS_OZONE) +#include "ui/ozone/public/ozone_platform.h" +#endif + #if BUILDFLAG(IS_WIN) #include #include "base/win/windows_version.h" +#include "ui/display/win/screen_win.h" #include "ui/views/win/hwnd_util.h" #endif @@ -68,17 +77,47 @@ class NativeFrameViewEx : public views::NativeFrameView { gfx::Rect GetWindowBoundsForClientBounds( const gfx::Rect& client_bounds) const override { -#if BUILDFLAG(IS_WIN) - // views::GetWindowBoundsForClientBounds() expects the input Rect to be in - // pixel coordinates. NativeFrameView does not implement this correctly so - // we need to provide our own implementation. See http://crbug.com/602692. - gfx::Rect pixel_bounds = - display::Screen::GetScreen()->DIPToScreenRectInWindow( - view_util::GetNativeWindow(widget_), client_bounds); - pixel_bounds = views::GetWindowBoundsForClientBounds( - static_cast(const_cast(this)), pixel_bounds); - return display::Screen::GetScreen()->ScreenToDIPRectInWindow( - view_util::GetNativeWindow(widget_), pixel_bounds); +#if BUILDFLAG(IS_MAC) + // From NativeFrameView::GetWindowBoundsForClientBounds: + gfx::Rect window_bounds = client_bounds; + // Enforce minimum size (1, 1) in case that |client_bounds| is passed with + // empty size. + if (window_bounds.IsEmpty()) { + window_bounds.set_size(gfx::Size(1, 1)); + } + + if (!view_->IsFrameless()) { + if (auto titlebar_height = view_->GetTitlebarHeight()) { + window_bounds.Inset(gfx::Insets::TLBR(-(*titlebar_height), 0, 0, 0)); + } + } + + return window_bounds; +#elif BUILDFLAG(IS_WIN) + HWND window = views::HWNDForWidget(widget_); + CHECK(window); + + const DWORD style = GetWindowLong(window, GWL_STYLE); + const DWORD ex_style = GetWindowLong(window, GWL_EXSTYLE); + const bool has_menu = !(style & WS_CHILD) && (GetMenu(window) != NULL); + + // Convert from DIP to pixel coordinates using a method that can handle + // multiple displays with different DPI. + const auto screen_rect = + display::win::ScreenWin::DIPToScreenRect(window, client_bounds); + + RECT rect = {screen_rect.x(), screen_rect.y(), + screen_rect.x() + screen_rect.width(), + screen_rect.y() + screen_rect.height()}; + AdjustWindowRectEx(&rect, style, has_menu, ex_style); + + // Keep the original origin while potentially increasing the size to include + // the frame non-client area. + gfx::Rect pixel_rect(screen_rect.x(), screen_rect.y(), + rect.right - rect.left, rect.bottom - rect.top); + + // Convert back to DIP. + return display::win::ScreenWin::ScreenToDIPRect(window, pixel_rect); #else // Use the default implementation. return views::NativeFrameView::GetWindowBoundsForClientBounds( @@ -273,13 +312,62 @@ bool IsWindowBorderHit(int code) { #endif } +// Based on UpdateModalDialogPosition() from +// components/constrained_window/constrained_window_views.cc +void UpdateModalDialogPosition(views::Widget* widget, + views::Widget* host_widget) { + // Do not forcibly update the dialog widget position if it is being dragged. + if (widget->HasCapture()) { + return; + } + + const gfx::Size& size = widget->GetRootView()->GetPreferredSize(); + const gfx::Size& host_size = + host_widget->GetClientAreaBoundsInScreen().size(); + + // Center the dialog. Position is relative to the host. + gfx::Point position; + position.set_x((host_size.width() - size.width()) / 2); + position.set_y((host_size.height() - size.height()) / 2); + + // Align the first row of pixels inside the border. This is the apparent top + // of the dialog. + position.set_y(position.y() - + widget->non_client_view()->frame_view()->GetInsets().top()); + + const bool supports_global_screen_coordinates = +#if !BUILDFLAG(IS_OZONE) + true; +#else + ui::OzonePlatform::GetInstance() + ->GetPlatformProperties() + .supports_global_screen_coordinates; +#endif + + if (widget->is_top_level() && supports_global_screen_coordinates) { + position += host_widget->GetClientAreaBoundsInScreen().OffsetFromOrigin(); + // If the dialog extends partially off any display, clamp its position to + // be fully visible within that display. If the dialog doesn't intersect + // with any display clamp its position to be fully on the nearest display. + gfx::Rect display_rect = gfx::Rect(position, size); + const display::Display display = + display::Screen::GetScreen()->GetDisplayNearestView( + view_util::GetNativeView(host_widget)); + const gfx::Rect work_area = display.work_area(); + if (!work_area.Contains(display_rect)) { + display_rect.AdjustToFit(work_area); + } + position = display_rect.origin(); + } + + widget->SetBounds(gfx::Rect(position, size)); +} + } // namespace CefWindowView::CefWindowView(CefWindowDelegate* cef_delegate, Delegate* window_delegate) - : ParentClass(cef_delegate), - window_delegate_(window_delegate), - is_frameless_(false) { + : ParentClass(cef_delegate), window_delegate_(window_delegate) { DCHECK(window_delegate_); } @@ -294,6 +382,8 @@ void CefWindowView::CreateWidget(gfx::AcceleratedWidget parent_widget) { views::Widget::InitParams params; params.delegate = this; + views::Widget* host_widget = nullptr; + bool can_activate = true; bool can_resize = true; @@ -363,6 +453,12 @@ void CefWindowView::CreateWidget(gfx::AcceleratedWidget parent_widget) { CefWindowImpl* parent_window_impl = static_cast(parent_window.get()); params.parent = view_util::GetNativeView(parent_window_impl->widget()); + + // Aura uses the same types for NativeView and NativeWindow, which can + // be confusing. Verify that we set |params.parent| correctly (to the + // expected internal::NativeWidgetPrivate) for Widget::Init usage. + DCHECK(views::Widget::GetWidgetForNativeView(params.parent)); + if (is_menu) { // Don't clip the window to parent bounds. params.type = views::Widget::InitParams::TYPE_MENU; @@ -371,6 +467,24 @@ void CefWindowView::CreateWidget(gfx::AcceleratedWidget parent_widget) { params.z_order = ui::ZOrderLevel::kNormal; can_activate = can_activate_menu; + } else { + // Create a top-level window that is moveable and can exceed the + // bounds of the parent window. By not setting |params.child| here we + // cause OnBeforeWidgetInit to create a views::DesktopNativeWidgetAura + // instead of a views::NativeWidgetAura. We need to use this desktop + // variant with browser windows to get proper focus and shutdown + // behavior. + +#if !BUILDFLAG(IS_LINUX) + // SetModalType doesn't work on Linux (no implementation in + // DesktopWindowTreeHostLinux::InitModalType). See the X11-specific + // implementation below that may work with some window managers. + if (cef_delegate()->IsWindowModalDialog(cef_window)) { + SetModalType(ui::MODAL_TYPE_WINDOW); + } +#endif + + host_widget = parent_window_impl->widget(); } } } @@ -415,13 +529,53 @@ void CefWindowView::CreateWidget(gfx::AcceleratedWidget parent_widget) { #if BUILDFLAG(IS_LINUX) #if BUILDFLAG(OZONE_PLATFORM_X11) + auto x11window = static_cast(view_util::GetWindowHandle(widget)); + CHECK(x11window); + if (is_frameless_) { - auto window = view_util::GetWindowHandle(widget); - DCHECK(window); - ui::SetUseOSWindowFrame(static_cast(window), false); + ui::SetUseOSWindowFrame(x11window, false); + } + + if (host_widget) { + auto parent = static_cast( + view_util::GetWindowHandle(host_widget)); + CHECK(parent); + + if (cef_delegate() && cef_delegate()->IsWindowModalDialog(GetCefWindow())) { + // The presence of _NET_WM_STATE_MODAL in _NET_SUPPORTED indicates + // possible window manager support. However, some window managers still + // don't support this properly. + x11::Atom modal_atom = x11::GetAtom("_NET_WM_STATE_MODAL"); + if (ui::WmSupportsHint(modal_atom)) { + ui::SetWMSpecState(x11window, true, modal_atom, x11::Atom::None); + } else { + LOG(ERROR) + << "Window modal dialogs are not supported by the window manager"; + } + } + + // From GtkUiPlatformX11::SetGtkWidgetTransientFor: + x11::SetProperty(x11window, x11::Atom::WM_TRANSIENT_FOR, x11::Atom::WINDOW, + parent); + x11::SetProperty(x11window, x11::GetAtom("_NET_WM_WINDOW_TYPE"), + x11::Atom::ATOM, + x11::GetAtom("_NET_WM_WINDOW_TYPE_DIALOG")); + + ui::LinuxUiDelegate::GetInstance()->SetTransientWindowForParent( + parent, static_cast(x11window)); } #endif #endif + + if (host_widget) { + // Position |widget| relative to |host_widget|. + UpdateModalDialogPosition(widget, host_widget); + + // Track the lifespan of |host_widget|, which may be destroyed before + // |widget|. + host_widget_destruction_observer_ = + std::make_unique(host_widget); + } } CefRefPtr CefWindowView::GetCefWindow() const { @@ -477,6 +631,20 @@ ui::ImageModel CefWindowView::GetWindowAppIcon() { } void CefWindowView::WindowClosing() { +#if BUILDFLAG(IS_LINUX) +#if BUILDFLAG(OZONE_PLATFORM_X11) + if (host_widget()) { + auto parent = static_cast( + view_util::GetWindowHandle(host_widget())); + CHECK(parent); + + // From GtkUiPlatformX11::ClearTransientFor: + ui::LinuxUiDelegate::GetInstance()->SetTransientWindowForParent( + parent, static_cast(x11::Window::None)); + } +#endif +#endif + window_delegate_->OnWindowClosing(); } @@ -724,6 +892,13 @@ void CefWindowView::UpdateFindBarBoundingBox(gfx::Rect* bounds) const { } } +views::Widget* CefWindowView::host_widget() const { + if (host_widget_destruction_observer_) { + return host_widget_destruction_observer_->widget(); + } + return nullptr; +} + absl::optional CefWindowView::GetTitlebarHeight() const { if (cef_delegate()) { float title_bar_height = 0; diff --git a/libcef/browser/views/window_view.h b/libcef/browser/views/window_view.h index 46ec20691..b065f3700 100644 --- a/libcef/browser/views/window_view.h +++ b/libcef/browser/views/window_view.h @@ -13,6 +13,7 @@ #include "libcef/browser/views/overlay_view_host.h" #include "libcef/browser/views/panel_view.h" +#include "libcef/browser/views/widget_destruction_observer.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/display/display.h" @@ -116,19 +117,24 @@ class CefWindowView // Optionally modify the bounding box for the Chrome Find bar. void UpdateFindBarBoundingBox(gfx::Rect* bounds) const; + absl::optional GetTitlebarHeight() const; + bool IsFrameless() const { return is_frameless_; } + + // The Widget that hosts us, if we're a modal dialog. May return nullptr + // during initialization and destruction. + views::Widget* host_widget() const; + private: // Called when removed from the Widget and before |this| is deleted. void DeleteDelegate(); void MoveOverlaysIfNecessary(); - absl::optional GetTitlebarHeight() const; - // Not owned by this object. Delegate* window_delegate_; // True if the window is frameless. It might still be resizable and draggable. - bool is_frameless_; + bool is_frameless_ = false; std::u16string title_; CefRefPtr window_icon_; @@ -137,6 +143,9 @@ class CefWindowView std::unique_ptr draggable_region_; std::vector draggable_rects_; + // Tracks the Widget that hosts us, if we're a modal dialog. + std::unique_ptr host_widget_destruction_observer_; + // Hosts for overlay widgets. std::vector> overlay_hosts_; }; diff --git a/libcef_dll/cpptoc/views/window_cpptoc.cc b/libcef_dll/cpptoc/views/window_cpptoc.cc index 0408a4203..93ee5341f 100644 --- a/libcef_dll/cpptoc/views/window_cpptoc.cc +++ b/libcef_dll/cpptoc/views/window_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=2f28922e536557bff211610dd38bb7b4c8a64d5a$ +// $hash=23777aea864e9abf38c2e2c5d79a40d6bd22876d$ // #include "libcef_dll/cpptoc/views/window_cpptoc.h" @@ -66,6 +66,28 @@ void CEF_CALLBACK window_show(struct _cef_window_t* self) { CefWindowCppToC::Get(self)->Show(); } +void CEF_CALLBACK +window_show_as_browser_modal_dialog(struct _cef_window_t* self, + cef_browser_view_t* browser_view) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) { + return; + } + // Verify param: browser_view; type: refptr_same + DCHECK(browser_view); + if (!browser_view) { + return; + } + + // Execute + CefWindowCppToC::Get(self)->ShowAsBrowserModalDialog( + CefBrowserViewCppToC::Unwrap(browser_view)); +} + void CEF_CALLBACK window_hide(struct _cef_window_t* self) { shutdown_checker::AssertNotShutdown(); @@ -1970,6 +1992,8 @@ int CEF_CALLBACK window_convert_point_from_view(struct _cef_view_t* self, CefWindowCppToC::CefWindowCppToC() { GetStruct()->show = window_show; + GetStruct()->show_as_browser_modal_dialog = + window_show_as_browser_modal_dialog; GetStruct()->hide = window_hide; GetStruct()->center_window = window_center_window; GetStruct()->close = window_close; diff --git a/libcef_dll/cpptoc/views/window_cpptoc.h b/libcef_dll/cpptoc/views/window_cpptoc.h index e02be4494..501b0524b 100644 --- a/libcef_dll/cpptoc/views/window_cpptoc.h +++ b/libcef_dll/cpptoc/views/window_cpptoc.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=12ff3d7d14f9977ff1f62e9a35b04b153a135480$ +// $hash=6700d328968cb5c0141cd2d93ce66835e97a9d66$ // #ifndef CEF_LIBCEF_DLL_CPPTOC_VIEWS_WINDOW_CPPTOC_H_ @@ -20,7 +20,9 @@ #error This file can be included DLL-side only #endif +#include "include/capi/views/cef_browser_view_capi.h" #include "include/capi/views/cef_window_capi.h" +#include "include/views/cef_browser_view.h" #include "include/views/cef_window.h" #include "libcef_dll/cpptoc/cpptoc_ref_counted.h" diff --git a/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc b/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc index ca15fcb43..0904b0986 100644 --- a/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc +++ b/libcef_dll/cpptoc/views/window_delegate_cpptoc.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=c84eb9772fc902904ccbd0cebe2c2105d06a8164$ +// $hash=10ec416d3aeba7215b08604b1a329adc1c9aaf6f$ // #include "libcef_dll/cpptoc/views/window_delegate_cpptoc.h" @@ -194,6 +194,31 @@ window_delegate_get_parent_window(struct _cef_window_delegate_t* self, return CefWindowCToCpp::Unwrap(_retval); } +int CEF_CALLBACK +window_delegate_is_window_modal_dialog(struct _cef_window_delegate_t* self, + cef_window_t* window) { + shutdown_checker::AssertNotShutdown(); + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + DCHECK(self); + if (!self) { + return 0; + } + // Verify param: window; type: refptr_diff + DCHECK(window); + if (!window) { + return 0; + } + + // Execute + bool _retval = CefWindowDelegateCppToC::Get(self)->IsWindowModalDialog( + CefWindowCToCpp::Wrap(window)); + + // Return type: bool + return _retval; +} + cef_rect_t CEF_CALLBACK window_delegate_get_initial_bounds(struct _cef_window_delegate_t* self, cef_window_t* window) { @@ -781,6 +806,7 @@ CefWindowDelegateCppToC::CefWindowDelegateCppToC() { GetStruct()->on_window_bounds_changed = window_delegate_on_window_bounds_changed; GetStruct()->get_parent_window = window_delegate_get_parent_window; + GetStruct()->is_window_modal_dialog = window_delegate_is_window_modal_dialog; GetStruct()->get_initial_bounds = window_delegate_get_initial_bounds; GetStruct()->get_initial_show_state = window_delegate_get_initial_show_state; GetStruct()->is_frameless = window_delegate_is_frameless; diff --git a/libcef_dll/ctocpp/views/window_ctocpp.cc b/libcef_dll/ctocpp/views/window_ctocpp.cc index c5ff71d87..3a0423d28 100644 --- a/libcef_dll/ctocpp/views/window_ctocpp.cc +++ b/libcef_dll/ctocpp/views/window_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=29399b84620b09e086f65f712b50573d7fcd1be8$ +// $hash=b6b0a2a563b475163aa71b20af6ec2ac8c1f0cae$ // #include "libcef_dll/ctocpp/views/window_ctocpp.h" @@ -65,6 +65,29 @@ NO_SANITIZE("cfi-icall") void CefWindowCToCpp::Show() { _struct->show(_struct); } +NO_SANITIZE("cfi-icall") +void CefWindowCToCpp::ShowAsBrowserModalDialog( + CefRefPtr browser_view) { + shutdown_checker::AssertNotShutdown(); + + cef_window_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, show_as_browser_modal_dialog)) { + return; + } + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: browser_view; type: refptr_same + DCHECK(browser_view.get()); + if (!browser_view.get()) { + return; + } + + // Execute + _struct->show_as_browser_modal_dialog( + _struct, CefBrowserViewCToCpp::Unwrap(browser_view)); +} + NO_SANITIZE("cfi-icall") void CefWindowCToCpp::Hide() { shutdown_checker::AssertNotShutdown(); diff --git a/libcef_dll/ctocpp/views/window_ctocpp.h b/libcef_dll/ctocpp/views/window_ctocpp.h index 5953e5e9b..bc797350c 100644 --- a/libcef_dll/ctocpp/views/window_ctocpp.h +++ b/libcef_dll/ctocpp/views/window_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=5ea05d2b5c24bfc214a529d62fba2e69ea626b78$ +// $hash=2a7aaed7d4296e29dca74345cf2b2d4db221a738$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_VIEWS_WINDOW_CTOCPP_H_ @@ -21,7 +21,9 @@ #endif #include +#include "include/capi/views/cef_browser_view_capi.h" #include "include/capi/views/cef_window_capi.h" +#include "include/views/cef_browser_view.h" #include "include/views/cef_window.h" #include "libcef_dll/ctocpp/ctocpp_ref_counted.h" @@ -35,6 +37,8 @@ class CefWindowCToCpp // CefWindow methods. void Show() override; + void ShowAsBrowserModalDialog( + CefRefPtr browser_view) override; void Hide() override; void CenterWindow(const CefSize& size) override; void Close() override; diff --git a/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc b/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc index 3fe0a81aa..a7ec62658 100644 --- a/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc +++ b/libcef_dll/ctocpp/views/window_delegate_ctocpp.cc @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=8998830c31f54e6e1199390c42a912033dabd6ad$ +// $hash=12bd03a8fb7d680a4e2b1a6818313c29bf14f011$ // #include "libcef_dll/ctocpp/views/window_delegate_ctocpp.h" @@ -182,6 +182,31 @@ CefRefPtr CefWindowDelegateCToCpp::GetParentWindow( return CefWindowCppToC::Unwrap(_retval); } +NO_SANITIZE("cfi-icall") +bool CefWindowDelegateCToCpp::IsWindowModalDialog(CefRefPtr window) { + shutdown_checker::AssertNotShutdown(); + + cef_window_delegate_t* _struct = GetStruct(); + if (CEF_MEMBER_MISSING(_struct, is_window_modal_dialog)) { + return false; + } + + // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING + + // Verify param: window; type: refptr_diff + DCHECK(window.get()); + if (!window.get()) { + return false; + } + + // Execute + int _retval = + _struct->is_window_modal_dialog(_struct, CefWindowCppToC::Wrap(window)); + + // Return type: bool + return _retval ? true : false; +} + NO_SANITIZE("cfi-icall") CefRect CefWindowDelegateCToCpp::GetInitialBounds(CefRefPtr window) { shutdown_checker::AssertNotShutdown(); diff --git a/libcef_dll/ctocpp/views/window_delegate_ctocpp.h b/libcef_dll/ctocpp/views/window_delegate_ctocpp.h index cbcf5530c..b11aa6449 100644 --- a/libcef_dll/ctocpp/views/window_delegate_ctocpp.h +++ b/libcef_dll/ctocpp/views/window_delegate_ctocpp.h @@ -9,7 +9,7 @@ // implementations. See the translator.README.txt file in the tools directory // for more information. // -// $hash=bf87c473a5bafd3f8c30bd06c033b0182f65a7b7$ +// $hash=25487f4d82069d1e4c0c7b27c1c1fcbcebbbacea$ // #ifndef CEF_LIBCEF_DLL_CTOCPP_VIEWS_WINDOW_DELEGATE_CTOCPP_H_ @@ -47,6 +47,7 @@ class CefWindowDelegateCToCpp CefRefPtr GetParentWindow(CefRefPtr window, bool* is_menu, bool* can_activate_menu) override; + bool IsWindowModalDialog(CefRefPtr window) override; CefRect GetInitialBounds(CefRefPtr window) override; cef_show_state_t GetInitialShowState(CefRefPtr window) override; bool IsFrameless(CefRefPtr window) override; diff --git a/patch/patch.cfg b/patch/patch.cfg index 9f065a81d..2e1031542 100644 --- a/patch/patch.cfg +++ b/patch/patch.cfg @@ -243,6 +243,9 @@ patches = [ # - Support nullptr gfx::NativeWindow/gfx::NativeView dialog parent for CEF # windowless rendering. # https://github.com/chromiumembedded/cef/issues/3316 + # + # Fix NativeWebContentsModalDialogManagerViews::HostChanged to not reparent + # between different root windows. 'name': 'chrome_browser_dialogs_widget', }, { @@ -627,5 +630,14 @@ patches = [ # macOS: Remove run-time dependency on libtest_trace_processor.dylib # Reverts the changes from https://crrev.com/8b18bd125d 'name': 'base_test_4396276' + }, + { + # Create top-level widget type when Widget::InitParams::child is false. + 'name': 'ui_views_widget_type' + }, + { + # win/linux: Fix rounding errors with Screen[Win] DIP/pixel conversions. + # https://bugs.chromium.org/p/chromium/issues/detail?id=1443650#c18 + 'name': 'screen_1443650' } ] diff --git a/patch/patches/chrome_browser_dialogs_widget.patch b/patch/patches/chrome_browser_dialogs_widget.patch index 722af685a..d99752c54 100644 --- a/patch/patches/chrome_browser_dialogs_widget.patch +++ b/patch/patches/chrome_browser_dialogs_widget.patch @@ -77,10 +77,10 @@ index dc648ad1ae595..70698ed848990 100644 DCHECK_EQ(parent_view, host->GetHostView()); ModalDialogHostObserver* dialog_host_observer = diff --git components/constrained_window/native_web_contents_modal_dialog_manager_views.cc components/constrained_window/native_web_contents_modal_dialog_manager_views.cc -index 647391095306e..02aea9b01f59b 100644 +index 647391095306e..bd49316ac758e 100644 --- components/constrained_window/native_web_contents_modal_dialog_manager_views.cc +++ components/constrained_window/native_web_contents_modal_dialog_manager_views.cc -@@ -184,9 +184,12 @@ void NativeWebContentsModalDialogManagerViews::HostChanged( +@@ -184,9 +184,20 @@ void NativeWebContentsModalDialogManagerViews::HostChanged( if (host_) { host_->AddObserver(this); @@ -90,8 +90,16 @@ index 647391095306e..02aea9b01f59b 100644 + // |host_view| will be nullptr with CEF windowless rendering. + if (auto host_view = host_->GetHostView()) { + for (auto* widget : observed_widgets_) { -+ views::Widget::ReparentNativeView(widget->GetNativeView(), -+ host_view); ++#if defined(USE_AURA) ++ auto widget_view = widget->GetNativeView(); ++ // Don't reparent between different root windows. Doing so causes ++ // issues with layout of dialogs containing Chrome browsers. ++ if (host_view->GetRootWindow() == widget_view->GetRootWindow()) { ++ views::Widget::ReparentNativeView(widget_view, host_view); ++ } ++#else ++ views::Widget::ReparentNativeView(widget->GetNativeView(), host_view); ++#endif + } } diff --git a/patch/patches/screen_1443650.patch b/patch/patches/screen_1443650.patch new file mode 100644 index 000000000..6df26b393 --- /dev/null +++ b/patch/patches/screen_1443650.patch @@ -0,0 +1,42 @@ +diff --git ui/display/screen.cc ui/display/screen.cc +index b74a058c4034e..441ccadf6a729 100644 +--- ui/display/screen.cc ++++ ui/display/screen.cc +@@ -107,13 +107,13 @@ base::TimeDelta Screen::CalculateIdleTime() const { + gfx::Rect Screen::ScreenToDIPRectInWindow(gfx::NativeWindow window, + const gfx::Rect& screen_rect) const { + float scale = GetDisplayNearestWindow(window).device_scale_factor(); +- return ScaleToEnclosingRect(screen_rect, 1.0f / scale); ++ return ScaleToEnclosedRect(screen_rect, 1.0f / scale); + } + + gfx::Rect Screen::DIPToScreenRectInWindow(gfx::NativeWindow window, + const gfx::Rect& dip_rect) const { + float scale = GetDisplayNearestWindow(window).device_scale_factor(); +- return ScaleToEnclosingRect(dip_rect, scale); ++ return ScaleToEnclosedRect(dip_rect, scale); + } + + bool Screen::GetDisplayWithDisplayId(int64_t display_id, +diff --git ui/display/win/screen_win.cc ui/display/win/screen_win.cc +index 1bddf0a8b84b4..da4b443f5f5a9 100644 +--- ui/display/win/screen_win.cc ++++ ui/display/win/screen_win.cc +@@ -580,7 +580,7 @@ gfx::Rect ScreenWin::ScreenToDIPRect(HWND hwnd, const gfx::Rect& pixel_bounds) { + gfx::PointF(pixel_bounds.origin()), screen_win_display)); + const float scale_factor = + 1.0f / screen_win_display.display().device_scale_factor(); +- return {origin, ScaleToEnclosingRect(pixel_bounds, scale_factor).size()}; ++ return {origin, ScaleToEnclosedRect(pixel_bounds, scale_factor).size()}; + } + + // static +@@ -595,7 +595,7 @@ gfx::Rect ScreenWin::DIPToScreenRect(HWND hwnd, const gfx::Rect& dip_bounds) { + const gfx::Point origin = + display::win::DIPToScreenPoint(dip_bounds.origin(), screen_win_display); + const float scale_factor = screen_win_display.display().device_scale_factor(); +- return {origin, ScaleToEnclosingRect(dip_bounds, scale_factor).size()}; ++ return {origin, ScaleToEnclosedRect(dip_bounds, scale_factor).size()}; + } + + // static diff --git a/patch/patches/ui_views_widget_type.patch b/patch/patches/ui_views_widget_type.patch new file mode 100644 index 000000000..588b95bcf --- /dev/null +++ b/patch/patches/ui_views_widget_type.patch @@ -0,0 +1,27 @@ +diff --git chrome/browser/ui/views/chrome_views_delegate_linux.cc chrome/browser/ui/views/chrome_views_delegate_linux.cc +index ecc07a2a60505..918116c0efeef 100644 +--- chrome/browser/ui/views/chrome_views_delegate_linux.cc ++++ chrome/browser/ui/views/chrome_views_delegate_linux.cc +@@ -51,7 +51,7 @@ NativeWidgetType GetNativeWidgetTypeForInitParams( + if (params.requires_accelerated_widget) + return NativeWidgetType::DESKTOP_NATIVE_WIDGET_AURA; + +- return (params.parent && ++ return (params.parent && params.child && + params.type != views::Widget::InitParams::TYPE_MENU && + params.type != views::Widget::InitParams::TYPE_TOOLTIP) + ? NativeWidgetType::NATIVE_WIDGET_AURA +diff --git ui/views/test/desktop_test_views_delegate_aura.cc ui/views/test/desktop_test_views_delegate_aura.cc +index 110eab7eec92a..2bfc96b6c439d 100644 +--- ui/views/test/desktop_test_views_delegate_aura.cc ++++ ui/views/test/desktop_test_views_delegate_aura.cc +@@ -27,7 +27,8 @@ void DesktopTestViewsDelegate::OnBeforeWidgetInit( + if (params->native_widget) + return; + +- if (params->parent && params->type != views::Widget::InitParams::TYPE_MENU && ++ if (params->parent && params->child && ++ params->type != views::Widget::InitParams::TYPE_MENU && + params->type != views::Widget::InitParams::TYPE_TOOLTIP) { + params->native_widget = new views::NativeWidgetAura(delegate); + } else { diff --git a/tests/cefclient/browser/resource.h b/tests/cefclient/browser/resource.h index 0bd35c866..b3b5102f3 100644 --- a/tests/cefclient/browser/resource.h +++ b/tests/cefclient/browser/resource.h @@ -29,19 +29,20 @@ #define ID_TESTS_OTHER_TESTS 32702 #define ID_TESTS_WINDOW_NEW 32703 #define ID_TESTS_WINDOW_POPUP 32704 -#define ID_TESTS_PRINT 32705 -#define ID_TESTS_REQUEST 32706 -#define ID_TESTS_TRACING_BEGIN 32707 -#define ID_TESTS_TRACING_END 32708 -#define ID_TESTS_ZOOM_IN 32709 -#define ID_TESTS_ZOOM_OUT 32710 -#define ID_TESTS_ZOOM_RESET 32711 -#define ID_TESTS_OSR_FPS 32712 -#define ID_TESTS_OSR_DSF 32713 -#define ID_TESTS_PRINT_TO_PDF 32714 -#define ID_TESTS_MUTE_AUDIO 32715 -#define ID_TESTS_UNMUTE_AUDIO 32716 -#define ID_TESTS_LAST 32716 +#define ID_TESTS_WINDOW_DIALOG 32705 +#define ID_TESTS_PRINT 32706 +#define ID_TESTS_REQUEST 32707 +#define ID_TESTS_TRACING_BEGIN 32708 +#define ID_TESTS_TRACING_END 32709 +#define ID_TESTS_ZOOM_IN 32710 +#define ID_TESTS_ZOOM_OUT 32711 +#define ID_TESTS_ZOOM_RESET 32712 +#define ID_TESTS_OSR_FPS 32713 +#define ID_TESTS_OSR_DSF 32714 +#define ID_TESTS_PRINT_TO_PDF 32715 +#define ID_TESTS_MUTE_AUDIO 32716 +#define ID_TESTS_UNMUTE_AUDIO 32717 +#define ID_TESTS_LAST 32717 #define IDC_STATIC -1 #define IDS_BINDING_HTML 1000 #define IDS_DIALOGS_HTML 1001 diff --git a/tests/cefclient/browser/root_window.h b/tests/cefclient/browser/root_window.h index ba86c0e5a..53d6f8c87 100644 --- a/tests/cefclient/browser/root_window.h +++ b/tests/cefclient/browser/root_window.h @@ -20,10 +20,23 @@ namespace client { +enum class WindowType { + NORMAL, + + // The window is hosting an extension app. + EXTENSION, + + // The window is a modal dialog. + DIALOG, +}; + // Used to configure how a RootWindow is created. struct RootWindowConfig { RootWindowConfig(); + // Configure the window type. + WindowType window_type = WindowType::NORMAL; + // If true the window will always display above other windows. bool always_on_top = false; @@ -33,9 +46,6 @@ struct RootWindowConfig { // If true the window will use off-screen rendering. bool with_osr = false; - // If true the window is hosting an extension app. - bool with_extension = false; - // If true the window will be created initially hidden. bool initially_hidden = false; diff --git a/tests/cefclient/browser/root_window_gtk.cc b/tests/cefclient/browser/root_window_gtk.cc index cee0041c9..420bc8787 100644 --- a/tests/cefclient/browser/root_window_gtk.cc +++ b/tests/cefclient/browser/root_window_gtk.cc @@ -119,7 +119,7 @@ void RootWindowGtk::Init(RootWindow::Delegate* delegate, with_controls_ = config->with_controls; always_on_top_ = config->always_on_top; with_osr_ = config->with_osr; - with_extension_ = config->with_extension; + with_extension_ = config->window_type == WindowType::EXTENSION; start_rect_ = config->bounds; CreateBrowserWindow(config->url); diff --git a/tests/cefclient/browser/root_window_mac.mm b/tests/cefclient/browser/root_window_mac.mm index 4eb056fa9..bc9c73b2a 100644 --- a/tests/cefclient/browser/root_window_mac.mm +++ b/tests/cefclient/browser/root_window_mac.mm @@ -279,7 +279,7 @@ void RootWindowMacImpl::Init(RootWindow::Delegate* delegate, with_controls_ = config->with_controls; with_osr_ = config->with_osr; - with_extension_ = config->with_extension; + with_extension_ = config->window_type == WindowType::EXTENSION; if (!config->bounds.IsEmpty()) { // Initial state was specified via the config object. diff --git a/tests/cefclient/browser/root_window_manager.cc b/tests/cefclient/browser/root_window_manager.cc index dba3da815..d256d74f5 100644 --- a/tests/cefclient/browser/root_window_manager.cc +++ b/tests/cefclient/browser/root_window_manager.cc @@ -183,9 +183,9 @@ scoped_refptr RootWindowManager::CreateRootWindowAsExtension( // We'll show the window when the desired size becomes available via // ClientHandler::OnAutoResize. auto config = std::make_unique(); + config->window_type = WindowType::EXTENSION; config->with_controls = with_controls; config->with_osr = with_osr; - config->with_extension = true; config->initially_hidden = true; config->source_bounds = source_bounds; config->parent_window = parent_window; diff --git a/tests/cefclient/browser/root_window_views.cc b/tests/cefclient/browser/root_window_views.cc index a8e6ff1cb..9171e0a65 100644 --- a/tests/cefclient/browser/root_window_views.cc +++ b/tests/cefclient/browser/root_window_views.cc @@ -184,7 +184,7 @@ ClientWindowHandle RootWindowViews::GetWindowHandle() const { bool RootWindowViews::WithExtension() const { DCHECK(initialized_); - return config_->with_extension; + return config_->window_type == WindowType::EXTENSION; } bool RootWindowViews::WithControls() { @@ -192,11 +192,6 @@ bool RootWindowViews::WithControls() { return config_->with_controls; } -bool RootWindowViews::WithExtension() { - DCHECK(initialized_); - return config_->with_extension; -} - void RootWindowViews::OnExtensionsChanged(const ExtensionSet& extensions) { if (!CefCurrentlyOn(TID_UI)) { // Execute this method on the UI thread. @@ -254,6 +249,10 @@ void RootWindowViews::OnViewsWindowClosing(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); DCHECK(window_); + if (config_->window_type != WindowType::NORMAL) { + return; + } + cef_show_state_t show_state; std::optional dip_bounds; if (window_->GetWindowRestorePreferences(show_state, dip_bounds)) { @@ -517,7 +516,7 @@ void RootWindowViews::InitOnUIThread( // Initial state was specified via the config object. initial_bounds_ = config_->bounds; initial_show_state_ = config_->show_state; - } else { + } else if (config_->window_type == WindowType::NORMAL) { // Initial state may be specified via the command-line or global // preferences. std::optional bounds; @@ -556,8 +555,8 @@ void RootWindowViews::CreateViewsWindow( #endif // Create the ViewsWindow. It will show itself after creation. - ViewsWindow::Create(this, client_handler_, config_->url, settings, - request_context); + ViewsWindow::Create(config_->window_type, this, client_handler_, config_->url, + settings, request_context); } void RootWindowViews::NotifyViewsWindowDestroyed() { diff --git a/tests/cefclient/browser/root_window_views.h b/tests/cefclient/browser/root_window_views.h index 75997e1aa..c6ce55632 100644 --- a/tests/cefclient/browser/root_window_views.h +++ b/tests/cefclient/browser/root_window_views.h @@ -53,7 +53,6 @@ class RootWindowViews : public RootWindow, // ViewsWindow::Delegate methods: bool WithControls() override; - bool WithExtension() override; bool InitiallyHidden() override; CefRefPtr GetParentWindow() override; CefRect GetInitialBounds() override; diff --git a/tests/cefclient/browser/root_window_win.cc b/tests/cefclient/browser/root_window_win.cc index 1a03cf229..19b489b1d 100644 --- a/tests/cefclient/browser/root_window_win.cc +++ b/tests/cefclient/browser/root_window_win.cc @@ -133,7 +133,7 @@ void RootWindowWin::Init(RootWindow::Delegate* delegate, with_controls_ = config->with_controls; always_on_top_ = config->always_on_top; with_osr_ = config->with_osr; - with_extension_ = config->with_extension; + with_extension_ = config->window_type == WindowType::EXTENSION; CreateBrowserWindow(config->url); diff --git a/tests/cefclient/browser/test_runner.cc b/tests/cefclient/browser/test_runner.cc index e9ad8548c..a8df8b9ca 100644 --- a/tests/cefclient/browser/test_runner.cc +++ b/tests/cefclient/browser/test_runner.cc @@ -13,6 +13,7 @@ #include "include/cef_parser.h" #include "include/cef_task.h" #include "include/cef_trace.h" +#include "include/views/cef_browser_view.h" #include "include/wrapper/cef_closure_task.h" #include "include/wrapper/cef_stream_resource_handler.h" #include "tests/cefclient/browser/binding_test.h" @@ -145,6 +146,20 @@ void RunPopupWindowTest(CefRefPtr browser) { "window.open('https://www.google.com');", "about:blank", 0); } +void RunDialogWindowTest(CefRefPtr browser) { + auto browser_view = CefBrowserView::GetForBrowser(browser); + if (!browser_view) { + LOG(ERROR) << "Dialog windows require Views"; + return; + } + + auto config = std::make_unique(); + config->window_type = WindowType::DIALOG; + config->parent_window = browser_view->GetWindow(); + MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + std::move(config)); +} + void ModifyZoom(CefRefPtr browser, double delta) { if (!CefCurrentlyOn(TID_UI)) { // Execute on the UI thread. @@ -547,6 +562,9 @@ void RunTest(CefRefPtr browser, int id) { case ID_TESTS_WINDOW_POPUP: RunPopupWindowTest(browser); break; + case ID_TESTS_WINDOW_DIALOG: + RunDialogWindowTest(browser); + break; case ID_TESTS_REQUEST: RunRequestTest(browser); break; diff --git a/tests/cefclient/browser/views_window.cc b/tests/cefclient/browser/views_window.cc index a466047ed..7b7df79e5 100644 --- a/tests/cefclient/browser/views_window.cc +++ b/tests/cefclient/browser/views_window.cc @@ -90,6 +90,7 @@ void AddTestMenuItems(CefRefPtr test_menu) { test_menu->AddItem(ID_TESTS_GETTEXT, "Get Text"); test_menu->AddItem(ID_TESTS_WINDOW_NEW, "New Window"); test_menu->AddItem(ID_TESTS_WINDOW_POPUP, "Popup Window"); + test_menu->AddItem(ID_TESTS_WINDOW_DIALOG, "Dialog Window"); test_menu->AddItem(ID_TESTS_REQUEST, "Request"); test_menu->AddItem(ID_TESTS_ZOOM_IN, "Zoom In"); test_menu->AddItem(ID_TESTS_ZOOM_OUT, "Zoom Out"); @@ -131,6 +132,7 @@ CefBrowserViewDelegate::ChromeToolbarType CalculateChromeToolbarType( // static CefRefPtr ViewsWindow::Create( + WindowType type, Delegate* delegate, CefRefPtr client, const CefString& url, @@ -140,7 +142,8 @@ CefRefPtr ViewsWindow::Create( DCHECK(delegate); // Create a new ViewsWindow. - CefRefPtr views_window = new ViewsWindow(delegate, nullptr); + CefRefPtr views_window = + new ViewsWindow(type, delegate, nullptr); // Create a new BrowserView. CefRefPtr browser_view = CefBrowserView::CreateBrowserView( @@ -158,7 +161,26 @@ CefRefPtr ViewsWindow::Create( void ViewsWindow::Show() { CEF_REQUIRE_UI_THREAD(); if (window_) { - window_->Show(); + if (type_ == WindowType::DIALOG) { + if (use_window_modal_dialog_) { + // Show as a window modal dialog (IsWindowModalDialog() will return + // true). + window_->Show(); + } else { + CefRefPtr browser_view; + if (auto parent_window = delegate_->GetParentWindow()) { + if (auto view = parent_window->GetViewForID(ID_BROWSER_VIEW)) { + browser_view = view->AsBrowserView(); + } + } + CHECK(browser_view); + + // Show as a browser modal dialog (relative to |browser_view|). + window_->ShowAsBrowserModalDialog(browser_view); + } + } else { + window_->Show(); + } } if (browser_view_ && !window_->IsMinimized()) { // Give keyboard focus to the BrowserView. @@ -430,7 +452,7 @@ CefRefPtr ViewsWindow::GetDelegateForPopupBrowserView( DCHECK(popup_delegate != delegate_); // Create a new ViewsWindow for the popup BrowserView. - return new ViewsWindow(popup_delegate, nullptr); + return new ViewsWindow(WindowType::NORMAL, popup_delegate, nullptr); } bool ViewsWindow::OnPopupBrowserViewCreated( @@ -591,18 +613,18 @@ void ViewsWindow::OnWindowCreated(CefRefPtr window) { window_ = window; window_->SetID(ID_WINDOW); - with_controls_ = delegate_->WithControls(); - delegate_->OnViewsWindowCreated(this); - const CefRect bounds = delegate_->GetInitialBounds(); - if (bounds.IsEmpty()) { - // Size the Window and center it at the default size. - window_->CenterWindow(CefSize(kDefaultWidth, kDefaultHeight)); - } else { - // Remember the bounds from the previous application run in case the user - // does not move or resize the window during this application run. - last_visible_bounds_ = bounds; + if (type_ == WindowType::NORMAL) { + const CefRect bounds = delegate_->GetInitialBounds(); + if (bounds.IsEmpty()) { + // Size the Window and center it at the default size. + window_->CenterWindow(CefSize(kDefaultWidth, kDefaultHeight)); + } else { + // Remember the bounds from the previous application run in case the user + // does not move or resize the window during this application run. + last_visible_bounds_ = bounds; + } } // Set the background color for regions that are not obscured by other Views. @@ -628,7 +650,7 @@ void ViewsWindow::OnWindowCreated(CefRefPtr window) { // Add the BrowserView as the only child of the Window. window_->AddChildView(browser_view_); - if (!delegate_->WithExtension()) { + if (type_ != WindowType::EXTENSION) { // Choose a reasonable minimum window size. minimum_window_size_ = CefSize(100, 100); } @@ -675,8 +697,8 @@ void ViewsWindow::OnWindowActivationChanged(CefRefPtr window, void ViewsWindow::OnWindowBoundsChanged(CefRefPtr window, const CefRect& new_bounds) { - if (!window->IsMinimized() && !window->IsMaximized() && - !window->IsFullscreen()) { + if (type_ == WindowType::NORMAL && !window->IsMinimized() && + !window->IsMaximized() && !window->IsFullscreen()) { // Track the last visible bounds for window restore purposes. last_visible_bounds_ = new_bounds; } @@ -707,15 +729,21 @@ CefRefPtr ViewsWindow::GetParentWindow(CefRefPtr window, CEF_REQUIRE_UI_THREAD(); CefRefPtr parent_window = delegate_->GetParentWindow(); if (parent_window) { - // Should be an extension window, in which case we want it to behave as a - // menu and allow activation. - DCHECK(delegate_->WithExtension()); - *is_menu = true; - *can_activate_menu = true; + // Extension windows behave as a menu and allow activation. + if (type_ == WindowType::EXTENSION) { + *is_menu = true; + *can_activate_menu = true; + } } return parent_window; } +bool ViewsWindow::IsWindowModalDialog(CefRefPtr window) { + CEF_REQUIRE_UI_THREAD(); + DCHECK(delegate_->GetParentWindow()); + return use_window_modal_dialog_; +} + CefRect ViewsWindow::GetInitialBounds(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); const CefRect bounds = delegate_->GetInitialBounds(); @@ -756,8 +784,20 @@ bool ViewsWindow::GetTitlebarHeight(CefRefPtr window, bool ViewsWindow::CanResize(CefRefPtr window) { CEF_REQUIRE_UI_THREAD(); - // Don't allow windows hosting extensions to resize. - return !delegate_->WithExtension(); + // Only allow resize of normal windows. + return type_ == WindowType::NORMAL; +} + +bool ViewsWindow::CanMaximize(CefRefPtr window) { + CEF_REQUIRE_UI_THREAD(); + // Only allow maximize of normal windows. + return type_ == WindowType::NORMAL; +} + +bool ViewsWindow::CanMinimize(CefRefPtr window) { + CEF_REQUIRE_UI_THREAD(); + // Only allow minimize of normal windows. + return type_ == WindowType::NORMAL; } bool ViewsWindow::OnAccelerator(CefRefPtr window, int command_id) { @@ -779,7 +819,7 @@ bool ViewsWindow::OnKeyEvent(CefRefPtr window, return false; } - if (delegate_->WithExtension() && event.type == KEYEVENT_RAWKEYDOWN && + if (type_ == WindowType::EXTENSION && event.type == KEYEVENT_RAWKEYDOWN && event.windows_key_code == VK_ESCAPE) { // Close the extension window on escape. Close(false); @@ -813,6 +853,18 @@ bool ViewsWindow::OnKeyEvent(CefRefPtr window, return false; } +CefSize ViewsWindow::GetPreferredSize(CefRefPtr view) { + CEF_REQUIRE_UI_THREAD(); + + if (view->GetID() == ID_WINDOW && type_ == WindowType::DIALOG) { + // Preferred size for a browser modal dialog. The dialog will be shrunk to + // fit inside the parent browser view if necessary. + return CefSize(kDefaultWidth, kDefaultHeight); + } + + return CefSize(); +} + CefSize ViewsWindow::GetMinimumSize(CefRefPtr view) { CEF_REQUIRE_UI_THREAD(); @@ -850,7 +902,7 @@ void ViewsWindow::OnBlur(CefRefPtr view) { CEF_REQUIRE_UI_THREAD(); const int view_id = view->GetID(); - if (view_id == ID_BROWSER_VIEW && delegate_->WithExtension()) { + if (view_id == ID_BROWSER_VIEW && type_ == WindowType::EXTENSION) { // Close windows hosting extensions when the browser loses focus. Close(false); } @@ -904,10 +956,11 @@ void ViewsWindow::MenuBarExecuteCommand(CefRefPtr menu_model, ExecuteCommand(menu_model, command_id, event_flags); } -ViewsWindow::ViewsWindow(Delegate* delegate, +ViewsWindow::ViewsWindow(WindowType type, + Delegate* delegate, CefRefPtr browser_view) - : delegate_(delegate), - with_controls_(false), + : type_(type), + delegate_(delegate), menu_has_focus_(false), last_focused_view_(false) { DCHECK(delegate_); @@ -918,19 +971,22 @@ ViewsWindow::ViewsWindow(Delegate* delegate, CefRefPtr command_line = CefCommandLine::GetGlobalCommandLine(); + const bool is_normal_type = type_ == WindowType::NORMAL; + + with_controls_ = is_normal_type && delegate_->WithControls(); + const bool hide_frame = command_line->HasSwitch(switches::kHideFrame); - const bool hide_overlays = command_line->HasSwitch(switches::kHideOverlays); - const bool hide_toolbar = - hide_frame && hide_overlays && !delegate_->WithControls(); + const bool hide_overlays = + !is_normal_type || command_line->HasSwitch(switches::kHideOverlays); + const bool hide_toolbar = hide_overlays && !with_controls_; const bool show_window_buttons = command_line->HasSwitch(switches::kShowWindowButtons); // Without a window frame. - frameless_ = hide_frame || delegate_->WithExtension(); + frameless_ = hide_frame || type_ == WindowType::EXTENSION; // With an overlay that mimics window controls. - with_overlay_controls_ = - hide_frame && !hide_overlays && !delegate_->WithControls(); + with_overlay_controls_ = hide_frame && !hide_overlays && !with_controls_; // If window has frame or flag passed explicitly with_standard_buttons_ = !frameless_ || show_window_buttons; @@ -954,6 +1010,9 @@ ViewsWindow::ViewsWindow(Delegate* delegate, top_menu_bar_ = new ViewsMenuBar(this, ID_TOP_MENU_FIRST); } #endif + + use_window_modal_dialog_ = + command_line->HasSwitch(switches::kUseWindowModalDialog); } void ViewsWindow::SetBrowserView(CefRefPtr browser_view) { @@ -1084,8 +1143,9 @@ void ViewsWindow::AddControls() { top_panel->SetToBoxLayout(top_panel_layout_settings); // Add the buttons and URL textfield to |top_panel|. - for (auto& browse_button : browse_buttons) + for (auto& browse_button : browse_buttons) { top_panel->AddChildView(browse_button); + } top_panel->AddChildView(location_bar_); UpdateExtensionControls(); diff --git a/tests/cefclient/browser/views_window.h b/tests/cefclient/browser/views_window.h index 9dc34ada8..311cc872c 100644 --- a/tests/cefclient/browser/views_window.h +++ b/tests/cefclient/browser/views_window.h @@ -25,6 +25,7 @@ #include "include/views/cef_window.h" #include "include/views/cef_window_delegate.h" #include "tests/cefclient/browser/image_cache.h" +#include "tests/cefclient/browser/root_window.h" #include "tests/cefclient/browser/views_menu_bar.h" #include "tests/cefclient/browser/views_overlay_controls.h" @@ -48,9 +49,6 @@ class ViewsWindow : public CefBrowserViewDelegate, // Return true if the window should show controls. virtual bool WithControls() = 0; - // Return true if the window is hosting an extension. - virtual bool WithExtension() = 0; - // Return true if the window should be created initially hidden. virtual bool InitiallyHidden() = 0; @@ -102,6 +100,7 @@ class ViewsWindow : public CefBrowserViewDelegate, // Create a new top-level ViewsWindow hosting a browser with the specified // configuration. static CefRefPtr Create( + WindowType type, Delegate* delegate, CefRefPtr client, const CefString& url, @@ -172,6 +171,7 @@ class ViewsWindow : public CefBrowserViewDelegate, CefRefPtr GetParentWindow(CefRefPtr window, bool* is_menu, bool* can_activate_menu) override; + bool IsWindowModalDialog(CefRefPtr window) override; CefRect GetInitialBounds(CefRefPtr window) override; cef_show_state_t GetInitialShowState(CefRefPtr window) override; bool IsFrameless(CefRefPtr window) override; @@ -179,12 +179,15 @@ class ViewsWindow : public CefBrowserViewDelegate, bool GetTitlebarHeight(CefRefPtr window, float* titlebar_height) override; bool CanResize(CefRefPtr window) override; + bool CanMaximize(CefRefPtr window) override; + bool CanMinimize(CefRefPtr window) override; bool CanClose(CefRefPtr window) override; bool OnAccelerator(CefRefPtr window, int command_id) override; bool OnKeyEvent(CefRefPtr window, const CefKeyEvent& event) override; // CefViewDelegate methods: + CefSize GetPreferredSize(CefRefPtr view) override; CefSize GetMinimumSize(CefRefPtr view) override; void OnFocus(CefRefPtr view) override; void OnBlur(CefRefPtr view) override; @@ -201,7 +204,9 @@ class ViewsWindow : public CefBrowserViewDelegate, // |delegate| is guaranteed to outlive this object. // |browser_view| may be nullptr, in which case SetBrowserView() will be // called. - ViewsWindow(Delegate* delegate, CefRefPtr browser_view); + ViewsWindow(WindowType type, + Delegate* delegate, + CefRefPtr browser_view); void SetBrowserView(CefRefPtr browser_view); @@ -239,6 +244,7 @@ class ViewsWindow : public CefBrowserViewDelegate, void NudgeWindow(); + const WindowType type_; Delegate* delegate_; // Not owned by this object. CefRefPtr browser_view_; bool frameless_; @@ -246,6 +252,7 @@ class ViewsWindow : public CefBrowserViewDelegate, bool with_overlay_controls_; bool with_standard_buttons_; ChromeToolbarType chrome_toolbar_type_; + bool use_window_modal_dialog_; CefRefPtr window_; CefRefPtr button_menu_model_; diff --git a/tests/cefclient/cefclient_mac.mm b/tests/cefclient/cefclient_mac.mm index 5c5106af3..32c4c75aa 100644 --- a/tests/cefclient/cefclient_mac.mm +++ b/tests/cefclient/cefclient_mac.mm @@ -40,6 +40,13 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { return nil; } +void RemoveMenuItem(NSMenu* menu, SEL action_selector) { + NSMenuItem* item = GetMenuItemWithAction(menu, action_selector); + if (item) { + [menu removeItem:item]; + } +} + } // namespace // Receives notifications from the application. Will delete itself when done. @@ -57,6 +64,7 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { - (IBAction)menuTestsGetSource:(id)sender; - (IBAction)menuTestsWindowNew:(id)sender; - (IBAction)menuTestsWindowPopup:(id)sender; +- (IBAction)menuTestsWindowDialog:(id)sender; - (IBAction)menuTestsRequest:(id)sender; - (IBAction)menuTestsZoomIn:(id)sender; - (IBAction)menuTestsZoomOut:(id)sender; @@ -206,20 +214,18 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { // Set the delegate for application events. [application setDelegate:self]; - if (!with_osr_) { - // Remove the OSR-related menu items when OSR is disabled. - NSMenuItem* tests_menu = GetMenuBarMenuWithTag(8); - if (tests_menu) { - NSMenuItem* set_fps_item = GetMenuItemWithAction( - tests_menu.submenu, @selector(menuTestsSetFPS:)); - if (set_fps_item) { - [tests_menu.submenu removeItem:set_fps_item]; - } - NSMenuItem* set_scale_factor_item = GetMenuItemWithAction( - tests_menu.submenu, @selector(menuTestsSetScaleFactor:)); - if (set_scale_factor_item) { - [tests_menu.submenu removeItem:set_scale_factor_item]; - } + auto* main_context = client::MainContext::Get(); + + NSMenuItem* tests_menu = GetMenuBarMenuWithTag(8); + if (tests_menu) { + if (!with_osr_) { + // Remove the OSR-related menu items when not using OSR. + RemoveMenuItem(tests_menu.submenu, @selector(menuTestsSetFPS:)); + RemoveMenuItem(tests_menu.submenu, @selector(menuTestsSetScaleFactor:)); + } + if (!main_context->UseViews()) { + // Remove the Views-related menu items when not using Views. + RemoveMenuItem(tests_menu.submenu, @selector(menuTestsWindowDialog:)); } } @@ -228,7 +234,7 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { window_config->with_osr = with_osr_; // Create the first window. - client::MainContext::Get()->GetRootWindowManager()->CreateRootWindow( + main_context->GetRootWindowManager()->CreateRootWindow( std::move(window_config)); } @@ -262,6 +268,10 @@ NSMenuItem* GetMenuItemWithAction(NSMenu* menu, SEL action_selector) { [self testsItemSelected:ID_TESTS_WINDOW_POPUP]; } +- (IBAction)menuTestsWindowDialog:(id)sender { + [self testsItemSelected:ID_TESTS_WINDOW_DIALOG]; +} + - (IBAction)menuTestsRequest:(id)sender { [self testsItemSelected:ID_TESTS_REQUEST]; } diff --git a/tests/cefclient/resources/mac/English.lproj/MainMenu.xib b/tests/cefclient/resources/mac/English.lproj/MainMenu.xib index d5f8f2377..e751d4eac 100644 --- a/tests/cefclient/resources/mac/English.lproj/MainMenu.xib +++ b/tests/cefclient/resources/mac/English.lproj/MainMenu.xib @@ -362,6 +362,12 @@ + + + + + + diff --git a/tests/shared/common/client_switches.cc b/tests/shared/common/client_switches.cc index cbd254c68..3753756c5 100644 --- a/tests/shared/common/client_switches.cc +++ b/tests/shared/common/client_switches.cc @@ -54,6 +54,7 @@ const char kUseDefaultPopup[] = "use-default-popup"; const char kUseClientDialogs[] = "use-client-dialogs"; const char kUseTestHttpServer[] = "use-test-http-server"; const char kShowWindowButtons[] = "show-window-buttons"; +const char kUseWindowModalDialog[] = "use-window-modal-dialog"; } // namespace switches } // namespace client diff --git a/tests/shared/common/client_switches.h b/tests/shared/common/client_switches.h index 84ac2edb9..cf1dc3127 100644 --- a/tests/shared/common/client_switches.h +++ b/tests/shared/common/client_switches.h @@ -48,6 +48,7 @@ extern const char kUseDefaultPopup[]; extern const char kUseClientDialogs[]; extern const char kUseTestHttpServer[]; extern const char kShowWindowButtons[]; +extern const char kUseWindowModalDialog[]; } // namespace switches } // namespace client