From 5dbfe13ac8f35d2ca5037ce421d8a1d2d8badaa4 Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Wed, 14 Jun 2023 11:20:02 +0300 Subject: [PATCH] views: Add support for modal browser dialogs A modal dialog is a child CefWindow that implements some special behaviors relative to a parent CefWindow. Like any CefWindow it can be framed with titlebar or frameless, and optionally contain draggable regions (subject to platform limitations described below). Modal dialogs are shown centered on the parent window (inside a single display) and always stay on top of the parent window in z-order. Sizing behavior and available window buttons are controlled via the usual CefWindowDelegate callbacks. For example, the dialog can have a preferred size with resize, minimize and maximize disabled (via GetPreferredSize, CanResize, CanMinimize and CanMaximize respectively). This change adds support for two modality modes. With window modality all controls in the parent window are disabled. With browser modality only the browser view in the parent window is disabled. Both modality modes require that a valid parent window be returned via GetParentWindow. For window modality return true from IsWindowModalDialog and call CefWindow::Show. For browser modality return false from IsWindowModalDialog (the default value) and call CefWindow::ShowAsBrowserModalDialog with a reference to the parent window's browser view. Window modal dialog behavior depends on the platform. On Windows and Linux these dialogs have a titlebar and can be moved independent of the parent window. On macOS these dialogs do not have a titlebar, move with the parent window, and do not support draggable regions (because they are implemented using sheets). On Linux disabling the parent window controls requires a window manager the supports _NET_WM_STATE_MODAL. Browser modal dialog behavior is similar on all platforms. The dialog will be automatically sized and positioned relative to the parent window's browser view. Closing the parent window or navigating the parent browser view will dismiss the dialog. The dialog can also be moved independent of the parent window though it will be recentered when the parent window itself is resized or redisplayed. On MacOS the dialog will move along with the parent window while on Windows and Linux the parent window can be moved independently. To test: Use the Tests > Dialog Window menu option in cefclient with Views enabled (`--use-views` or `--enable-chrome-runtime` command-line flag). Browser modal dialog is the default behavior. For window modal dialog add the `--use-window-modal-dialog` command-line flag. --- BUILD.gn | 1 + include/capi/views/cef_window_capi.h | 19 +- include/capi/views/cef_window_delegate_capi.h | 14 +- include/cef_api_hash.h | 8 +- include/views/cef_window.h | 16 ++ include/views/cef_window_delegate.h | 13 ++ libcef/browser/views/view_util_aura.cc | 10 +- .../views/widget_destruction_observer.h | 34 +++ libcef/browser/views/window_impl.cc | 36 +++ libcef/browser/views/window_impl.h | 5 + libcef/browser/views/window_view.cc | 209 ++++++++++++++++-- libcef/browser/views/window_view.h | 15 +- libcef_dll/cpptoc/views/window_cpptoc.cc | 26 ++- libcef_dll/cpptoc/views/window_cpptoc.h | 4 +- .../cpptoc/views/window_delegate_cpptoc.cc | 28 ++- libcef_dll/ctocpp/views/window_ctocpp.cc | 25 ++- libcef_dll/ctocpp/views/window_ctocpp.h | 6 +- .../ctocpp/views/window_delegate_ctocpp.cc | 27 ++- .../ctocpp/views/window_delegate_ctocpp.h | 3 +- patch/patch.cfg | 12 + .../chrome_browser_dialogs_widget.patch | 16 +- patch/patches/screen_1443650.patch | 42 ++++ patch/patches/ui_views_widget_type.patch | 27 +++ tests/cefclient/browser/resource.h | 27 +-- tests/cefclient/browser/root_window.h | 16 +- tests/cefclient/browser/root_window_gtk.cc | 2 +- tests/cefclient/browser/root_window_mac.mm | 2 +- .../cefclient/browser/root_window_manager.cc | 2 +- tests/cefclient/browser/root_window_views.cc | 17 +- tests/cefclient/browser/root_window_views.h | 1 - tests/cefclient/browser/root_window_win.cc | 2 +- tests/cefclient/browser/test_runner.cc | 18 ++ tests/cefclient/browser/views_window.cc | 130 ++++++++--- tests/cefclient/browser/views_window.h | 15 +- tests/cefclient/cefclient_mac.mm | 40 ++-- .../resources/mac/English.lproj/MainMenu.xib | 6 + tests/shared/common/client_switches.cc | 1 + tests/shared/common/client_switches.h | 1 + 38 files changed, 750 insertions(+), 126 deletions(-) create mode 100644 libcef/browser/views/widget_destruction_observer.h create mode 100644 patch/patches/screen_1443650.patch create mode 100644 patch/patches/ui_views_widget_type.patch 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