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.
This commit is contained in:
Marshall Greenblatt 2023-06-14 11:20:02 +03:00
parent 05930b7bc9
commit 0b99f92e97
38 changed files with 750 additions and 126 deletions

View File

@ -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",

View File

@ -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.
///

View File

@ -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

View File

@ -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

View File

@ -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<CefBrowserView> browser_view) = 0;
///
/// Hide the Window.
///

View File

@ -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<CefWindow> window) {
return false;
}
///
/// Return the initial bounds for |window| in density independent pixel (DIP)
/// coordinates. If this method returns an empty CefRect then

View File

@ -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) {

View File

@ -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<views::Widget, views::WidgetObserver> observation_{
this};
};

View File

@ -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> CefWindowImpl::Create(
void CefWindowImpl::Show() {
CEF_REQUIRE_VALID_RETURN_VOID();
if (widget_) {
shown_as_browser_modal_ = false;
widget_->Show();
}
}
void CefWindowImpl::ShowAsBrowserModalDialog(
CefRefPtr<CefBrowserView> browser_view) {
CEF_REQUIRE_VALID_RETURN_VOID();
if (widget_) {
auto* browser_view_impl =
static_cast<CefBrowserViewImpl*>(browser_view.get());
// |browser_view| must belong to the host widget.
auto* host_widget = static_cast<CefWindowView*>(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);
}

View File

@ -40,6 +40,8 @@ class CefWindowImpl
// CefWindow methods:
void Show() override;
void ShowAsBrowserModalDialog(
CefRefPtr<CefBrowserView> browser_view) override;
void Hide() override;
void CenterWindow(const CefSize& size) override;
void Close() override;
@ -162,6 +164,9 @@ class CefWindowImpl
std::unique_ptr<ui::EventHandler> 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);
};

View File

