// 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. #ifndef CEF_LIBCEF_BROWSER_VIEWS_VIEW_VIEW_H_ #define CEF_LIBCEF_BROWSER_VIEWS_VIEW_VIEW_H_ #pragma once #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "cef/include/views/cef_view.h" #include "cef/include/views/cef_view_delegate.h" #include "cef/libcef/browser/thread_util.h" #include "cef/libcef/browser/views/view_util.h" #include "ui/views/accessibility/accessibility_paint_checks.h" #include "ui/views/background.h" #include "ui/views/view.h" // Helpers for template boiler-plate. #define CEF_VIEW_VIEW_T \ template #define CEF_VIEW_VIEW_A ViewsViewClass, CefViewDelegateClass #define CEF_VIEW_VIEW_D CefViewView // Base template for implementing views::View-derived classes. The views::View- // derived type passed to this template must provide a no-argument constructor // (for example, see LabelButtonEx from basic_label_button_view.h). See comments // in view_impl.h for a usage overview. CEF_VIEW_VIEW_T class CefViewView : public ViewsViewClass { public: using ParentClass = ViewsViewClass; // Should be created from CreateRootView() in a CefViewImpl-derived class. // Do not call complex views::View-derived methods from a CefViewView-derived // constructor as they may attempt to call back into CefViewImpl before // registration has been performed. |cef_delegate| may be nullptr. template explicit CefViewView(CefViewDelegateClass* cef_delegate, Args... args) : ParentClass(args...), cef_delegate_(cef_delegate) {} ~CefViewView() override { // Clear the reference to the delegate which may be released by the // CefViewImpl when it's destroyed via UserData. cef_delegate_ = nullptr; // Remove any UserData references to class members before they're destroyed. ParentClass::ClearAllUserData(); } // Should be called from InitializeRootView() in the CefViewImpl-derived // class that created this object. This method will be called after // CefViewImpl registration has completed so it is safe to call complex // views::View-derived methods here. virtual void Initialize() { // TODO(crbug.com/1218186): Remove this, if this view is focusable then it // needs to add a name so that the screen reader knows what to announce. ParentClass::SetProperty(views::kSkipAccessibilityPaintChecks, true); } // Returns the CefViewDelegate-derived delegate associated with this view. // May return nullptr. CefViewDelegateClass* cef_delegate() const { return cef_delegate_; } // Returns the CefView associated with this view. May return nullptr during // CefViewImpl initialization. If callbacks to the CefViewImpl-derived class // are required define an interface that the CefViewImpl-derived class can // implement and pass as an unowned instance to this object's constructor (see // for example CefWindowView). CefRefPtr GetCefView() const { CefRefPtr view = view_util::GetFor(this, false); DCHECK(view); return view; } // views::View methods: gfx::Size CalculatePreferredSize( const views::SizeBounds& available_size) const override; gfx::Size GetMinimumSize() const override; gfx::Size GetMaximumSize() const override; int GetHeightForWidth(int w) const override; void Layout(views::View::PassKey) override; void ViewHierarchyChanged( const views::ViewHierarchyChangedDetails& details) override; void AddedToWidget() override; void RemovedFromWidget() override; void OnFocus() override; void OnBlur() override; void OnThemeChanged() override; // Return true if this View is expected to have a minimum size (for example, // a button where the minimum size is based on the label). virtual bool HasMinimumSize() const { return false; } private: void NotifyChildViewChanged( const views::ViewHierarchyChangedDetails& details); void NotifyParentViewChanged( const views::ViewHierarchyChangedDetails& details); // Not owned by this object. raw_ptr cef_delegate_; }; CEF_VIEW_VIEW_T gfx::Size CEF_VIEW_VIEW_D::CalculatePreferredSize( const views::SizeBounds& available_size) const { gfx::Size result; if (cef_delegate()) { CefSize cef_size = cef_delegate()->GetPreferredSize(GetCefView()); if (!cef_size.IsEmpty()) { result = gfx::Size(cef_size.width, cef_size.height); } } if (result.IsEmpty()) { result = ParentClass::CalculatePreferredSize(available_size); } if (result.IsEmpty()) { // Some layouts like BoxLayout expect the preferred size to be non-empty. // The user may have set the size explicitly. Therefore return the current // size as the preferred size. result = ParentClass::size(); } return result; } CEF_VIEW_VIEW_T gfx::Size CEF_VIEW_VIEW_D::GetMinimumSize() const { gfx::Size result; if (cef_delegate()) { CefSize cef_size = cef_delegate()->GetMinimumSize(GetCefView()); if (!cef_size.IsEmpty()) { result = gfx::Size(cef_size.width, cef_size.height); } } // We don't want to call ParentClass::GetMinimumSize() in all cases because // the default views::View implementation will call GetPreferredSize(). That // may result in size() being returned which keeps the View from shrinking. if (result.IsEmpty() && HasMinimumSize()) { result = ParentClass::GetMinimumSize(); } return result; } CEF_VIEW_VIEW_T gfx::Size CEF_VIEW_VIEW_D::GetMaximumSize() const { gfx::Size result; if (cef_delegate()) { CefSize cef_size = cef_delegate()->GetMaximumSize(GetCefView()); if (!cef_size.IsEmpty()) { result = gfx::Size(cef_size.width, cef_size.height); } } if (result.IsEmpty()) { result = ParentClass::GetMaximumSize(); } return result; } CEF_VIEW_VIEW_T int CEF_VIEW_VIEW_D::GetHeightForWidth(int w) const { int result = 0; if (cef_delegate()) { result = cef_delegate()->GetHeightForWidth(GetCefView(), w); } if (result == 0) { result = ParentClass::GetHeightForWidth(w); } if (result == 0) { // Some layouts like FillLayout will ignore the preferred size if this view // has no children. We want to use the preferred size if not otherwise // specified. result = ParentClass::GetPreferredSize().height(); } return result; } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::Layout(views::View::PassKey) { ParentClass::template LayoutSuperclass(this); // If Layout() did not provide a size then use the preferred size. if (ParentClass::size().IsEmpty()) { ParentClass::SizeToPreferredSize(); } if (cef_delegate()) { const auto new_bounds = ParentClass::bounds(); CefRect new_rect(new_bounds.x(), new_bounds.y(), new_bounds.width(), new_bounds.height()); cef_delegate()->OnLayoutChanged(GetCefView(), new_rect); } } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::ViewHierarchyChanged( const views::ViewHierarchyChangedDetails& details) { NotifyChildViewChanged(details); NotifyParentViewChanged(details); ParentClass::ViewHierarchyChanged(details); } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::AddedToWidget() { ParentClass::AddedToWidget(); if (cef_delegate()) { cef_delegate()->OnWindowChanged(GetCefView(), /*added=*/true); } } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::RemovedFromWidget() { if (cef_delegate()) { cef_delegate()->OnWindowChanged(GetCefView(), /*added=*/false); } ParentClass::RemovedFromWidget(); } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::OnFocus() { if (cef_delegate()) { cef_delegate()->OnFocus(GetCefView()); } ParentClass::OnFocus(); } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::OnBlur() { if (cef_delegate()) { cef_delegate()->OnBlur(GetCefView()); } ParentClass::OnBlur(); } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::OnThemeChanged() { // Clear the background, if set. if (ParentClass::background()) { ParentClass::SetBackground(nullptr); } // Apply default theme colors. ParentClass::OnThemeChanged(); // Allow the client to override the default colors. if (cef_delegate()) { cef_delegate()->OnThemeChanged(GetCefView()); } // If the background is still unset then possibly set it to the desired value. if (!ParentClass::background()) { // May return an empty value. const auto& color = view_util::GetBackgroundColor(this, /*allow_transparent=*/true); if (color) { ParentClass::SetBackground(views::CreateSolidBackground(*color)); } } } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::NotifyChildViewChanged( const views::ViewHierarchyChangedDetails& details) { if (!cef_delegate()) { return; } // Only interested with the parent is |this| object and the notification is // about an immediate child (notifications are also sent for grandchildren). if (details.parent != this || details.child->parent() != this) { return; } // Only notify for children that have a known CEF root view. For example, // don't notify when ScrollView adds child scroll bars. CefRefPtr child = view_util::GetFor(details.child, false); if (child) { cef_delegate()->OnChildViewChanged(GetCefView(), details.is_add, child); } } CEF_VIEW_VIEW_T void CEF_VIEW_VIEW_D::NotifyParentViewChanged( const views::ViewHierarchyChangedDetails& details) { if (!cef_delegate()) { return; } // Only interested when the child is |this| object and notification is about // the immediate parent (notifications are sent for all parents). if (details.child != this || details.parent != ParentClass::parent()) { return; } // The immediate parent might be an intermediate view so find the closest // known CEF root view. |parent| might be nullptr for overlays. CefRefPtr parent = view_util::GetFor(details.parent, true); if (parent) { cef_delegate()->OnParentViewChanged(GetCefView(), details.is_add, parent); } } #endif // CEF_LIBCEF_BROWSER_VIEWS_VIEW_VIEW_H_