cef/libcef/browser/views/overlay_view_host.cc

385 lines
12 KiB
C++
Raw Normal View History

// Copyright 2021 The Chromium Embedded Framework Authors. Portions copyright
// 2011 The Chromium 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 "cef/libcef/browser/views/overlay_view_host.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
#include "cef/libcef/browser/views/view_util.h"
#include "cef/libcef/browser/views/window_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/theme_copying_widget.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
namespace {
class CefOverlayControllerImpl : public CefOverlayController {
public:
CefOverlayControllerImpl(CefOverlayViewHost* host, CefRefPtr<CefView> view)
: host_(host), view_(view) {}
CefOverlayControllerImpl(const CefOverlayControllerImpl&) = delete;
CefOverlayControllerImpl& operator=(const CefOverlayControllerImpl&) = delete;
bool IsValid() override {
// View validity implies that CefOverlayViewHost is still valid, because the
// Widget that it owns (and that owns the View) is still valid.
return view_ && view_->IsValid();
}
bool IsSame(CefRefPtr<CefOverlayController> that) override {
return IsValid() && that && that->IsValid() &&
that->GetContentsView()->IsSame(view_);
}
CefRefPtr<CefView> GetContentsView() override { return view_; }
CefRefPtr<CefWindow> GetWindow() override {
if (IsValid()) {
return view_util::GetWindowFor(host_->window_view()->GetWidget());
}
return nullptr;
}
cef_docking_mode_t GetDockingMode() override {
if (IsValid()) {
return host_->docking_mode();
}
return CEF_DOCKING_MODE_TOP_LEFT;
}
void Destroy() override {
if (IsValid()) {
// Results in a call to Destroyed().
host_->Close();
}
}
void Destroyed() {
DCHECK(view_);
view_ = nullptr;
host_ = nullptr;
}
void SetBounds(const CefRect& bounds) override {
if (IsValid() && host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
host_->SetOverlayBounds(
gfx::Rect(bounds.x, bounds.y, bounds.width, bounds.height));
}
}
CefRect GetBounds() override {
if (IsValid()) {
const auto& bounds = host_->bounds();
return CefRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
}
return CefRect();
}
CefRect GetBoundsInScreen() override {
if (IsValid()) {
const auto& bounds = host_->widget()->GetWindowBoundsInScreen();
return CefRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
}
return CefRect();
}
void SetSize(const CefSize& size) override {
if (IsValid() && host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
// Update the size without changing the origin.
const auto& origin = host_->bounds().origin();
host_->SetOverlayBounds(
gfx::Rect(origin, gfx::Size(size.width, size.height)));
}
}
CefSize GetSize() override {
const auto& bounds = GetBounds();
return CefSize(bounds.width, bounds.height);
}
void SetPosition(const CefPoint& position) override {
if (IsValid() && host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
// Update the origin without changing the size.
const auto& size = host_->bounds().size();
host_->SetOverlayBounds(
gfx::Rect(gfx::Point(position.x, position.y), size));
}
}
CefPoint GetPosition() override {
const auto& bounds = GetBounds();
return CefPoint(bounds.x, bounds.y);
}
void SetInsets(const CefInsets& insets) override {
if (IsValid() && host_->docking_mode() != CEF_DOCKING_MODE_CUSTOM) {
host_->SetOverlayInsets(insets);
}
}
CefInsets GetInsets() override {
if (IsValid()) {
return host_->insets();
}
return CefInsets();
}
void SizeToPreferredSize() override {
if (IsValid()) {
if (host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
// Update the size without changing the origin.
const auto& origin = host_->bounds().origin();
const auto& preferred_size = host_->view()->GetPreferredSize();
host_->SetOverlayBounds(gfx::Rect(origin, preferred_size));
} else {
host_->MoveIfNecessary();
}
}
}
void SetVisible(bool visible) override {
if (IsValid()) {
if (visible) {
host_->MoveIfNecessary();
host_->widget()->Show();
} else {
host_->widget()->Hide();
}
}
}
bool IsVisible() override {
if (IsValid()) {
return host_->widget()->IsVisible();
}
return false;
}
bool IsDrawn() override { return IsVisible(); }
private:
raw_ptr<CefOverlayViewHost> host_;
CefRefPtr<CefView> view_;
IMPLEMENT_REFCOUNTING(CefOverlayControllerImpl);
};
} // namespace
CefOverlayViewHost::CefOverlayViewHost(CefWindowView* window_view,
cef_docking_mode_t docking_mode)
: window_view_(window_view), docking_mode_(docking_mode) {}
void CefOverlayViewHost::Init(views::View* host_view,
CefRefPtr<CefView> view,
bool can_activate) {
DCHECK(view);
// Match the logic in CEF_PANEL_IMPL_D::AddChildView().
auto controls_view = view->IsAttached()
? base::WrapUnique(view_util::GetFor(view))
: view_util::PassOwnership(view);
DCHECK(controls_view.get());
cef_controller_ = new CefOverlayControllerImpl(this, view);
// Initialize the Widget. |widget_| will be deleted by the NativeWidget or
// when WidgetDelegate::DeleteDelegate() deletes |this|.
widget_ = std::make_unique<ThemeCopyingWidget>(window_view_->GetWidget());
views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
params.delegate = this;
params.name = "CefOverlayViewHost";
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.parent = window_view_->GetWidget()->GetNativeView();
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
params.activatable = can_activate
? views::Widget::InitParams::Activatable::kYes
: views::Widget::InitParams::Activatable::kNo;
widget_->Init(std::move(params));
// |widget_| should now be associated with |this|.
DCHECK_EQ(widget_.get(), GetWidget());
// Make the Widget background transparent. The View might still be opaque.
if (widget_->GetCompositor()) {
widget_->GetCompositor()->SetBackgroundColor(SK_ColorTRANSPARENT);
}
host_view_ = host_view;
view_util::SetHostView(widget_.get(), host_view);
// Cause WidgetDelegate::DeleteDelegate() to delete |this| after executing the
// registered DeleteDelegate callback.
SetOwnedByWidget(true);
RegisterDeleteDelegateCallback(
base::BindOnce(&CefOverlayViewHost::Cleanup, base::Unretained(this)));
chrome: Add support for Alloy style browsers and windows (see #3681) 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]`
2024-04-17 18:01:26 +02:00
if (window_view_->IsChromeStyle()) {
// Some attributes associated with a Chrome toolbar are located via the
// Widget. See matching logic in BrowserView::AddedToWidget.
auto browser_view = BrowserView::GetBrowserViewForNativeWindow(
view_util::GetNativeWindow(window_view_->GetWidget()));
if (browser_view) {
widget_->SetNativeWindowProperty(BrowserView::kBrowserViewKey,
browser_view);
}
}
// Call AddChildView after the Widget properties have been configured.
// Notifications resulting from this call may attempt to access those
// properties (OnThemeChanged calling GetHostView, for example).
view_ = widget_->GetContentsView()->AddChildView(std::move(controls_view));
// Set the initial bounds after the View has been added to the Widget.
// Otherwise, preferred size won't calculate correctly.
gfx::Rect bounds;
if (docking_mode_ == CEF_DOCKING_MODE_CUSTOM) {
if (view_->size().IsEmpty()) {
// Size to the preferred size to start.
view_->SizeToPreferredSize();
}
// Top-left origin with existing size.
bounds = gfx::Rect(gfx::Point(), view_->size());
} else {
bounds = ComputeBounds();
}
SetOverlayBounds(bounds);
// Register for future bounds change notifications.
view_->AddObserver(this);
// Initially hidden.
widget_->Hide();
}
void CefOverlayViewHost::Close() {
if (widget_ && !widget_->IsClosed()) {
// Remove all references ASAP, before the Widget is destroyed.
Cleanup();
// Eventually calls DeleteDelegate().
widget_->Close();
}
}
void CefOverlayViewHost::MoveIfNecessary() {
if (bounds_changing_ || docking_mode_ == CEF_DOCKING_MODE_CUSTOM) {
return;
}
SetOverlayBounds(ComputeBounds());
}
void CefOverlayViewHost::SetOverlayBounds(const gfx::Rect& bounds) {
// Avoid re-entrancy of this method.
2023-01-02 23:59:03 +01:00
if (bounds_changing_) {
return;
2023-01-02 23:59:03 +01:00
}
gfx::Rect new_bounds = bounds;
// Keep the result inside the widget.
new_bounds.Intersect(window_view_->bounds());
2023-01-02 23:59:03 +01:00
if (new_bounds == bounds_) {
return;
2023-01-02 23:59:03 +01:00
}
bounds_changing_ = true;
bounds_ = new_bounds;
if (view_->size() != bounds_.size()) {
view_->SetSize(bounds_.size());
}
widget_->SetBounds(bounds_);
window_view_->OnOverlayBoundsChanged();
bounds_changing_ = false;
}
void CefOverlayViewHost::SetOverlayInsets(const CefInsets& insets) {
2023-01-02 23:59:03 +01:00
if (insets == insets_) {
return;
2023-01-02 23:59:03 +01:00
}
insets_ = insets;
MoveIfNecessary();
}
void CefOverlayViewHost::OnViewBoundsChanged(views::View* observed_view) {
MoveIfNecessary();
}
gfx::Rect CefOverlayViewHost::ComputeBounds() const {
// This method is only used with corner docking.
DCHECK_NE(docking_mode_, CEF_DOCKING_MODE_CUSTOM);
// Find the area we have to work with.
const auto& widget_bounds = window_view_->bounds();
// Ask the view how large an area it needs to draw on.
const auto& prefsize = view_->GetPreferredSize();
// Swap left/right docking with RTL.
const bool is_rtl = base::i18n::IsRTL();
// Dock to the correct corner, considering insets in the docking corner only.
int x = widget_bounds.x();
int y = widget_bounds.y();
if (((docking_mode_ == CEF_DOCKING_MODE_TOP_RIGHT ||
docking_mode_ == CEF_DOCKING_MODE_BOTTOM_RIGHT) &&
!is_rtl) ||
((docking_mode_ == CEF_DOCKING_MODE_TOP_LEFT ||
docking_mode_ == CEF_DOCKING_MODE_BOTTOM_LEFT) &&
is_rtl)) {
x += widget_bounds.width() - prefsize.width() - insets_.right;
} else {
x += insets_.left;
}
if (docking_mode_ == CEF_DOCKING_MODE_BOTTOM_LEFT ||
docking_mode_ == CEF_DOCKING_MODE_BOTTOM_RIGHT) {
y += widget_bounds.height() - prefsize.height() - insets_.bottom;
} else {
y += insets_.top;
}
return gfx::Rect(x, y, prefsize.width(), prefsize.height());
}
void CefOverlayViewHost::Cleanup() {
// This method may be called multiple times. For example, explicitly after the
// client calls CefOverlayController::Destroy or implicitly when the host
// Widget is being closed or destroyed. In most implicit cases
// CefWindowView::WindowClosing will call this before the host Widget is
// destroyed, allowing the client to optionally reuse the child View. However,
// if CefWindowView::WindowClosing is not called, DeleteDelegate will call
// this after the host Widget and all associated Widgets/Views have been
// destroyed. In the DeleteDelegate case |widget_| will return nullptr.
if (view_ && widget_) {
// Remove the child View immediately. It may be reused by the client.
auto view = view_util::GetFor(view_, /*find_known_parent=*/false);
widget_->GetContentsView()->RemoveChildView(view_);
if (view) {
view_util::ResumeOwnership(view);
}
view_->RemoveObserver(this);
view_ = nullptr;
}
if (cef_controller_) {
CefOverlayControllerImpl* controller_impl =
static_cast<CefOverlayControllerImpl*>(cef_controller_.get());
controller_impl->Destroyed();
cef_controller_ = nullptr;
}
if (window_view_) {
window_view_->RemoveOverlayView(this, host_view_);
window_view_ = nullptr;
host_view_ = nullptr;
}
}