• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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