1 // Copyright 2021 The Chromium Embedded Framework Authors. Portions copyright
2 // 2011 The Chromium Authors. All rights reserved. Use of this source code is
3 // governed by a BSD-style license that can be found in the LICENSE file.
4
5 #include "libcef/browser/views/overlay_view_host.h"
6
7 #include "libcef/browser/views/view_util.h"
8 #include "libcef/browser/views/window_view.h"
9
10 #include "base/i18n/rtl.h"
11 #include "chrome/browser/ui/views/frame/browser_view.h"
12 #include "chrome/browser/ui/views/theme_copying_widget.h"
13 #include "third_party/skia/include/core/SkColor.h"
14 #include "ui/compositor/compositor.h"
15 #include "ui/compositor/layer.h"
16
17 #if defined(USE_AURA)
18 #include "ui/aura/window.h"
19 #include "ui/views/view_constants_aura.h"
20 #endif
21
22 namespace {
23
24 class CefOverlayControllerImpl : public CefOverlayController {
25 public:
CefOverlayControllerImpl(CefOverlayViewHost * host,CefRefPtr<CefView> view)26 CefOverlayControllerImpl(CefOverlayViewHost* host, CefRefPtr<CefView> view)
27 : host_(host), view_(view) {}
28
29 CefOverlayControllerImpl(const CefOverlayControllerImpl&) = delete;
30 CefOverlayControllerImpl& operator=(const CefOverlayControllerImpl&) = delete;
31
IsValid()32 bool IsValid() override {
33 // View validity implies that CefOverlayViewHost is still valid, because the
34 // Widget that it owns (and that owns the View) is still valid.
35 return view_ && view_->IsValid();
36 }
37
IsSame(CefRefPtr<CefOverlayController> that)38 bool IsSame(CefRefPtr<CefOverlayController> that) override {
39 return that && that->GetContentsView()->IsSame(view_);
40 }
41
GetContentsView()42 CefRefPtr<CefView> GetContentsView() override { return view_; }
43
GetWindow()44 CefRefPtr<CefWindow> GetWindow() override {
45 if (IsValid()) {
46 return view_util::GetWindowFor(host_->window_view()->GetWidget());
47 }
48 return nullptr;
49 }
50
GetDockingMode()51 cef_docking_mode_t GetDockingMode() override {
52 if (IsValid()) {
53 return host_->docking_mode();
54 }
55 return CEF_DOCKING_MODE_TOP_LEFT;
56 }
57
Destroy()58 void Destroy() override {
59 if (IsValid()) {
60 host_->Destroy();
61 view_ = nullptr;
62 }
63 }
64
SetBounds(const CefRect & bounds)65 void SetBounds(const CefRect& bounds) override {
66 if (IsValid() && host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
67 host_->SetOverlayBounds(
68 gfx::Rect(bounds.x, bounds.y, bounds.width, bounds.height));
69 }
70 }
71
GetBounds()72 CefRect GetBounds() override {
73 if (IsValid()) {
74 const auto& bounds = host_->bounds();
75 return CefRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
76 }
77 return CefRect();
78 }
79
GetBoundsInScreen()80 CefRect GetBoundsInScreen() override {
81 if (IsValid()) {
82 const auto& bounds = host_->widget()->GetWindowBoundsInScreen();
83 return CefRect(bounds.x(), bounds.y(), bounds.width(), bounds.height());
84 }
85 return CefRect();
86 }
87
SetSize(const CefSize & size)88 void SetSize(const CefSize& size) override {
89 if (IsValid() && host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
90 // Update the size without changing the origin.
91 const auto& origin = host_->bounds().origin();
92 host_->SetOverlayBounds(
93 gfx::Rect(origin, gfx::Size(size.width, size.height)));
94 }
95 }
96
GetSize()97 CefSize GetSize() override {
98 const auto& bounds = GetBounds();
99 return CefSize(bounds.width, bounds.height);
100 }
101
SetPosition(const CefPoint & position)102 void SetPosition(const CefPoint& position) override {
103 if (IsValid() && host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
104 // Update the origin without changing the size.
105 const auto& size = host_->bounds().size();
106 host_->SetOverlayBounds(
107 gfx::Rect(gfx::Point(position.x, position.y), size));
108 }
109 }
110
GetPosition()111 CefPoint GetPosition() override {
112 const auto& bounds = GetBounds();
113 return CefPoint(bounds.x, bounds.y);
114 }
115
SetInsets(const CefInsets & insets)116 void SetInsets(const CefInsets& insets) override {
117 if (IsValid() && host_->docking_mode() != CEF_DOCKING_MODE_CUSTOM) {
118 host_->SetOverlayInsets(insets);
119 }
120 }
121
GetInsets()122 CefInsets GetInsets() override {
123 if (IsValid()) {
124 return host_->insets();
125 }
126 return CefInsets();
127 }
128
SizeToPreferredSize()129 void SizeToPreferredSize() override {
130 if (IsValid()) {
131 if (host_->docking_mode() == CEF_DOCKING_MODE_CUSTOM) {
132 // Update the size without changing the origin.
133 const auto& origin = host_->bounds().origin();
134 const auto& preferred_size = host_->view()->GetPreferredSize();
135 host_->SetOverlayBounds(gfx::Rect(origin, preferred_size));
136 } else {
137 host_->MoveIfNecessary();
138 }
139 }
140 }
141
SetVisible(bool visible)142 void SetVisible(bool visible) override {
143 if (IsValid()) {
144 if (visible) {
145 host_->MoveIfNecessary();
146 host_->widget()->Show();
147 } else {
148 host_->widget()->Hide();
149 }
150 }
151 }
152
IsVisible()153 bool IsVisible() override {
154 if (IsValid()) {
155 return host_->widget()->IsVisible();
156 }
157 return false;
158 }
159
IsDrawn()160 bool IsDrawn() override { return IsVisible(); }
161
162 private:
163 CefOverlayViewHost* const host_;
164 CefRefPtr<CefView> view_;
165
166 IMPLEMENT_REFCOUNTING(CefOverlayControllerImpl);
167 };
168
169 } // namespace
170
CefOverlayViewHost(CefWindowView * window_view,cef_docking_mode_t docking_mode)171 CefOverlayViewHost::CefOverlayViewHost(CefWindowView* window_view,
172 cef_docking_mode_t docking_mode)
173 : window_view_(window_view), docking_mode_(docking_mode) {}
174
Init(views::View * widget_view,CefRefPtr<CefView> view)175 void CefOverlayViewHost::Init(views::View* widget_view,
176 CefRefPtr<CefView> view) {
177 DCHECK(view);
178
179 // Match the logic in CEF_PANEL_IMPL_D::AddChildView().
180 auto controls_view = view->IsAttached()
181 ? base::WrapUnique(view_util::GetFor(view))
182 : view_util::PassOwnership(view);
183 DCHECK(controls_view.get());
184
185 cef_controller_ = new CefOverlayControllerImpl(this, view);
186
187 // Initialize the Widget.
188 widget_ = std::make_unique<ThemeCopyingWidget>(window_view_->GetWidget());
189 views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
190 params.delegate = this;
191 params.name = "CefOverlayViewHost";
192 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
193 params.parent = window_view_->GetWidget()->GetNativeView();
194 params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
195 params.activatable = views::Widget::InitParams::Activatable::kNo;
196 widget_->Init(std::move(params));
197
198 view_ = widget_->GetContentsView()->AddChildView(std::move(controls_view));
199
200 // Make the Widget background transparent. The View might still be opaque.
201 if (widget_->GetCompositor()) {
202 widget_->GetCompositor()->SetBackgroundColor(SK_ColorTRANSPARENT);
203 }
204
205 #if defined(USE_AURA)
206 // See matching logic in view_util::GetWindowFor.
207 widget_->GetNativeView()->SetProperty(views::kHostViewKey, widget_view);
208 #endif
209
210 if (cef::IsChromeRuntimeEnabled()) {
211 // Some attributes associated with a Chrome toolbar are located via the
212 // Widget. See matching logic in BrowserView::AddedToWidget.
213 auto browser_view = BrowserView::GetBrowserViewForNativeWindow(
214 view_util::GetNativeWindow(window_view_->GetWidget()));
215 if (browser_view) {
216 widget_->SetNativeWindowProperty(BrowserView::kBrowserViewKey,
217 browser_view);
218 }
219 }
220
221 // Set the initial bounds after the View has been added to the Widget.
222 // Otherwise, preferred size won't calculate correctly.
223 gfx::Rect bounds;
224 if (docking_mode_ == CEF_DOCKING_MODE_CUSTOM) {
225 if (view_->size().IsEmpty()) {
226 // Size to the preferred size to start.
227 view_->SizeToPreferredSize();
228 }
229
230 // Top-left origin with existing size.
231 bounds = gfx::Rect(gfx::Point(), view_->size());
232 } else {
233 bounds = ComputeBounds();
234 }
235 SetOverlayBounds(bounds);
236
237 // Register for future bounds change notifications.
238 view_->AddObserver(this);
239
240 // Initially hidden.
241 widget_->Hide();
242 }
243
Destroy()244 void CefOverlayViewHost::Destroy() {
245 if (widget_ && !widget_->IsClosed()) {
246 // Remove the child View immediately. It may be reused by the client.
247 auto view = view_util::GetFor(view_, /*find_known_parent=*/false);
248 widget_->GetContentsView()->RemoveChildView(view_);
249 if (view) {
250 view_util::ResumeOwnership(view);
251 }
252
253 widget_->Close();
254 }
255 }
256
MoveIfNecessary()257 void CefOverlayViewHost::MoveIfNecessary() {
258 if (bounds_changing_ || docking_mode_ == CEF_DOCKING_MODE_CUSTOM) {
259 return;
260 }
261 SetOverlayBounds(ComputeBounds());
262 }
263
SetOverlayBounds(const gfx::Rect & bounds)264 void CefOverlayViewHost::SetOverlayBounds(const gfx::Rect& bounds) {
265 // Avoid re-entrancy of this method.
266 if (bounds_changing_)
267 return;
268
269 gfx::Rect new_bounds = bounds;
270
271 // Keep the result inside the widget.
272 new_bounds.Intersect(window_view_->bounds());
273
274 if (new_bounds == bounds_)
275 return;
276
277 bounds_changing_ = true;
278
279 bounds_ = new_bounds;
280 if (view_->size() != bounds_.size()) {
281 view_->SetSize(bounds_.size());
282 }
283 widget_->SetBounds(bounds_);
284
285 bounds_changing_ = false;
286 }
287
SetOverlayInsets(const CefInsets & insets)288 void CefOverlayViewHost::SetOverlayInsets(const CefInsets& insets) {
289 if (insets == insets_)
290 return;
291 insets_ = insets;
292 MoveIfNecessary();
293 }
294
OnViewBoundsChanged(views::View * observed_view)295 void CefOverlayViewHost::OnViewBoundsChanged(views::View* observed_view) {
296 MoveIfNecessary();
297 }
298
ComputeBounds() const299 gfx::Rect CefOverlayViewHost::ComputeBounds() const {
300 // This method is only used with corner docking.
301 DCHECK_NE(docking_mode_, CEF_DOCKING_MODE_CUSTOM);
302
303 // Find the area we have to work with.
304 const auto& widget_bounds = window_view_->bounds();
305
306 // Ask the view how large an area it needs to draw on.
307 const auto& prefsize = view_->GetPreferredSize();
308
309 // Swap left/right docking with RTL.
310 const bool is_rtl = base::i18n::IsRTL();
311
312 // Dock to the correct corner, considering insets in the docking corner only.
313 int x = widget_bounds.x();
314 int y = widget_bounds.y();
315 if (((docking_mode_ == CEF_DOCKING_MODE_TOP_RIGHT ||
316 docking_mode_ == CEF_DOCKING_MODE_BOTTOM_RIGHT) &&
317 !is_rtl) ||
318 ((docking_mode_ == CEF_DOCKING_MODE_TOP_LEFT ||
319 docking_mode_ == CEF_DOCKING_MODE_BOTTOM_LEFT) &&
320 is_rtl)) {
321 x += widget_bounds.width() - prefsize.width() - insets_.right;
322 } else {
323 x += insets_.left;
324 }
325 if (docking_mode_ == CEF_DOCKING_MODE_BOTTOM_LEFT ||
326 docking_mode_ == CEF_DOCKING_MODE_BOTTOM_RIGHT) {
327 y += widget_bounds.height() - prefsize.height() - insets_.bottom;
328 } else {
329 y += insets_.top;
330 }
331
332 return gfx::Rect(x, y, prefsize.width(), prefsize.height());
333 }
334