blob: de8cc3a57d48a85b755d10c66b56dc7b02200c38 [file] [log] [blame]
// 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.
#include "libcef/browser/views/window_view.h"
#include "libcef/browser/image_impl.h"
#include "libcef/browser/views/window_impl.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/window.h"
#include "ui/base/hit_test.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/native_frame_view.h"
#if defined(OS_LINUX) && defined(USE_X11)
#include <X11/Xlib.h>
#include "ui/gfx/x/x11_types.h"
#endif
#if defined(OS_WIN)
#include "ui/display/screen.h"
#include "ui/views/win/hwnd_util.h"
#endif
namespace {
// Specialize ClientView to handle Widget-related events.
class ClientViewEx : public views::ClientView {
public:
ClientViewEx(views::Widget* widget,
views::View* contents_view,
CefWindowView::Delegate* window_delegate)
: views::ClientView(widget, contents_view),
window_delegate_(window_delegate) {
DCHECK(window_delegate_);
}
bool CanClose() override { return window_delegate_->CanWidgetClose(); }
private:
CefWindowView::Delegate* window_delegate_; // Not owned by this object.
DISALLOW_COPY_AND_ASSIGN(ClientViewEx);
};
// Extend NativeFrameView with draggable region handling.
class NativeFrameViewEx : public views::NativeFrameView {
public:
NativeFrameViewEx(views::Widget* widget, CefWindowView* view)
: views::NativeFrameView(widget), widget_(widget), view_(view) {}
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override {
#if defined(OS_WIN)
// views::GetWindowBoundsForClientBounds() expects the input Rect to be in
// pixel coordinates. NativeFrameView does not implement this correctly so
// we need to provide our own implementation. See http://crbug.com/602692.
gfx::Rect pixel_bounds =
display::Screen::GetScreen()->DIPToScreenRectInWindow(
view_util::GetNativeWindow(widget_), client_bounds);
pixel_bounds = views::GetWindowBoundsForClientBounds(
static_cast<View*>(const_cast<NativeFrameViewEx*>(this)), pixel_bounds);
return display::Screen::GetScreen()->ScreenToDIPRectInWindow(
view_util::GetNativeWindow(widget_), pixel_bounds);
#else
// Use the default implementation.
return views::NativeFrameView::GetWindowBoundsForClientBounds(
client_bounds);
#endif
}
int NonClientHitTest(const gfx::Point& point) override {
if (widget_->IsFullscreen())
return HTCLIENT;
// Test for mouse clicks that fall within the draggable region.
SkRegion* draggable_region = view_->draggable_region();
if (draggable_region && draggable_region->contains(point.x(), point.y()))
return HTCAPTION;
return views::NativeFrameView::NonClientHitTest(point);
}
private:
// Not owned by this object.
views::Widget* widget_;
CefWindowView* view_;
DISALLOW_COPY_AND_ASSIGN(NativeFrameViewEx);
};
// The area inside the frame border that can be clicked and dragged for resizing
// the window. Only used in restored mode.
const int kResizeBorderThickness = 4;
// The distance from each window corner that triggers diagonal resizing. Only
// used in restored mode.
const int kResizeAreaCornerSize = 16;
// Implement NonClientFrameView without the system default caption and icon but
// with a resizable border. Based on AppWindowFrameView and CustomFrameView.
class CaptionlessFrameView : public views::NonClientFrameView {
public:
CaptionlessFrameView(views::Widget* widget, CefWindowView* view)
: widget_(widget), view_(view) {}
gfx::Rect GetBoundsForClientView() const override {
return client_view_bounds_;
}
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override {
return client_bounds;
}
int NonClientHitTest(const gfx::Point& point) override {
if (widget_->IsFullscreen())
return HTCLIENT;
// Sanity check.
if (!bounds().Contains(point))
return HTNOWHERE;
// Check the frame first, as we allow a small area overlapping the contents
// to be used for resize handles.
bool can_ever_resize = widget_->widget_delegate()
? widget_->widget_delegate()->CanResize()
: false;
// Don't allow overlapping resize handles when the window is maximized or
// fullscreen, as it can't be resized in those states.
int resize_border_thickness = ResizeBorderThickness();
int frame_component = GetHTComponentForFrame(
point, resize_border_thickness, resize_border_thickness,
kResizeAreaCornerSize, kResizeAreaCornerSize, can_ever_resize);
if (frame_component != HTNOWHERE)
return frame_component;
// Test for mouse clicks that fall within the draggable region.
SkRegion* draggable_region = view_->draggable_region();
if (draggable_region && draggable_region->contains(point.x(), point.y()))
return HTCAPTION;
int client_component = widget_->client_view()->NonClientHitTest(point);
if (client_component != HTNOWHERE)
return client_component;
// Caption is a safe default.
return HTCAPTION;
}
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {
// Nothing to do here.
}
void ResetWindowControls() override {
// Nothing to do here.
}
void UpdateWindowIcon() override {
// Nothing to do here.
}
void UpdateWindowTitle() override {
// Nothing to do here.
}
void SizeConstraintsChanged() override {
// Nothing to do here.
}
void OnPaint(gfx::Canvas* canvas) override {
// Nothing to do here.
}
void Layout() override {
client_view_bounds_.SetRect(0, 0, width(), height());
}
gfx::Size CalculatePreferredSize() const override {
return widget_->non_client_view()
->GetWindowBoundsForClientBounds(
gfx::Rect(widget_->client_view()->GetPreferredSize()))
.size();
}
gfx::Size GetMinimumSize() const override {
return widget_->non_client_view()
->GetWindowBoundsForClientBounds(
gfx::Rect(widget_->client_view()->GetMinimumSize()))
.size();
}
gfx::Size GetMaximumSize() const override {
gfx::Size max_size = widget_->client_view()->GetMaximumSize();
gfx::Size converted_size =
widget_->non_client_view()
->GetWindowBoundsForClientBounds(gfx::Rect(max_size))
.size();
return gfx::Size(max_size.width() == 0 ? 0 : converted_size.width(),
max_size.height() == 0 ? 0 : converted_size.height());
}
private:
int ResizeBorderThickness() const {
return (widget_->IsMaximized() || widget_->IsFullscreen()
? 0
: kResizeBorderThickness);
}
// Not owned by this object.
views::Widget* widget_;
CefWindowView* view_;
// The bounds of the client view, in this view's coordinates.
gfx::Rect client_view_bounds_;
DISALLOW_COPY_AND_ASSIGN(CaptionlessFrameView);
};
bool IsWindowBorderHit(int code) {
// On Windows HTLEFT = 10 and HTBORDER = 18. Values are not ordered the same
// in base/hit_test.h for non-Windows platforms.
#if defined(OS_WIN)
return code >= HTLEFT && code <= HTBORDER;
#else
return code == HTLEFT || code == HTRIGHT || code == HTTOP ||
code == HTTOPLEFT || code == HTTOPRIGHT || code == HTBOTTOM ||
code == HTBOTTOMLEFT || code == HTBOTTOMRIGHT || code == HTBORDER;
#endif
}
} // namespace
CefWindowView::CefWindowView(CefWindowDelegate* cef_delegate,
Delegate* window_delegate)
: ParentClass(cef_delegate),
window_delegate_(window_delegate),
is_frameless_(false) {
DCHECK(window_delegate_);
}
void CefWindowView::CreateWidget() {
DCHECK(!GetWidget());
// |widget| is owned by the NativeWidget and will be destroyed in response to
// a native destruction message.
views::Widget* widget = new views::Widget;
views::Widget::InitParams params;
params.delegate = this;
params.type = views::Widget::InitParams::TYPE_WINDOW;
params.bounds = gfx::Rect(CalculatePreferredSize());
bool can_activate = true;
if (cef_delegate()) {
CefRefPtr<CefWindow> cef_window = GetCefWindow();
is_frameless_ = cef_delegate()->IsFrameless(cef_window);
bool is_menu = false;
bool can_activate_menu = true;
CefRefPtr<CefWindow> parent_window = cef_delegate()->GetParentWindow(
cef_window, &is_menu, &can_activate_menu);
if (parent_window && !parent_window->IsSame(cef_window)) {
CefWindowImpl* parent_window_impl =
static_cast<CefWindowImpl*>(parent_window.get());
params.parent = view_util::GetNativeWindow(parent_window_impl->widget());
if (is_menu) {
// Don't clip the window to parent bounds.
params.type = views::Widget::InitParams::TYPE_MENU;
// Don't set "always on top" for the window.
params.z_order = ui::ZOrderLevel::kNormal;
can_activate = can_activate_menu;
if (can_activate_menu)
params.activatable = views::Widget::InitParams::ACTIVATABLE_YES;
}
}
}
#if defined(OS_WIN)
if (is_frameless_) {
// Don't show the native window caption. Setting this value on Linux will
// result in window resize artifacts.
params.remove_standard_frame = true;
}
#endif
widget->Init(std::move(params));
// |widget| should now be associated with |this|.
DCHECK_EQ(widget, GetWidget());
// |widget| must be top-level for focus handling to work correctly.
DCHECK(widget->is_top_level());
if (can_activate) {
// |widget| must be activatable for focus handling to work correctly.
DCHECK(widget->widget_delegate()->CanActivate());
}
#if defined(OS_LINUX) && defined(USE_X11)
if (is_frameless_) {
::Window window = view_util::GetWindowHandle(widget);
DCHECK(window);
::Display* display = gfx::GetXDisplay();
DCHECK(display);
// Make the window borderless. From
// http://stackoverflow.com/questions/1904445/borderless-windows-on-linux
struct MwmHints {
unsigned long flags;
unsigned long functions;
unsigned long decorations;
long input_mode;
unsigned long status;
};
enum {
MWM_HINTS_FUNCTIONS = (1L << 0),
MWM_HINTS_DECORATIONS = (1L << 1),
MWM_FUNC_ALL = (1L << 0),
MWM_FUNC_RESIZE = (1L << 1),
MWM_FUNC_MOVE = (1L << 2),
MWM_FUNC_MINIMIZE = (1L << 3),
MWM_FUNC_MAXIMIZE = (1L << 4),
MWM_FUNC_CLOSE = (1L << 5)
};
Atom mwmHintsProperty = XInternAtom(display, "_MOTIF_WM_HINTS", 0);
struct MwmHints hints = {};
hints.flags = MWM_HINTS_DECORATIONS;
hints.decorations = 0;
XChangeProperty(display, window, mwmHintsProperty, mwmHintsProperty, 32,
PropModeReplace, (unsigned char*)&hints, 5);
}
#endif // defined(OS_LINUX) && defined(USE_X11)
}
CefRefPtr<CefWindow> CefWindowView::GetCefWindow() const {
CefRefPtr<CefWindow> window = GetCefPanel()->AsWindow();
DCHECK(window);
return window;
}
void CefWindowView::DeleteDelegate() {
// Remove all child Views before deleting the Window so that notifications
// resolve correctly.
RemoveAllChildViews(true);
window_delegate_->OnWindowViewDeleted();
// Deletes |this|.
views::WidgetDelegateView::DeleteDelegate();
}
bool CefWindowView::CanResize() const {
if (!cef_delegate())
return true;
return cef_delegate()->CanResize(GetCefWindow());
}
bool CefWindowView::CanMinimize() const {
if (!cef_delegate())
return true;
return cef_delegate()->CanMinimize(GetCefWindow());
}
bool CefWindowView::CanMaximize() const {
if (!cef_delegate())
return true;
return cef_delegate()->CanMaximize(GetCefWindow());
}
base::string16 CefWindowView::GetWindowTitle() const {
return title_;
}
gfx::ImageSkia CefWindowView::GetWindowIcon() {
if (!window_icon_)
return ParentClass::GetWindowIcon();
return static_cast<CefImageImpl*>(window_icon_.get())
->GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
}
gfx::ImageSkia CefWindowView::GetWindowAppIcon() {
if (!window_app_icon_)
return ParentClass::GetWindowAppIcon();
return static_cast<CefImageImpl*>(window_app_icon_.get())
->GetForced1xScaleRepresentation(GetDisplay().device_scale_factor());
}
void CefWindowView::WindowClosing() {
window_delegate_->OnWindowClosing();
}
views::View* CefWindowView::GetContentsView() {
// |this| will be the "Contents View" hosted by the Widget via ClientView and
// RootView.
return this;
}
views::ClientView* CefWindowView::CreateClientView(views::Widget* widget) {
return new ClientViewEx(widget, GetContentsView(), window_delegate_);
}
views::NonClientFrameView* CefWindowView::CreateNonClientFrameView(
views::Widget* widget) {
if (is_frameless_) {
// Custom frame type that doesn't render a caption.
return new CaptionlessFrameView(widget, this);
} else if (widget->ShouldUseNativeFrame()) {
// DesktopNativeWidgetAura::CreateNonClientFrameView() returns
// NativeFrameView by default. Extend that type.
return new NativeFrameViewEx(widget, this);
}
// Use Chromium provided CustomFrameView. In case if we would like to
// customize the frame, provide own implementation.
return nullptr;
}
bool CefWindowView::ShouldDescendIntoChildForEventHandling(
gfx::NativeView child,
const gfx::Point& location) {
if (is_frameless_) {
// If the window is resizable it should claim mouse events that fall on the
// window border.
views::NonClientFrameView* ncfv = GetNonClientFrameView();
if (ncfv) {
int result = ncfv->NonClientHitTest(location);
if (IsWindowBorderHit(result))
return false;
}
}
// The window should claim mouse events that fall within the draggable region.
return !draggable_region_.get() ||
!draggable_region_->contains(location.x(), location.y());
}
bool CefWindowView::MaybeGetMinimumSize(gfx::Size* size) const {
#if defined(OS_LINUX)
// Resize is disabled on Linux by returning the preferred size as the min/max
// size.
if (!CanResize()) {
*size = CalculatePreferredSize();
return true;
}
#endif
return false;
}
bool CefWindowView::MaybeGetMaximumSize(gfx::Size* size) const {
#if defined(OS_LINUX)
// Resize is disabled on Linux by returning the preferred size as the min/max
// size.
if (!CanResize()) {
*size = CalculatePreferredSize();
return true;
}
#endif
return false;
}
void CefWindowView::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
if (details.child == this) {
// This View's parent types (RootView, ClientView) are not exposed via the
// CEF API. Therefore don't send notifications about this View's parent
// changes.
return;
}
ParentClass::ViewHierarchyChanged(details);
}
display::Display CefWindowView::GetDisplay() const {
const views::Widget* widget = GetWidget();
if (widget) {
return view_util::GetDisplayMatchingBounds(
widget->GetWindowBoundsInScreen(), false);
}
return display::Display();
}
void CefWindowView::SetTitle(const base::string16& title) {
title_ = title;
views::Widget* widget = GetWidget();
if (widget)
widget->UpdateWindowTitle();
}
void CefWindowView::SetWindowIcon(CefRefPtr<CefImage> window_icon) {
if (std::max(window_icon->GetWidth(), window_icon->GetHeight()) != 16U) {
DLOG(ERROR) << "Window icons must be 16 DIP in size.";
return;
}
window_icon_ = window_icon;
views::Widget* widget = GetWidget();
if (widget)
widget->UpdateWindowIcon();
}
void CefWindowView::SetWindowAppIcon(CefRefPtr<CefImage> window_app_icon) {
window_app_icon_ = window_app_icon;
views::Widget* widget = GetWidget();
if (widget)
widget->UpdateWindowIcon();
}
void CefWindowView::SetDraggableRegions(
const std::vector<CefDraggableRegion>& regions) {
if (regions.empty()) {
if (draggable_region_)
draggable_region_.reset(nullptr);
return;
}
draggable_region_.reset(new SkRegion);
for (const CefDraggableRegion& region : regions) {
draggable_region_->op(
{region.bounds.x, region.bounds.y,
region.bounds.x + region.bounds.width,
region.bounds.y + region.bounds.height},
region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
}
}
views::NonClientFrameView* CefWindowView::GetNonClientFrameView() const {
const views::Widget* widget = GetWidget();
if (!widget)
return nullptr;
if (!widget->non_client_view())
return nullptr;
return widget->non_client_view()->frame_view();
}