mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-02-20 22:20:41 +01:00
Split the Alloy runtime into bootstrap and style components. Support creation of Alloy style browsers and windows with the Chrome runtime. Chrome runtime (`--enable-chrome-runtime`) + Alloy style (`--use-alloy-style`) supports Views (`--use-views`), native parent (`--use-native`) and windowless rendering (`--off-screen-rendering-enabled`). Print preview is supported in all cases except with windowless rendering on all platforms and native parent on MacOS. It is disabled by default with Alloy style for legacy compatibility. Where supported it can be enabled or disabled globally using `--[enable|disable]-print-preview` or configured on a per-RequestContext basis using the `printing.print_preview_disabled` preference. It also behaves as expected when triggered via the PDF viewer print button. Chrome runtime + Alloy style behavior differs from Alloy runtime in the following significant ways: - Supports Chrome error pages by default. - DevTools popups are Chrome style only (cannot be windowless). - The Alloy extension API will not supported. Chrome runtime + Alloy style passes all expected Alloy ceftests except the following: - `DisplayTest.AutoResize` (Alloy extension API not supported) - `DownloadTest.*` (Download API not yet supported) - `ExtensionTest.*` (Alloy extension API not supported) This change also adds Chrome runtime support for CefContextMenuHandler::RunContextMenu (see #3293). This change also explicitly blocks (and doesn't retry) FrameAttached requests from PDF viewer and print preview excluded frames (see #3664). Known issues specific to Chrome runtime + Alloy style: - DevTools popup with windowless rendering doesn't load successfully. Use windowed rendering or remote debugging as a workaround. - Chrome style Window with Alloy style BrowserView (`--use-alloy-style --use-chrome-style-window`) does not show Chrome theme changes. To test: - Run `ceftests --enable-chrome-runtime --use-alloy-style [--use-chrome-style-window] [--use-views|--use-native] --gtest_filter=...` - Run `cefclient --enable-chrome-runtime --use-alloy-style [--use-chrome-style-window] [--use-views|--use-native|--off-screen-rendering-enabled]` - Run `cefsimple --enable-chrome-runtime --use-alloy-style [--use-views]`
1052 lines
32 KiB
C++
1052 lines
32 KiB
C++
// Copyright 2016 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 "libcef/browser/views/window_view.h"
|
|
|
|
#include <memory>
|
|
|
|
#if BUILDFLAG(IS_LINUX)
|
|
#include "ui/base/ozone_buildflags.h"
|
|
#if BUILDFLAG(IS_OZONE_X11)
|
|
// Include first due to redefinition of x11::EventMask.
|
|
#include "ui/base/x/x11_util.h"
|
|
#endif
|
|
#endif
|
|
|
|
#include "libcef/browser/geometry_util.h"
|
|
#include "libcef/browser/image_impl.h"
|
|
#include "libcef/browser/views/widget.h"
|
|
#include "libcef/browser/views/window_impl.h"
|
|
#include "libcef/features/runtime.h"
|
|
|
|
#include "base/ranges/algorithm.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"
|
|
|
|
#if BUILDFLAG(IS_LINUX)
|
|
#if BUILDFLAG(IS_OZONE_X11)
|
|
#include "ui/gfx/x/atom_cache.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 "ui/display/win/screen_win.h"
|
|
#include "ui/views/win/hwnd_util.h"
|
|
#endif
|
|
|
|
#if defined(USE_AURA)
|
|
#include "ui/aura/window.h"
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
// Specialize ClientView to handle Widget-related events.
|
|
class ClientViewEx : public views::ClientView {
|
|
public:
|
|
ClientViewEx(views::Widget* widget,
|
|
views::View* contents_view,
|
|
CefWindowView::Delegate* window_delegate)
|
|
: views::ClientView(widget, contents_view),
|
|
window_delegate_(window_delegate) {
|
|
DCHECK(window_delegate_);
|
|
}
|
|
|
|
ClientViewEx(const ClientViewEx&) = delete;
|
|
ClientViewEx& operator=(const ClientViewEx&) = delete;
|
|
|
|
views::CloseRequestResult OnWindowCloseRequested() override {
|
|
return window_delegate_->CanWidgetClose()
|
|
? views::CloseRequestResult::kCanClose
|
|
: views::CloseRequestResult::kCannotClose;
|
|
}
|
|
|
|
private:
|
|
CefWindowView::Delegate* window_delegate_; // Not owned by this object.
|
|
};
|
|
|
|
// Extend NativeFrameView with draggable region handling.
|
|
class NativeFrameViewEx : public views::NativeFrameView {
|
|
public:
|
|
NativeFrameViewEx(views::Widget* widget, CefWindowView* view)
|
|
: views::NativeFrameView(widget), widget_(widget), view_(view) {}
|
|
|
|
NativeFrameViewEx(const NativeFrameViewEx&) = delete;
|
|
NativeFrameViewEx& operator=(const NativeFrameViewEx&) = delete;
|
|
|
|
gfx::Rect GetWindowBoundsForClientBounds(
|
|
const gfx::Rect& client_bounds) const override {
|
|
#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(/*required=*/true)) {
|
|
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) != nullptr);
|
|
|
|
// 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(
|
|
client_bounds);
|
|
#endif
|
|
}
|
|
|
|
int NonClientHitTest(const gfx::Point& point) override {
|
|
if (widget_->IsFullscreen()) {
|
|
return HTCLIENT;
|
|
}
|
|
|
|
// Test for mouse clicks that fall within the draggable region.
|
|
SkRegion* draggable_region = view_->draggable_region();
|
|
if (draggable_region && draggable_region->contains(point.x(), point.y())) {
|
|
return HTCAPTION;
|
|
}
|
|
|
|
return views::NativeFrameView::NonClientHitTest(point);
|
|
}
|
|
|
|
void OnThemeChanged() override {
|
|
views::NativeFrameView::OnThemeChanged();
|
|
view_util::UpdateTitlebarTheme(widget_);
|
|
}
|
|
|
|
private:
|
|
// Not owned by this object.
|
|
views::Widget* widget_;
|
|
CefWindowView* view_;
|
|
};
|
|
|
|
// The area inside the frame border that can be clicked and dragged for resizing
|
|
// the window. Only used in restored mode.
|
|
const int kResizeBorderThickness = 4;
|
|
|
|
// The distance from each window corner that triggers diagonal resizing. Only
|
|
// used in restored mode.
|
|
const int kResizeAreaCornerSize = 16;
|
|
|
|
// Implement NonClientFrameView without the system default caption and icon but
|
|
// with a resizable border. Based on AppWindowFrameView and CustomFrameView.
|
|
class CaptionlessFrameView : public views::NonClientFrameView {
|
|
public:
|
|
CaptionlessFrameView(views::Widget* widget, CefWindowView* view)
|
|
: widget_(widget), view_(view) {}
|
|
|
|
CaptionlessFrameView(const CaptionlessFrameView&) = delete;
|
|
CaptionlessFrameView& operator=(const CaptionlessFrameView&) = delete;
|
|
|
|
gfx::Rect GetBoundsForClientView() const override {
|
|
return client_view_bounds_;
|
|
}
|
|
|
|
gfx::Rect GetWindowBoundsForClientBounds(
|
|
const gfx::Rect& client_bounds) const override {
|
|
return client_bounds;
|
|
}
|
|
|
|
int NonClientHitTest(const gfx::Point& point) override {
|
|
if (widget_->IsFullscreen()) {
|
|
return HTCLIENT;
|
|
}
|
|
|
|
// Sanity check.
|
|
if (!bounds().Contains(point)) {
|
|
return HTNOWHERE;
|
|
}
|
|
|
|
// Check the frame first, as we allow a small area overlapping the contents
|
|
// to be used for resize handles.
|
|
bool can_ever_resize = widget_->widget_delegate()
|
|
? widget_->widget_delegate()->CanResize()
|
|
: false;
|
|
// Don't allow overlapping resize handles when the window is maximized or
|
|
// fullscreen, as it can't be resized in those states.
|
|
int resize_border_thickness = ResizeBorderThickness();
|
|
int frame_component = GetHTComponentForFrame(
|
|
point,
|
|
gfx::Insets::VH(resize_border_thickness, resize_border_thickness),
|
|
kResizeAreaCornerSize, kResizeAreaCornerSize, can_ever_resize);
|
|
if (frame_component != HTNOWHERE) {
|
|
return frame_component;
|
|
}
|
|
|
|
// Test for mouse clicks that fall within the draggable region.
|
|
SkRegion* draggable_region = view_->draggable_region();
|
|
if (draggable_region && draggable_region->contains(point.x(), point.y())) {
|
|
return HTCAPTION;
|
|
}
|
|
|
|
int client_component = widget_->client_view()->NonClientHitTest(point);
|
|
if (client_component != HTNOWHERE) {
|
|
return client_component;
|
|
}
|
|
|
|
// Caption is a safe default.
|
|
return HTCAPTION;
|
|
}
|
|
|
|
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {
|
|
// Nothing to do here.
|
|
}
|
|
|
|
void ResetWindowControls() override {
|
|
// Nothing to do here.
|
|
}
|
|
|
|
void UpdateWindowIcon() override {
|
|
// Nothing to do here.
|
|
}
|
|
|
|
void UpdateWindowTitle() override {
|
|
// Nothing to do here.
|
|
}
|
|
|
|
void SizeConstraintsChanged() override {
|
|
// Nothing to do here.
|
|
}
|
|
|
|
void OnPaint(gfx::Canvas* canvas) override {
|
|
// Nothing to do here.
|
|
}
|
|
|
|
void Layout(views::View::PassKey) override {
|
|
client_view_bounds_.SetRect(0, 0, width(), height());
|
|
LayoutSuperclass<views::NonClientFrameView>(this);
|
|
}
|
|
|
|
gfx::Size CalculatePreferredSize() const override {
|
|
return widget_->non_client_view()
|
|
->GetWindowBoundsForClientBounds(
|
|
gfx::Rect(widget_->client_view()->GetPreferredSize()))
|
|
.size();
|
|
}
|
|
|
|
gfx::Size GetMinimumSize() const override {
|
|
return widget_->non_client_view()
|
|
->GetWindowBoundsForClientBounds(
|
|
gfx::Rect(widget_->client_view()->GetMinimumSize()))
|
|
.size();
|
|
}
|
|
|
|
gfx::Size GetMaximumSize() const override {
|
|
gfx::Size max_size = widget_->client_view()->GetMaximumSize();
|
|
gfx::Size converted_size =
|
|
widget_->non_client_view()
|
|
->GetWindowBoundsForClientBounds(gfx::Rect(max_size))
|
|
.size();
|
|
return gfx::Size(max_size.width() == 0 ? 0 : converted_size.width(),
|
|
max_size.height() == 0 ? 0 : converted_size.height());
|
|
}
|
|
|
|
private:
|
|
int ResizeBorderThickness() const {
|
|
return (widget_->IsMaximized() || widget_->IsFullscreen()
|
|
? 0
|
|
: kResizeBorderThickness);
|
|
}
|
|
|
|
// Not owned by this object.
|
|
views::Widget* widget_;
|
|
CefWindowView* view_;
|
|
|
|
// The bounds of the client view, in this view's coordinates.
|
|
gfx::Rect client_view_bounds_;
|
|
};
|
|
|
|
bool IsWindowBorderHit(int code) {
|
|
// On Windows HTLEFT = 10 and HTBORDER = 18. Values are not ordered the same
|
|
// in base/hit_test.h for non-Windows platforms.
|
|
#if BUILDFLAG(IS_WIN)
|
|
return code >= HTLEFT && code <= HTBORDER;
|
|
#else
|
|
return code == HTLEFT || code == HTRIGHT || code == HTTOP ||
|
|
code == HTTOPLEFT || code == HTTOPRIGHT || code == HTBOTTOM ||
|
|
code == HTBOTTOMLEFT || code == HTBOTTOMRIGHT || code == HTBORDER;
|
|
#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));
|
|
}
|
|
|
|
bool ComputeAlloyStyle(CefWindowDelegate* cef_delegate) {
|
|
const bool supports_chrome_style = cef::IsChromeRuntimeEnabled();
|
|
const auto default_style = cef::IsAlloyRuntimeEnabled()
|
|
? CEF_RUNTIME_STYLE_ALLOY
|
|
: CEF_RUNTIME_STYLE_CHROME;
|
|
|
|
auto result_style = default_style;
|
|
|
|
if (cef_delegate) {
|
|
auto requested_style = cef_delegate->GetWindowRuntimeStyle();
|
|
if (requested_style == CEF_RUNTIME_STYLE_ALLOY) {
|
|
// Alloy style is always supported.
|
|
result_style = requested_style;
|
|
} else if (requested_style == CEF_RUNTIME_STYLE_CHROME) {
|
|
if (supports_chrome_style) {
|
|
result_style = requested_style;
|
|
} else {
|
|
LOG(ERROR) << "GetWindowRuntimeStyle() requested Chrome style; only "
|
|
"Alloy style is supported";
|
|
}
|
|
}
|
|
}
|
|
|
|
return result_style == CEF_RUNTIME_STYLE_ALLOY;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CefWindowView::CefWindowView(CefWindowDelegate* cef_delegate,
|
|
Delegate* window_delegate)
|
|
: ParentClass(cef_delegate),
|
|
window_delegate_(window_delegate),
|
|
is_alloy_style_(ComputeAlloyStyle(cef_delegate)) {
|
|
DCHECK(window_delegate_);
|
|
}
|
|
|
|
void CefWindowView::CreateWidget(gfx::AcceleratedWidget parent_widget) {
|
|
DCHECK(!GetWidget());
|
|
|
|
// |widget| is owned by the NativeWidget and will be destroyed in response to
|
|
// a native destruction message.
|
|
CefWidget* cef_widget = CefWidget::Create(this);
|
|
views::Widget* widget = cef_widget->GetWidget();
|
|
|
|
views::Widget::InitParams params;
|
|
params.delegate = this;
|
|
|
|
views::Widget* host_widget = nullptr;
|
|
|
|
bool can_activate = true;
|
|
bool can_resize = true;
|
|
|
|
const bool has_native_parent = parent_widget != gfx::kNullAcceleratedWidget;
|
|
if (has_native_parent) {
|
|
params.parent_widget = parent_widget;
|
|
|
|
// Remove the window frame.
|
|
is_frameless_ = true;
|
|
|
|
// See CalculateWindowStylesFromInitParams in
|
|
// ui/views/widget/widget_hwnd_utils.cc for the conversion of |params| to
|
|
// Windows style flags.
|
|
// - Set the WS_CHILD flag.
|
|
params.child = true;
|
|
// - Set the WS_VISIBLE flag.
|
|
params.type = views::Widget::InitParams::TYPE_CONTROL;
|
|
// - Don't set the WS_EX_COMPOSITED flag.
|
|
params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
|
|
} else {
|
|
params.type = views::Widget::InitParams::TYPE_WINDOW;
|
|
}
|
|
|
|
// Cause WidgetDelegate::DeleteDelegate() to delete |this| after executing the
|
|
// registered DeleteDelegate callback.
|
|
SetOwnedByWidget(true);
|
|
RegisterDeleteDelegateCallback(
|
|
base::BindOnce(&CefWindowView::DeleteDelegate, base::Unretained(this)));
|
|
|
|
if (cef_delegate()) {
|
|
CefRefPtr<CefWindow> cef_window = GetCefWindow();
|
|
|
|
auto bounds = cef_delegate()->GetInitialBounds(cef_window);
|
|
params.bounds = gfx::Rect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
|
|
if (has_native_parent) {
|
|
DCHECK(!params.bounds.IsEmpty());
|
|
} else {
|
|
is_frameless_ = cef_delegate()->IsFrameless(cef_window);
|
|
|
|
params.native_widget =
|
|
view_util::CreateNativeWidget(widget, cef_window, cef_delegate());
|
|
|
|
can_resize = cef_delegate()->CanResize(cef_window);
|
|
|
|
const auto show_state = cef_delegate()->GetInitialShowState(cef_window);
|
|
switch (show_state) {
|
|
case CEF_SHOW_STATE_NORMAL:
|
|
params.show_state = ui::SHOW_STATE_NORMAL;
|
|
break;
|
|
case CEF_SHOW_STATE_MINIMIZED:
|
|
params.show_state = ui::SHOW_STATE_MINIMIZED;
|
|
break;
|
|
case CEF_SHOW_STATE_MAXIMIZED:
|
|
params.show_state = ui::SHOW_STATE_MAXIMIZED;
|
|
break;
|
|
case CEF_SHOW_STATE_FULLSCREEN:
|
|
params.show_state = ui::SHOW_STATE_FULLSCREEN;
|
|
break;
|
|
case CEF_SHOW_STATE_HIDDEN:
|
|
#if BUILDFLAG(IS_MAC)
|
|
params.show_state = ui::SHOW_STATE_HIDDEN;
|
|
#else
|
|
params.show_state = ui::SHOW_STATE_MINIMIZED;
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
bool is_menu = false;
|
|
bool can_activate_menu = true;
|
|
CefRefPtr<CefWindow> parent_window = cef_delegate()->GetParentWindow(
|
|
cef_window, &is_menu, &can_activate_menu);
|
|
if (parent_window && !parent_window->IsSame(cef_window)) {
|
|
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;
|
|
|
|
// Don't set "always on top" for the window.
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (params.bounds.IsEmpty()) {
|
|
// The window will be placed on the default screen with origin (0,0).
|
|
params.bounds = gfx::Rect(CalculatePreferredSize());
|
|
if (params.bounds.IsEmpty()) {
|
|
// Choose a reasonable default size.
|
|
params.bounds.set_size({800, 600});
|
|
}
|
|
}
|
|
|
|
if (can_activate) {
|
|
// Cause WidgetDelegate::CanActivate to return true.
|
|
params.activatable = views::Widget::InitParams::Activatable::kYes;
|
|
}
|
|
|
|
SetCanResize(can_resize);
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
if (is_frameless_) {
|
|
// Don't show the native window caption. Setting this value on Linux will
|
|
// result in window resize artifacts.
|
|
params.remove_standard_frame = true;
|
|
}
|
|
#endif
|
|
|
|
widget->Init(std::move(params));
|
|
widget->AddObserver(this);
|
|
|
|
// |widget| should now be associated with |this|.
|
|
DCHECK_EQ(widget, GetWidget());
|
|
// |widget| must be top-level for focus handling to work correctly.
|
|
DCHECK(widget->is_top_level());
|
|
|
|
if (can_activate) {
|
|
// |widget| must be activatable for focus handling to work correctly.
|
|
DCHECK(widget->widget_delegate()->CanActivate());
|
|
}
|
|
|
|
cef_widget->Initialized();
|
|
|
|
#if BUILDFLAG(IS_LINUX)
|
|
#if BUILDFLAG(IS_OZONE_X11)
|
|
auto x11window = static_cast<x11::Window>(view_util::GetWindowHandle(widget));
|
|
CHECK(x11window != x11::Window::None);
|
|
|
|
if (is_frameless_) {
|
|
ui::SetUseOSWindowFrame(x11window, false);
|
|
}
|
|
|
|
if (host_widget) {
|
|
auto parent = static_cast<gfx::AcceleratedWidget>(
|
|
view_util::GetWindowHandle(host_widget));
|
|
CHECK(parent);
|
|
|
|
auto connection = x11::Connection::Get();
|
|
|
|
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 (connection->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:
|
|
connection->SetProperty(x11window, x11::Atom::WM_TRANSIENT_FOR,
|
|
x11::Atom::WINDOW, parent);
|
|
connection->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 {
|
|
CefRefPtr<CefWindow> window = GetCefPanel()->AsWindow();
|
|
DCHECK(window);
|
|
return window;
|
|
}
|
|
|
|
void CefWindowView::DeleteDelegate() {
|
|
// Any overlays should already be removed.
|
|
DCHECK(overlay_hosts_.empty());
|
|
|
|
// Remove all child Views before deleting the Window so that notifications
|
|
// resolve correctly.
|
|
RemoveAllChildViews();
|
|
|
|
window_delegate_->OnWindowViewDeleted();
|
|
}
|
|
|
|
bool CefWindowView::CanMinimize() const {
|
|
if (!cef_delegate()) {
|
|
return true;
|
|
}
|
|
return cef_delegate()->CanMinimize(GetCefWindow());
|
|
}
|
|
|
|
bool CefWindowView::CanMaximize() const {
|
|
if (!cef_delegate()) {
|
|
return true;
|
|
}
|
|
return cef_delegate()->CanMaximize(GetCefWindow());
|
|
}
|
|
|
|
std::u16string CefWindowView::GetWindowTitle() const {
|
|
return title_;
|
|
}
|
|
|
|
ui::ImageModel CefWindowView::GetWindowIcon() {
|
|
if (!window_icon_) {
|
|
return ParentClass::GetWindowIcon();
|
|
}
|
|
auto image_skia =
|
|
static_cast<CefImageImpl*>(window_icon_.get())
|
|
->GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
|
|
return ui::ImageModel::FromImageSkia(image_skia);
|
|
}
|
|
|
|
ui::ImageModel CefWindowView::GetWindowAppIcon() {
|
|
if (!window_app_icon_) {
|
|
return ParentClass::GetWindowAppIcon();
|
|
}
|
|
auto image_skia =
|
|
static_cast<CefImageImpl*>(window_app_icon_.get())
|
|
->GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
|
|
return ui::ImageModel::FromImageSkia(image_skia);
|
|
}
|
|
|
|
void CefWindowView::WindowClosing() {
|
|
// Close any overlays now, before the Widget is destroyed.
|
|
// Use a copy of the array because the original may be modified while
|
|
// iterating.
|
|
std::vector<CefOverlayViewHost*> overlay_hosts = overlay_hosts_;
|
|
for (auto* overlay_host : overlay_hosts) {
|
|
overlay_host->Close();
|
|
}
|
|
|
|
#if BUILDFLAG(IS_LINUX)
|
|
#if BUILDFLAG(IS_OZONE_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();
|
|
|
|
views::WidgetDelegateView::WindowClosing();
|
|
}
|
|
|
|
views::View* CefWindowView::GetContentsView() {
|
|
// |this| will be the "Contents View" hosted by the Widget via ClientView and
|
|
// RootView.
|
|
return this;
|
|
}
|
|
|
|
views::ClientView* CefWindowView::CreateClientView(views::Widget* widget) {
|
|
return new ClientViewEx(widget, GetContentsView(), window_delegate_);
|
|
}
|
|
|
|
std::unique_ptr<views::NonClientFrameView>
|
|
CefWindowView::CreateNonClientFrameView(views::Widget* widget) {
|
|
if (is_frameless_) {
|
|
// Custom frame type that doesn't render a caption.
|
|
return std::make_unique<CaptionlessFrameView>(widget, this);
|
|
} else if (widget->ShouldUseNativeFrame()) {
|
|
// DesktopNativeWidgetAura::CreateNonClientFrameView() returns
|
|
// NativeFrameView by default. Extend that type.
|
|
return std::make_unique<NativeFrameViewEx>(widget, this);
|
|
}
|
|
|
|
// Use Chromium provided CustomFrameView. In case if we would like to
|
|
// customize the frame, provide own implementation.
|
|
return nullptr;
|
|
}
|
|
|
|
bool CefWindowView::ShouldDescendIntoChildForEventHandling(
|
|
gfx::NativeView child,
|
|
const gfx::Point& location) {
|
|
if (is_frameless_) {
|
|
// If the window is resizable it should claim mouse events that fall on the
|
|
// window border.
|
|
views::NonClientFrameView* ncfv = GetNonClientFrameView();
|
|
if (ncfv) {
|
|
int result = ncfv->NonClientHitTest(location);
|
|
if (IsWindowBorderHit(result)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The window should claim mouse events that fall within the draggable region.
|
|
return !draggable_region_.get() ||
|
|
!draggable_region_->contains(location.x(), location.y());
|
|
}
|
|
|
|
bool CefWindowView::MaybeGetMinimumSize(gfx::Size* size) const {
|
|
#if BUILDFLAG(IS_LINUX)
|
|
// Resize is disabled on Linux by returning the preferred size as the min/max
|
|
// size.
|
|
if (!CanResize()) {
|
|
*size = CalculatePreferredSize();
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool CefWindowView::MaybeGetMaximumSize(gfx::Size* size) const {
|
|
#if BUILDFLAG(IS_LINUX)
|
|
// Resize is disabled on Linux by returning the preferred size as the min/max
|
|
// size.
|
|
if (!CanResize()) {
|
|
*size = CalculatePreferredSize();
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void CefWindowView::ViewHierarchyChanged(
|
|
const views::ViewHierarchyChangedDetails& details) {
|
|
if (details.child == this) {
|
|
// This View's parent types (RootView, ClientView) are not exposed via the
|
|
// CEF API. Therefore don't send notifications about this View's parent
|
|
// changes.
|
|
return;
|
|
}
|
|
|
|
ParentClass::ViewHierarchyChanged(details);
|
|
}
|
|
|
|
void CefWindowView::OnThemeChanged() {
|
|
bool initialized = false;
|
|
if (auto* cef_widget = CefWidget::GetForWidget(GetWidget())) {
|
|
initialized = cef_widget->IsInitialized();
|
|
}
|
|
|
|
if (!initialized) {
|
|
// Skip the CefViewView logic.
|
|
views::WidgetDelegateView::OnThemeChanged();
|
|
} else {
|
|
ParentClass::OnThemeChanged();
|
|
}
|
|
}
|
|
|
|
void CefWindowView::OnWidgetActivationChanged(views::Widget* widget,
|
|
bool active) {
|
|
if (cef_delegate()) {
|
|
cef_delegate()->OnWindowActivationChanged(GetCefWindow(), active);
|
|
}
|
|
}
|
|
|
|
void CefWindowView::OnWidgetBoundsChanged(views::Widget* widget,
|
|
const gfx::Rect& new_bounds) {
|
|
// Size is set to zero when the host Widget is hidden. We don't need to move
|
|
// overlays in that case.
|
|
if (!new_bounds.IsEmpty()) {
|
|
MoveOverlaysIfNecessary();
|
|
}
|
|
|
|
if (cef_delegate()) {
|
|
cef_delegate()->OnWindowBoundsChanged(
|
|
GetCefWindow(), {new_bounds.x(), new_bounds.y(), new_bounds.width(),
|
|
new_bounds.height()});
|
|
}
|
|
}
|
|
|
|
display::Display CefWindowView::GetDisplay() const {
|
|
const views::Widget* widget = GetWidget();
|
|
if (widget) {
|
|
return view_util::GetDisplayMatchingBounds(
|
|
widget->GetWindowBoundsInScreen(), false);
|
|
}
|
|
return display::Display();
|
|
}
|
|
|
|
void CefWindowView::SetTitle(const std::u16string& title) {
|
|
title_ = title;
|
|
views::Widget* widget = GetWidget();
|
|
if (widget) {
|
|
widget->UpdateWindowTitle();
|
|
}
|
|
}
|
|
|
|
void CefWindowView::SetWindowIcon(CefRefPtr<CefImage> window_icon) {
|
|
if (std::max(window_icon->GetWidth(), window_icon->GetHeight()) != 16U) {
|
|
DLOG(ERROR) << "Window icons must be 16 DIP in size.";
|
|
return;
|
|
}
|
|
|
|
window_icon_ = window_icon;
|
|
views::Widget* widget = GetWidget();
|
|
if (widget) {
|
|
widget->UpdateWindowIcon();
|
|
}
|
|
}
|
|
|
|
void CefWindowView::SetWindowAppIcon(CefRefPtr<CefImage> window_app_icon) {
|
|
window_app_icon_ = window_app_icon;
|
|
views::Widget* widget = GetWidget();
|
|
if (widget) {
|
|
widget->UpdateWindowIcon();
|
|
}
|
|
}
|
|
|
|
CefRefPtr<CefOverlayController> CefWindowView::AddOverlayView(
|
|
CefRefPtr<CefView> view,
|
|
cef_docking_mode_t docking_mode,
|
|
bool can_activate) {
|
|
DCHECK(view.get());
|
|
DCHECK(view->IsValid());
|
|
if (!view.get() || !view->IsValid()) {
|
|
return nullptr;
|
|
}
|
|
|
|
views::Widget* widget = GetWidget();
|
|
if (widget) {
|
|
// Owned by the View hierarchy. Acts as a z-order reference for the overlay.
|
|
auto overlay_host_view = AddChildView(std::make_unique<views::View>());
|
|
|
|
// Owned by the resulting Widget, after calling Init().
|
|
auto* overlay_host = new CefOverlayViewHost(this, docking_mode);
|
|
overlay_hosts_.push_back(overlay_host);
|
|
|
|
overlay_host->Init(overlay_host_view, view, can_activate);
|
|
|
|
return overlay_host->controller();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void CefWindowView::RemoveOverlayView(CefOverlayViewHost* host,
|
|
views::View* host_view) {
|
|
DCHECK_EQ(host_view->parent(), this);
|
|
RemoveChildView(host_view);
|
|
|
|
const auto it = base::ranges::find_if(
|
|
overlay_hosts_,
|
|
[host](CefOverlayViewHost* current) { return current == host; });
|
|
DCHECK(it != overlay_hosts_.end());
|
|
overlay_hosts_.erase(it);
|
|
}
|
|
|
|
void CefWindowView::MoveOverlaysIfNecessary() {
|
|
if (overlay_hosts_.empty()) {
|
|
return;
|
|
}
|
|
for (auto& overlay_host : overlay_hosts_) {
|
|
overlay_host->MoveIfNecessary();
|
|
}
|
|
}
|
|
|
|
void CefWindowView::InvalidateExclusionRegions() {
|
|
if (last_dialog_top_inset_ != -1) {
|
|
last_dialog_top_y_ = last_dialog_top_inset_ = -1;
|
|
}
|
|
}
|
|
|
|
void CefWindowView::SetDraggableRegions(
|
|
const std::vector<CefDraggableRegion>& regions) {
|
|
if (regions.empty() && !draggable_region_) {
|
|
// Still empty.
|
|
return;
|
|
}
|
|
|
|
InvalidateExclusionRegions();
|
|
|
|
if (regions.empty()) {
|
|
draggable_region_.reset();
|
|
draggable_rects_.clear();
|
|
return;
|
|
}
|
|
|
|
draggable_region_ = std::make_unique<SkRegion>();
|
|
for (const CefDraggableRegion& region : regions) {
|
|
draggable_region_->op(
|
|
{region.bounds.x, region.bounds.y,
|
|
region.bounds.x + region.bounds.width,
|
|
region.bounds.y + region.bounds.height},
|
|
region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
|
|
|
|
if (region.draggable) {
|
|
draggable_rects_.emplace_back(region.bounds.x, region.bounds.y,
|
|
region.bounds.width, region.bounds.height);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CefWindowView::OnOverlayBoundsChanged() {
|
|
InvalidateExclusionRegions();
|
|
}
|
|
|
|
views::NonClientFrameView* CefWindowView::GetNonClientFrameView() const {
|
|
const views::Widget* widget = GetWidget();
|
|
if (!widget) {
|
|
return nullptr;
|
|
}
|
|
if (!widget->non_client_view()) {
|
|
return nullptr;
|
|
}
|
|
return widget->non_client_view()->frame_view();
|
|
}
|
|
|
|
void CefWindowView::UpdateBoundingBox(gfx::Rect* bounds,
|
|
bool add_titlebar_height) const {
|
|
// Max distance from the edges of |bounds| to qualify for subtraction.
|
|
const int kMaxDistance = 10;
|
|
|
|
for (auto& overlay_host : overlay_hosts_) {
|
|
*bounds = SubtractOverlayFromBoundingBox(*bounds, overlay_host->bounds(),
|
|
kMaxDistance);
|
|
}
|
|
|
|
for (auto& rect : draggable_rects_) {
|
|
*bounds = SubtractOverlayFromBoundingBox(*bounds, rect, kMaxDistance);
|
|
}
|
|
|
|
if (auto titlebar_height = GetTitlebarHeight(add_titlebar_height)) {
|
|
gfx::Insets inset;
|
|
if (add_titlebar_height) {
|
|
inset.set_top(*titlebar_height);
|
|
} else if (bounds->y() < *titlebar_height) {
|
|
inset.set_top(*titlebar_height - bounds->y());
|
|
}
|
|
|
|
if (!inset.IsEmpty()) {
|
|
bounds->Inset(inset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CefWindowView::UpdateFindBarBoundingBox(gfx::Rect* bounds) const {
|
|
#if BUILDFLAG(IS_MAC)
|
|
// For framed windows on macOS we must add the titlebar height.
|
|
const bool add_titlebar_height = !is_frameless_;
|
|
#else
|
|
const bool add_titlebar_height = false;
|
|
#endif
|
|
|
|
UpdateBoundingBox(bounds, add_titlebar_height);
|
|
}
|
|
|
|
void CefWindowView::UpdateDialogTopInset(int* dialog_top_y) const {
|
|
if (*dialog_top_y == last_dialog_top_y_ && last_dialog_top_inset_ != -1) {
|
|
// Return the cached value.
|
|
*dialog_top_y = last_dialog_top_inset_;
|
|
return;
|
|
}
|
|
|
|
const views::Widget* widget = GetWidget();
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
gfx::Rect bounds(widget->GetSize());
|
|
if (*dialog_top_y > 0) {
|
|
// Start with the value computed in
|
|
// BrowserViewLayout::LayoutBookmarkAndInfoBars.
|
|
gfx::Insets inset;
|
|
inset.set_top(*dialog_top_y);
|
|
bounds.Inset(inset);
|
|
}
|
|
|
|
UpdateBoundingBox(&bounds, /*add_titlebar_height=*/false);
|
|
|
|
last_dialog_top_y_ = *dialog_top_y;
|
|
last_dialog_top_inset_ = bounds.y();
|
|
|
|
*dialog_top_y = bounds.y();
|
|
}
|
|
|
|
views::Widget* CefWindowView::host_widget() const {
|
|
if (host_widget_destruction_observer_) {
|
|
return host_widget_destruction_observer_->widget();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::optional<float> CefWindowView::GetTitlebarHeight(bool required) const {
|
|
if (cef_delegate()) {
|
|
float title_bar_height = 0;
|
|
const bool has_title_bar_height =
|
|
cef_delegate()->GetTitlebarHeight(GetCefWindow(), &title_bar_height);
|
|
if (has_title_bar_height) {
|
|
return title_bar_height;
|
|
}
|
|
}
|
|
|
|
#if BUILDFLAG(IS_MAC)
|
|
if (required) {
|
|
// For framed windows on macOS we must include the titlebar height in the
|
|
// UpdateFindBarBoundingBox() calculation.
|
|
return view_util::GetNSWindowTitleBarHeight(
|
|
const_cast<views::Widget*>(GetWidget()));
|
|
}
|
|
#endif
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
void CefWindowView::OnThemeColorsChanged(bool chrome_theme) {
|
|
if (cef_delegate()) {
|
|
cef_delegate()->OnThemeColorsChanged(GetCefWindow(), chrome_theme);
|
|
}
|
|
}
|