@ -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 <dwmapi.h>
#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<View*>(const_cast<NativeFrameViewEx*>(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<CefWindowImpl*>(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<x11::Window>(view_util::GetWindowHandle(widget));
CHECK(x11window);
if (is_frameless_) {
auto window = view_util::GetWindowHandle(widget);
DCHECK(window);
ui::SetUseOSWindowFrame(static_cast<x11::Window>(window), false);
ui::SetUseOSWindowFrame(x11window, false);
}
if (host_widget) {
auto parent = static_cast<gfx::AcceleratedWidget>(
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<gfx::AcceleratedWidget>(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<WidgetDestructionObserver>(host_widget);
}
}
CefRefPtr<CefWindow> 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<gfx::AcceleratedWidget>(
view_util::GetWindowHandle(host_widget()));
CHECK(parent);
// From GtkUiPlatformX11::ClearTransientFor:
ui::LinuxUiDelegate::GetInstance()->SetTransientWindowForParent(
parent, static_cast<gfx::AcceleratedWidget>(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<float> CefWindowView::GetTitlebarHeight() const {
if (cef_delegate()) {
float title_bar_height = 0;

View File

@ -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<float> 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<float> 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<CefImage> window_icon_;
@ -137,6 +143,9 @@ class CefWindowView
std::unique_ptr<SkRegion> draggable_region_;
std::vector<gfx::Rect> draggable_rects_;
// Tracks the Widget that hosts us, if we're a modal dialog.
std::unique_ptr<WidgetDestructionObserver> host_widget_destruction_observer_;
// Hosts for overlay widgets.
std::vector<std::unique_ptr<CefOverlayViewHost>> overlay_hosts_;
};

View File

@ -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;

View File

@ -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"

View File

@ -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;

View File

@ -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<CefBrowserView> 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();

View File

@ -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 <vector>
#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<CefBrowserView> browser_view) override;
void Hide() override;
void CenterWindow(const CefSize& size) override;
void Close() override;

View File

@ -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<CefWindow> CefWindowDelegateCToCpp::GetParentWindow(
return CefWindowCppToC::Unwrap(_retval);
}
NO_SANITIZE("cfi-icall")
bool CefWindowDelegateCToCpp::IsWindowModalDialog(CefRefPtr<CefWindow> 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<CefWindow> window) {
shutdown_checker::AssertNotShutdown();

View File

@ -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<CefWindow> GetParentWindow(CefRefPtr<CefWindow> window,
bool* is_menu,
bool* can_activate_menu) override;
bool IsWindowModalDialog(CefRefPtr<CefWindow> window) override;
CefRect GetInitialBounds(CefRefPtr<CefWindow> window) override;
cef_show_state_t GetInitialShowState(CefRefPtr<CefWindow> window) override;
bool IsFrameless(CefRefPtr<CefWindow> window) override;

View File

@ -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'
}
]

View File

@ -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
+ }
}

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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.

View File

@ -183,9 +183,9 @@ scoped_refptr<RootWindow> RootWindowManager::CreateRootWindowAsExtension(
// We'll show the window when the desired size becomes available via
// ClientHandler::OnAutoResize.
auto config = std::make_unique<RootWindowConfig>();
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;

View File

@ -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<ViewsWindow> window) {
CEF_REQUIRE_UI_THREAD();
DCHECK(window_);
if (config_->window_type != WindowType::NORMAL) {
return;
}
cef_show_state_t show_state;
std::optional<CefRect> 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<CefRect> 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() {

View File

@ -53,7 +53,6 @@ class RootWindowViews : public RootWindow,
// ViewsWindow::Delegate methods:
bool WithControls() override;
bool WithExtension() override;
bool InitiallyHidden() override;
CefRefPtr<CefWindow> GetParentWindow() override;
CefRect GetInitialBounds() override;

View File

@ -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);

View File

@ -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<CefBrowser> browser) {
"window.open('https://www.google.com');", "about:blank", 0);
}
void RunDialogWindowTest(CefRefPtr<CefBrowser> browser) {
auto browser_view = CefBrowserView::GetForBrowser(browser);
if (!browser_view) {
LOG(ERROR) << "Dialog windows require Views";
return;
}
auto config = std::make_unique<RootWindowConfig>();
config->window_type = WindowType::DIALOG;
config->parent_window = browser_view->GetWindow();
MainContext::Get()->GetRootWindowManager()->CreateRootWindow(
std::move(config));
}
void ModifyZoom(CefRefPtr<CefBrowser> browser, double delta) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute on the UI thread.
@ -547,6 +562,9 @@ void RunTest(CefRefPtr<CefBrowser> 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;

View File

@ -90,6 +90,7 @@ void AddTestMenuItems(CefRefPtr<CefMenuModel> 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> ViewsWindow::Create(
WindowType type,
Delegate* delegate,
CefRefPtr<CefClient> client,
const CefString& url,
@ -140,7 +142,8 @@ CefRefPtr<ViewsWindow> ViewsWindow::Create(
DCHECK(delegate);
// Create a new ViewsWindow.
CefRefPtr<ViewsWindow> views_window = new ViewsWindow(delegate, nullptr);
CefRefPtr<ViewsWindow> views_window =
new ViewsWindow(type, delegate, nullptr);
// Create a new BrowserView.
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
@ -158,7 +161,26 @@ CefRefPtr<ViewsWindow> 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<CefBrowserView> 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<CefBrowserViewDelegate> 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<CefWindow> 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<CefWindow> 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<CefWindow> window,
void ViewsWindow::OnWindowBoundsChanged(CefRefPtr<CefWindow> 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<CefWindow> ViewsWindow::GetParentWindow(CefRefPtr<CefWindow> window,
CEF_REQUIRE_UI_THREAD();
CefRefPtr<CefWindow> 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<CefWindow> window) {
CEF_REQUIRE_UI_THREAD();
DCHECK(delegate_->GetParentWindow());
return use_window_modal_dialog_;
}
CefRect ViewsWindow::GetInitialBounds(CefRefPtr<CefWindow> window) {
CEF_REQUIRE_UI_THREAD();
const CefRect bounds = delegate_->GetInitialBounds();
@ -756,8 +784,20 @@ bool ViewsWindow::GetTitlebarHeight(CefRefPtr<CefWindow> window,
bool ViewsWindow::CanResize(CefRefPtr<CefWindow> 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<CefWindow> window) {
CEF_REQUIRE_UI_THREAD();
// Only allow maximize of normal windows.
return type_ == WindowType::NORMAL;
}
bool ViewsWindow::CanMinimize(CefRefPtr<CefWindow> window) {
CEF_REQUIRE_UI_THREAD();
// Only allow minimize of normal windows.
return type_ == WindowType::NORMAL;
}
bool ViewsWindow::OnAccelerator(CefRefPtr<CefWindow> window, int command_id) {
@ -779,7 +819,7 @@ bool ViewsWindow::OnKeyEvent(CefRefPtr<CefWindow> 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<CefWindow> window,
return false;
}
CefSize ViewsWindow::GetPreferredSize(CefRefPtr<CefView> 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<CefView> view) {
CEF_REQUIRE_UI_THREAD();
@ -850,7 +902,7 @@ void ViewsWindow::OnBlur(CefRefPtr<CefView> 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<CefMenuModel> menu_model,
ExecuteCommand(menu_model, command_id, event_flags);
}
ViewsWindow::ViewsWindow(Delegate* delegate,
ViewsWindow::ViewsWindow(WindowType type,
Delegate* delegate,
CefRefPtr<CefBrowserView> 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<CefCommandLine> 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<CefBrowserView> 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();

View File

@ -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<ViewsWindow> Create(
WindowType type,
Delegate* delegate,
CefRefPtr<CefClient> client,
const CefString& url,
@ -172,6 +171,7 @@ class ViewsWindow : public CefBrowserViewDelegate,
CefRefPtr<CefWindow> GetParentWindow(CefRefPtr<CefWindow> window,
bool* is_menu,
bool* can_activate_menu) override;
bool IsWindowModalDialog(CefRefPtr<CefWindow> window) override;
CefRect GetInitialBounds(CefRefPtr<CefWindow> window) override;
cef_show_state_t GetInitialShowState(CefRefPtr<CefWindow> window) override;
bool IsFrameless(CefRefPtr<CefWindow> window) override;
@ -179,12 +179,15 @@ class ViewsWindow : public CefBrowserViewDelegate,
bool GetTitlebarHeight(CefRefPtr<CefWindow> window,
float* titlebar_height) override;
bool CanResize(CefRefPtr<CefWindow> window) override;
bool CanMaximize(CefRefPtr<CefWindow> window) override;
bool CanMinimize(CefRefPtr<CefWindow> window) override;
bool CanClose(CefRefPtr<CefWindow> window) override;
bool OnAccelerator(CefRefPtr<CefWindow> window, int command_id) override;
bool OnKeyEvent(CefRefPtr<CefWindow> window,
const CefKeyEvent& event) override;
// CefViewDelegate methods:
CefSize GetPreferredSize(CefRefPtr<CefView> view) override;
CefSize GetMinimumSize(CefRefPtr<CefView> view) override;
void OnFocus(CefRefPtr<CefView> view) override;
void OnBlur(CefRefPtr<CefView> 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<CefBrowserView> browser_view);
ViewsWindow(WindowType type,
Delegate* delegate,
CefRefPtr<CefBrowserView> browser_view);
void SetBrowserView(CefRefPtr<CefBrowserView> browser_view);
@ -239,6 +244,7 @@ class ViewsWindow : public CefBrowserViewDelegate,
void NudgeWindow();
const WindowType type_;
Delegate* delegate_; // Not owned by this object.
CefRefPtr<CefBrowserView> 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<CefWindow> window_;
CefRefPtr<CefMenuModel> button_menu_model_;

View File

@ -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];
}

View File

@ -362,6 +362,12 @@
<action selector="menuTestsWindowPopup:" target="-2" id="8GQ-Ph-2iP"/>
</connections>
</menuItem>
<menuItem title="Dialog Window" id="a52-WG-ltZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="menuTestsWindowDialog:" target="-2" id="8GQ-Ph-2iQ"/>
</connections>
</menuItem>
<menuItem title="Request" id="Ymm-D1-9xh">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>

View File

@ -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

View File

@ -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