diff options
Diffstat (limited to 'libcef/browser/views/overlay_view_host.cc')
-rw-r--r-- | libcef/browser/views/overlay_view_host.cc | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/libcef/browser/views/overlay_view_host.cc b/libcef/browser/views/overlay_view_host.cc new file mode 100644 index 00000000..ec61fd65 --- /dev/null +++ b/libcef/browser/views/overlay_view_host.cc @@ -0,0 +1,336 @@ +// 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 "libcef/browser/views/overlay_view_host.h" + +#include "libcef/browser/views/view_util.h" +#include "libcef/browser/views/window_view.h" + +#include "base/i18n/rtl.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" + +#if defined(USE_AURA) +#include "ui/aura/window.h" +#include "ui/views/view_constants_aura.h" +#endif + +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 that && 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()) { + host_->Destroy(); + view_ = 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: + CefOverlayViewHost* const 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* widget_view, + CefRefPtr<CefView> view) { + 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_ = 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 = views::Widget::InitParams::Activatable::kNo; + widget_->Init(std::move(params)); + + view_ = widget_->GetContentsView()->AddChildView(std::move(controls_view)); + + // Make the Widget background transparent. The View might still be opaque. + if (widget_->GetCompositor()) { + widget_->GetCompositor()->SetBackgroundColor(SK_ColorTRANSPARENT); + } + +#if defined(USE_AURA) + // See matching logic in view_util::GetWindowFor. + widget_->GetNativeView()->SetProperty(views::kHostViewKey, widget_view); +#endif + + if (cef::IsChromeRuntimeEnabled()) { + // 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); + } + } + + // 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::Destroy() { + if (widget_ && !widget_->IsClosed()) { + // 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); + } + + 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. + if (bounds_changing_) { + return; + } + + gfx::Rect new_bounds = bounds; + + // Keep the result inside the widget. + new_bounds.Intersect(window_view_->bounds()); + + if (new_bounds == bounds_) { + return; + } + + bounds_changing_ = true; + + bounds_ = new_bounds; + if (view_->size() != bounds_.size()) { + view_->SetSize(bounds_.size()); + } + widget_->SetBounds(bounds_); + + bounds_changing_ = false; +} + +void CefOverlayViewHost::SetOverlayInsets(const CefInsets& insets) { + if (insets == insets_) { + return; + } + 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()); +} |