1 // Copyright 2016 The Chromium Embedded Framework Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be found
3 // in the LICENSE file.
4
5 #include "libcef/browser/views/window_view.h"
6
7 #include "libcef/browser/chrome/views/chrome_browser_frame.h"
8 #include "libcef/browser/image_impl.h"
9 #include "libcef/browser/views/window_impl.h"
10 #include "libcef/features/runtime.h"
11
12 #include "ui/base/hit_test.h"
13 #include "ui/views/widget/widget.h"
14 #include "ui/views/window/native_frame_view.h"
15
16 #if BUILDFLAG(IS_LINUX)
17 #include "ui/ozone/buildflags.h"
18 #if BUILDFLAG(OZONE_PLATFORM_X11)
19 #include "ui/base/x/x11_util.h"
20 #endif
21 #endif
22
23 #if BUILDFLAG(IS_WIN)
24 #include "ui/display/screen.h"
25 #include "ui/views/win/hwnd_util.h"
26 #endif
27
28 #if defined(USE_AURA)
29 #include "ui/aura/window.h"
30 #endif
31
32 namespace {
33
34 // Specialize ClientView to handle Widget-related events.
35 class ClientViewEx : public views::ClientView {
36 public:
ClientViewEx(views::Widget * widget,views::View * contents_view,CefWindowView::Delegate * window_delegate)37 ClientViewEx(views::Widget* widget,
38 views::View* contents_view,
39 CefWindowView::Delegate* window_delegate)
40 : views::ClientView(widget, contents_view),
41 window_delegate_(window_delegate) {
42 DCHECK(window_delegate_);
43 }
44
45 ClientViewEx(const ClientViewEx&) = delete;
46 ClientViewEx& operator=(const ClientViewEx&) = delete;
47
OnWindowCloseRequested()48 views::CloseRequestResult OnWindowCloseRequested() override {
49 return window_delegate_->CanWidgetClose()
50 ? views::CloseRequestResult::kCanClose
51 : views::CloseRequestResult::kCannotClose;
52 }
53
54 private:
55 CefWindowView::Delegate* window_delegate_; // Not owned by this object.
56 };
57
58 // Extend NativeFrameView with draggable region handling.
59 class NativeFrameViewEx : public views::NativeFrameView {
60 public:
NativeFrameViewEx(views::Widget * widget,CefWindowView * view)61 NativeFrameViewEx(views::Widget* widget, CefWindowView* view)
62 : views::NativeFrameView(widget), widget_(widget), view_(view) {}
63
64 NativeFrameViewEx(const NativeFrameViewEx&) = delete;
65 NativeFrameViewEx& operator=(const NativeFrameViewEx&) = delete;
66
GetWindowBoundsForClientBounds(const gfx::Rect & client_bounds) const67 gfx::Rect GetWindowBoundsForClientBounds(
68 const gfx::Rect& client_bounds) const override {
69 #if BUILDFLAG(IS_WIN)
70 // views::GetWindowBoundsForClientBounds() expects the input Rect to be in
71 // pixel coordinates. NativeFrameView does not implement this correctly so
72 // we need to provide our own implementation. See http://crbug.com/602692.
73 gfx::Rect pixel_bounds =
74 display::Screen::GetScreen()->DIPToScreenRectInWindow(
75 view_util::GetNativeWindow(widget_), client_bounds);
76 pixel_bounds = views::GetWindowBoundsForClientBounds(
77 static_cast<View*>(const_cast<NativeFrameViewEx*>(this)), pixel_bounds);
78 return display::Screen::GetScreen()->ScreenToDIPRectInWindow(
79 view_util::GetNativeWindow(widget_), pixel_bounds);
80 #else
81 // Use the default implementation.
82 return views::NativeFrameView::GetWindowBoundsForClientBounds(
83 client_bounds);
84 #endif
85 }
86
NonClientHitTest(const gfx::Point & point)87 int NonClientHitTest(const gfx::Point& point) override {
88 if (widget_->IsFullscreen())
89 return HTCLIENT;
90
91 // Test for mouse clicks that fall within the draggable region.
92 SkRegion* draggable_region = view_->draggable_region();
93 if (draggable_region && draggable_region->contains(point.x(), point.y()))
94 return HTCAPTION;
95
96 return views::NativeFrameView::NonClientHitTest(point);
97 }
98
99 private:
100 // Not owned by this object.
101 views::Widget* widget_;
102 CefWindowView* view_;
103 };
104
105 // The area inside the frame border that can be clicked and dragged for resizing
106 // the window. Only used in restored mode.
107 const int kResizeBorderThickness = 4;
108
109 // The distance from each window corner that triggers diagonal resizing. Only
110 // used in restored mode.
111 const int kResizeAreaCornerSize = 16;
112
113 // Implement NonClientFrameView without the system default caption and icon but
114 // with a resizable border. Based on AppWindowFrameView and CustomFrameView.
115 class CaptionlessFrameView : public views::NonClientFrameView {
116 public:
CaptionlessFrameView(views::Widget * widget,CefWindowView * view)117 CaptionlessFrameView(views::Widget* widget, CefWindowView* view)
118 : widget_(widget), view_(view) {}
119
120 CaptionlessFrameView(const CaptionlessFrameView&) = delete;
121 CaptionlessFrameView& operator=(const CaptionlessFrameView&) = delete;
122
GetBoundsForClientView() const123 gfx::Rect GetBoundsForClientView() const override {
124 return client_view_bounds_;
125 }
126
GetWindowBoundsForClientBounds(const gfx::Rect & client_bounds) const127 gfx::Rect GetWindowBoundsForClientBounds(
128 const gfx::Rect& client_bounds) const override {
129 return client_bounds;
130 }
131
NonClientHitTest(const gfx::Point & point)132 int NonClientHitTest(const gfx::Point& point) override {
133 if (widget_->IsFullscreen())
134 return HTCLIENT;
135
136 // Sanity check.
137 if (!bounds().Contains(point))
138 return HTNOWHERE;
139
140 // Check the frame first, as we allow a small area overlapping the contents
141 // to be used for resize handles.
142 bool can_ever_resize = widget_->widget_delegate()
143 ? widget_->widget_delegate()->CanResize()
144 : false;
145 // Don't allow overlapping resize handles when the window is maximized or
146 // fullscreen, as it can't be resized in those states.
147 int resize_border_thickness = ResizeBorderThickness();
148 int frame_component = GetHTComponentForFrame(
149 point, gfx::Insets(resize_border_thickness, resize_border_thickness),
150 kResizeAreaCornerSize, kResizeAreaCornerSize, can_ever_resize);
151 if (frame_component != HTNOWHERE)
152 return frame_component;
153
154 // Test for mouse clicks that fall within the draggable region.
155 SkRegion* draggable_region = view_->draggable_region();
156 if (draggable_region && draggable_region->contains(point.x(), point.y()))
157 return HTCAPTION;
158
159 int client_component = widget_->client_view()->NonClientHitTest(point);
160 if (client_component != HTNOWHERE)
161 return client_component;
162
163 // Caption is a safe default.
164 return HTCAPTION;
165 }
166
GetWindowMask(const gfx::Size & size,SkPath * window_mask)167 void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {
168 // Nothing to do here.
169 }
170
ResetWindowControls()171 void ResetWindowControls() override {
172 // Nothing to do here.
173 }
174
UpdateWindowIcon()175 void UpdateWindowIcon() override {
176 // Nothing to do here.
177 }
178
UpdateWindowTitle()179 void UpdateWindowTitle() override {
180 // Nothing to do here.
181 }
182
SizeConstraintsChanged()183 void SizeConstraintsChanged() override {
184 // Nothing to do here.
185 }
186
OnPaint(gfx::Canvas * canvas)187 void OnPaint(gfx::Canvas* canvas) override {
188 // Nothing to do here.
189 }
190
Layout()191 void Layout() override {
192 client_view_bounds_.SetRect(0, 0, width(), height());
193 views::NonClientFrameView::Layout();
194 }
195
CalculatePreferredSize() const196 gfx::Size CalculatePreferredSize() const override {
197 return widget_->non_client_view()
198 ->GetWindowBoundsForClientBounds(
199 gfx::Rect(widget_->client_view()->GetPreferredSize()))
200 .size();
201 }
202
GetMinimumSize() const203 gfx::Size GetMinimumSize() const override {
204 return widget_->non_client_view()
205 ->GetWindowBoundsForClientBounds(
206 gfx::Rect(widget_->client_view()->GetMinimumSize()))
207 .size();
208 }
209
GetMaximumSize() const210 gfx::Size GetMaximumSize() const override {
211 gfx::Size max_size = widget_->client_view()->GetMaximumSize();
212 gfx::Size converted_size =
213 widget_->non_client_view()
214 ->GetWindowBoundsForClientBounds(gfx::Rect(max_size))
215 .size();
216 return gfx::Size(max_size.width() == 0 ? 0 : converted_size.width(),
217 max_size.height() == 0 ? 0 : converted_size.height());
218 }
219
220 private:
ResizeBorderThickness() const221 int ResizeBorderThickness() const {
222 return (widget_->IsMaximized() || widget_->IsFullscreen()
223 ? 0
224 : kResizeBorderThickness);
225 }
226
227 // Not owned by this object.
228 views::Widget* widget_;
229 CefWindowView* view_;
230
231 // The bounds of the client view, in this view's coordinates.
232 gfx::Rect client_view_bounds_;
233 };
234
IsWindowBorderHit(int code)235 bool IsWindowBorderHit(int code) {
236 // On Windows HTLEFT = 10 and HTBORDER = 18. Values are not ordered the same
237 // in base/hit_test.h for non-Windows platforms.
238 #if BUILDFLAG(IS_WIN)
239 return code >= HTLEFT && code <= HTBORDER;
240 #else
241 return code == HTLEFT || code == HTRIGHT || code == HTTOP ||
242 code == HTTOPLEFT || code == HTTOPRIGHT || code == HTBOTTOM ||
243 code == HTBOTTOMLEFT || code == HTBOTTOMRIGHT || code == HTBORDER;
244 #endif
245 }
246
247 } // namespace
248
CefWindowView(CefWindowDelegate * cef_delegate,Delegate * window_delegate)249 CefWindowView::CefWindowView(CefWindowDelegate* cef_delegate,
250 Delegate* window_delegate)
251 : ParentClass(cef_delegate),
252 window_delegate_(window_delegate),
253 is_frameless_(false) {
254 DCHECK(window_delegate_);
255 }
256
CreateWidget()257 void CefWindowView::CreateWidget() {
258 DCHECK(!GetWidget());
259
260 // |widget| is owned by the NativeWidget and will be destroyed in response to
261 // a native destruction message.
262 views::Widget* widget = cef::IsChromeRuntimeEnabled() ? new ChromeBrowserFrame
263 : new views::Widget;
264
265 views::Widget::InitParams params;
266 params.delegate = this;
267 params.type = views::Widget::InitParams::TYPE_WINDOW;
268 bool can_activate = true;
269
270 // WidgetDelegate::DeleteDelegate() will delete |this| after executing the
271 // registered callback.
272 SetOwnedByWidget(true);
273 RegisterDeleteDelegateCallback(
274 base::BindOnce(&CefWindowView::DeleteDelegate, base::Unretained(this)));
275
276 if (cef_delegate()) {
277 CefRefPtr<CefWindow> cef_window = GetCefWindow();
278 is_frameless_ = cef_delegate()->IsFrameless(cef_window);
279
280 auto bounds = cef_delegate()->GetInitialBounds(cef_window);
281 params.bounds = gfx::Rect(bounds.x, bounds.y, bounds.width, bounds.height);
282
283 SetCanResize(cef_delegate()->CanResize(cef_window));
284
285 const auto show_state = cef_delegate()->GetInitialShowState(cef_window);
286 switch (show_state) {
287 case CEF_SHOW_STATE_NORMAL:
288 params.show_state = ui::SHOW_STATE_NORMAL;
289 break;
290 case CEF_SHOW_STATE_MINIMIZED:
291 params.show_state = ui::SHOW_STATE_MINIMIZED;
292 break;
293 case CEF_SHOW_STATE_MAXIMIZED:
294 params.show_state = ui::SHOW_STATE_MAXIMIZED;
295 break;
296 case CEF_SHOW_STATE_FULLSCREEN:
297 params.show_state = ui::SHOW_STATE_FULLSCREEN;
298 break;
299 }
300
301 bool is_menu = false;
302 bool can_activate_menu = true;
303 CefRefPtr<CefWindow> parent_window = cef_delegate()->GetParentWindow(
304 cef_window, &is_menu, &can_activate_menu);
305 if (parent_window && !parent_window->IsSame(cef_window)) {
306 CefWindowImpl* parent_window_impl =
307 static_cast<CefWindowImpl*>(parent_window.get());
308 params.parent = view_util::GetNativeView(parent_window_impl->widget());
309 if (is_menu) {
310 // Don't clip the window to parent bounds.
311 params.type = views::Widget::InitParams::TYPE_MENU;
312
313 // Don't set "always on top" for the window.
314 params.z_order = ui::ZOrderLevel::kNormal;
315
316 can_activate = can_activate_menu;
317 if (can_activate_menu)
318 params.activatable = views::Widget::InitParams::Activatable::kYes;
319 }
320 }
321 }
322
323 if (params.bounds.IsEmpty()) {
324 // The window will be placed on the default screen with origin (0,0).
325 params.bounds = gfx::Rect(CalculatePreferredSize());
326 }
327
328 #if BUILDFLAG(IS_WIN)
329 if (is_frameless_) {
330 // Don't show the native window caption. Setting this value on Linux will
331 // result in window resize artifacts.
332 params.remove_standard_frame = true;
333 }
334 #endif
335
336 widget->Init(std::move(params));
337 widget->AddObserver(this);
338
339 // |widget| should now be associated with |this|.
340 DCHECK_EQ(widget, GetWidget());
341 // |widget| must be top-level for focus handling to work correctly.
342 DCHECK(widget->is_top_level());
343
344 if (can_activate) {
345 // |widget| must be activatable for focus handling to work correctly.
346 DCHECK(widget->widget_delegate()->CanActivate());
347 }
348
349 #if BUILDFLAG(IS_LINUX)
350 #if BUILDFLAG(OZONE_PLATFORM_X11)
351 if (is_frameless_) {
352 auto window = view_util::GetWindowHandle(widget);
353 DCHECK(window);
354 ui::SetUseOSWindowFrame(static_cast<x11::Window>(window), false);
355 }
356 #endif
357 #endif
358 }
359
GetCefWindow() const360 CefRefPtr<CefWindow> CefWindowView::GetCefWindow() const {
361 CefRefPtr<CefWindow> window = GetCefPanel()->AsWindow();
362 DCHECK(window);
363 return window;
364 }
365
DeleteDelegate()366 void CefWindowView::DeleteDelegate() {
367 // Remove all child Views before deleting the Window so that notifications
368 // resolve correctly.
369 RemoveAllChildViews();
370
371 window_delegate_->OnWindowViewDeleted();
372 }
373
CanMinimize() const374 bool CefWindowView::CanMinimize() const {
375 if (!cef_delegate())
376 return true;
377 return cef_delegate()->CanMinimize(GetCefWindow());
378 }
379
CanMaximize() const380 bool CefWindowView::CanMaximize() const {
381 if (!cef_delegate())
382 return true;
383 return cef_delegate()->CanMaximize(GetCefWindow());
384 }
385
GetWindowTitle() const386 std::u16string CefWindowView::GetWindowTitle() const {
387 return title_;
388 }
389
GetWindowIcon()390 ui::ImageModel CefWindowView::GetWindowIcon() {
391 if (!window_icon_)
392 return ParentClass::GetWindowIcon();
393 auto image_skia =
394 static_cast<CefImageImpl*>(window_icon_.get())
395 ->GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
396 return ui::ImageModel::FromImageSkia(image_skia);
397 }
398
GetWindowAppIcon()399 ui::ImageModel CefWindowView::GetWindowAppIcon() {
400 if (!window_app_icon_)
401 return ParentClass::GetWindowAppIcon();
402 auto image_skia =
403 static_cast<CefImageImpl*>(window_app_icon_.get())
404 ->GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
405 return ui::ImageModel::FromImageSkia(image_skia);
406 }
407
WindowClosing()408 void CefWindowView::WindowClosing() {
409 window_delegate_->OnWindowClosing();
410 }
411
GetContentsView()412 views::View* CefWindowView::GetContentsView() {
413 // |this| will be the "Contents View" hosted by the Widget via ClientView and
414 // RootView.
415 return this;
416 }
417
CreateClientView(views::Widget * widget)418 views::ClientView* CefWindowView::CreateClientView(views::Widget* widget) {
419 return new ClientViewEx(widget, GetContentsView(), window_delegate_);
420 }
421
422 std::unique_ptr<views::NonClientFrameView>
CreateNonClientFrameView(views::Widget * widget)423 CefWindowView::CreateNonClientFrameView(views::Widget* widget) {
424 if (is_frameless_) {
425 // Custom frame type that doesn't render a caption.
426 return std::make_unique<CaptionlessFrameView>(widget, this);
427 } else if (widget->ShouldUseNativeFrame()) {
428 // DesktopNativeWidgetAura::CreateNonClientFrameView() returns
429 // NativeFrameView by default. Extend that type.
430 return std::make_unique<NativeFrameViewEx>(widget, this);
431 }
432
433 // Use Chromium provided CustomFrameView. In case if we would like to
434 // customize the frame, provide own implementation.
435 return nullptr;
436 }
437
ShouldDescendIntoChildForEventHandling(gfx::NativeView child,const gfx::Point & location)438 bool CefWindowView::ShouldDescendIntoChildForEventHandling(
439 gfx::NativeView child,
440 const gfx::Point& location) {
441 if (is_frameless_) {
442 // If the window is resizable it should claim mouse events that fall on the
443 // window border.
444 views::NonClientFrameView* ncfv = GetNonClientFrameView();
445 if (ncfv) {
446 int result = ncfv->NonClientHitTest(location);
447 if (IsWindowBorderHit(result))
448 return false;
449 }
450 }
451
452 // The window should claim mouse events that fall within the draggable region.
453 return !draggable_region_.get() ||
454 !draggable_region_->contains(location.x(), location.y());
455 }
456
MaybeGetMinimumSize(gfx::Size * size) const457 bool CefWindowView::MaybeGetMinimumSize(gfx::Size* size) const {
458 #if BUILDFLAG(IS_LINUX)
459 // Resize is disabled on Linux by returning the preferred size as the min/max
460 // size.
461 if (!CanResize()) {
462 *size = CalculatePreferredSize();
463 return true;
464 }
465 #endif
466 return false;
467 }
468
MaybeGetMaximumSize(gfx::Size * size) const469 bool CefWindowView::MaybeGetMaximumSize(gfx::Size* size) const {
470 #if BUILDFLAG(IS_LINUX)
471 // Resize is disabled on Linux by returning the preferred size as the min/max
472 // size.
473 if (!CanResize()) {
474 *size = CalculatePreferredSize();
475 return true;
476 }
477 #endif
478 return false;
479 }
480
ViewHierarchyChanged(const views::ViewHierarchyChangedDetails & details)481 void CefWindowView::ViewHierarchyChanged(
482 const views::ViewHierarchyChangedDetails& details) {
483 if (details.child == this) {
484 // This View's parent types (RootView, ClientView) are not exposed via the
485 // CEF API. Therefore don't send notifications about this View's parent
486 // changes.
487 return;
488 }
489
490 ParentClass::ViewHierarchyChanged(details);
491 }
492
OnWidgetBoundsChanged(views::Widget * widget,const gfx::Rect & new_bounds)493 void CefWindowView::OnWidgetBoundsChanged(views::Widget* widget,
494 const gfx::Rect& new_bounds) {
495 MoveOverlaysIfNecessary();
496 }
497
GetDisplay() const498 display::Display CefWindowView::GetDisplay() const {
499 const views::Widget* widget = GetWidget();
500 if (widget) {
501 return view_util::GetDisplayMatchingBounds(
502 widget->GetWindowBoundsInScreen(), false);
503 }
504 return display::Display();
505 }
506
SetTitle(const std::u16string & title)507 void CefWindowView::SetTitle(const std::u16string& title) {
508 title_ = title;
509 views::Widget* widget = GetWidget();
510 if (widget)
511 widget->UpdateWindowTitle();
512 }
513
SetWindowIcon(CefRefPtr<CefImage> window_icon)514 void CefWindowView::SetWindowIcon(CefRefPtr<CefImage> window_icon) {
515 if (std::max(window_icon->GetWidth(), window_icon->GetHeight()) != 16U) {
516 DLOG(ERROR) << "Window icons must be 16 DIP in size.";
517 return;
518 }
519
520 window_icon_ = window_icon;
521 views::Widget* widget = GetWidget();
522 if (widget)
523 widget->UpdateWindowIcon();
524 }
525
SetWindowAppIcon(CefRefPtr<CefImage> window_app_icon)526 void CefWindowView::SetWindowAppIcon(CefRefPtr<CefImage> window_app_icon) {
527 window_app_icon_ = window_app_icon;
528 views::Widget* widget = GetWidget();
529 if (widget)
530 widget->UpdateWindowIcon();
531 }
532
AddOverlayView(CefRefPtr<CefView> view,cef_docking_mode_t docking_mode)533 CefRefPtr<CefOverlayController> CefWindowView::AddOverlayView(
534 CefRefPtr<CefView> view,
535 cef_docking_mode_t docking_mode) {
536 DCHECK(view.get());
537 DCHECK(view->IsValid());
538 if (!view.get() || !view->IsValid())
539 return nullptr;
540
541 views::Widget* widget = GetWidget();
542 if (widget) {
543 // Owned by the View hierarchy. Acts as a z-order reference for the overlay.
544 auto overlay_host_view = AddChildView(std::make_unique<views::View>());
545
546 overlay_hosts_.push_back(
547 std::make_unique<CefOverlayViewHost>(this, docking_mode));
548
549 auto& overlay_host = overlay_hosts_.back();
550 overlay_host->Init(overlay_host_view, view);
551
552 return overlay_host->controller();
553 }
554
555 return nullptr;
556 }
557
MoveOverlaysIfNecessary()558 void CefWindowView::MoveOverlaysIfNecessary() {
559 if (overlay_hosts_.empty())
560 return;
561 for (auto& overlay_host : overlay_hosts_) {
562 overlay_host->MoveIfNecessary();
563 }
564 }
565
SetDraggableRegions(const std::vector<CefDraggableRegion> & regions)566 void CefWindowView::SetDraggableRegions(
567 const std::vector<CefDraggableRegion>& regions) {
568 if (regions.empty()) {
569 if (draggable_region_)
570 draggable_region_.reset(nullptr);
571 return;
572 }
573
574 draggable_region_.reset(new SkRegion);
575 for (const CefDraggableRegion& region : regions) {
576 draggable_region_->op(
577 {region.bounds.x, region.bounds.y,
578 region.bounds.x + region.bounds.width,
579 region.bounds.y + region.bounds.height},
580 region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
581 }
582 }
583
GetNonClientFrameView() const584 views::NonClientFrameView* CefWindowView::GetNonClientFrameView() const {
585 const views::Widget* widget = GetWidget();
586 if (!widget)
587 return nullptr;
588 if (!widget->non_client_view())
589 return nullptr;
590 return widget->non_client_view()->frame_view();
591 }
592