blob: 184509be4f5d0d03663bbd3b3e610451bf4ee72f [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/components/native_app_window/native_app_window_views.h"
#include "base/functional/bind.h"
#include "base/observer_list_types.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/common/mojom/app_window.mojom.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/page/draggable_region.mojom.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/non_client_view.h"
#if defined(USE_AURA)
#include "ui/aura/window.h"
#endif
namespace native_app_window {
NativeAppWindowViews::NativeAppWindowViews() {
set_suppress_default_focus_handling();
SetLayoutManager(std::make_unique<views::FillLayout>());
}
void NativeAppWindowViews::Init(
extensions::AppWindow* app_window,
const extensions::AppWindow::CreateParams& create_params) {
app_window_ = app_window;
frameless_ = create_params.frame == extensions::AppWindow::FRAME_NONE;
resizable_ = create_params.resizable;
size_constraints_.set_minimum_size(
create_params.GetContentMinimumSize(gfx::Insets()));
size_constraints_.set_maximum_size(
create_params.GetContentMaximumSize(gfx::Insets()));
Observe(app_window_->web_contents());
// TODO(pbos): See if this can retain SetOwnedByWidget(true) and get deleted
// through WidgetDelegate::DeleteDelegate(). It's not clear to me how this
// ends up destructed, but the below preserves a previous DialogDelegate
// override that did not end with a direct `delete this;`.
SetOwnedByWidget(false);
RegisterDeleteDelegateCallback(base::BindOnce(
[](NativeAppWindowViews* dialog) {
dialog->widget_->RemoveObserver(dialog);
dialog->app_window_->OnNativeClose();
},
this));
web_view_ = AddChildView(std::make_unique<views::WebView>(nullptr));
web_view_->SetWebContents(app_window_->web_contents());
SetCanMinimize(!app_window_->show_on_lock_screen());
SetCanMaximize(GetCanMaximizeWindow());
// Intentionally the same as maximize.
SetCanFullscreen(GetCanMaximizeWindow());
SetCanResize(GetCanResizeWindow());
widget_ = new views::Widget;
widget_->AddObserver(this);
InitializeWindow(app_window, create_params);
OnViewWasResized();
}
NativeAppWindowViews::~NativeAppWindowViews() {
web_view_->SetWebContents(nullptr);
CHECK(!views::WidgetObserver::IsInObserverList());
}
void NativeAppWindowViews::OnCanHaveAlphaEnabledChanged() {
app_window_->OnNativeWindowChanged();
}
void NativeAppWindowViews::InitializeWindow(
extensions::AppWindow* app_window,
const extensions::AppWindow::CreateParams& create_params) {
// Stub implementation. See also ChromeNativeAppWindowViews.
views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_WINDOW);
init_params.delegate = this;
if (create_params.always_on_top)
init_params.z_order = ui::ZOrderLevel::kFloatingWindow;
widget_->Init(std::move(init_params));
widget_->CenterWindow(
create_params.GetInitialWindowBounds(gfx::Insets()).size());
}
// ui::BaseWindow implementation.
bool NativeAppWindowViews::IsActive() const {
return widget_->IsActive();
}
bool NativeAppWindowViews::IsMaximized() const {
return widget_->IsMaximized();
}
bool NativeAppWindowViews::IsMinimized() const {
return widget_->IsMinimized();
}
bool NativeAppWindowViews::IsFullscreen() const {
return widget_->IsFullscreen();
}
gfx::NativeWindow NativeAppWindowViews::GetNativeWindow() const {
return widget_->GetNativeWindow();
}
gfx::Rect NativeAppWindowViews::GetRestoredBounds() const {
return widget_->GetRestoredBounds();
}
ui::WindowShowState NativeAppWindowViews::GetRestoredState() const {
// Stub implementation. See also ChromeNativeAppWindowViews.
if (IsMaximized())
return ui::SHOW_STATE_MAXIMIZED;
if (IsFullscreen())
return ui::SHOW_STATE_FULLSCREEN;
return ui::SHOW_STATE_NORMAL;
}
gfx::Rect NativeAppWindowViews::GetBounds() const {
return widget_->GetWindowBoundsInScreen();
}
void NativeAppWindowViews::Show() {
if (widget_->IsVisible()) {
widget_->Activate();
return;
}
widget_->Show();
}
void NativeAppWindowViews::ShowInactive() {
if (widget_->IsVisible())
return;
widget_->ShowInactive();
}
void NativeAppWindowViews::Hide() {
widget_->Hide();
}
bool NativeAppWindowViews::IsVisible() const {
return widget_->IsVisible();
}
void NativeAppWindowViews::Close() {
widget_->Close();
}
void NativeAppWindowViews::Activate() {
widget_->Activate();
}
void NativeAppWindowViews::Deactivate() {
widget_->Deactivate();
}
void NativeAppWindowViews::Maximize() {
widget_->Maximize();
}
void NativeAppWindowViews::Minimize() {
widget_->Minimize();
}
void NativeAppWindowViews::Restore() {
widget_->Restore();
}
void NativeAppWindowViews::SetBounds(const gfx::Rect& bounds) {
widget_->SetBounds(bounds);
}
void NativeAppWindowViews::FlashFrame(bool flash) {
widget_->FlashFrame(flash);
}
ui::ZOrderLevel NativeAppWindowViews::GetZOrderLevel() const {
// Stub implementation. See also ChromeNativeAppWindowViews.
return widget_->GetZOrderLevel();
}
void NativeAppWindowViews::SetZOrderLevel(ui::ZOrderLevel order) {
widget_->SetZOrderLevel(order);
}
// WidgetDelegate implementation.
void NativeAppWindowViews::OnWidgetMove() {
app_window_->OnNativeWindowChanged();
}
views::View* NativeAppWindowViews::GetInitiallyFocusedView() {
return web_view_;
}
std::u16string NativeAppWindowViews::GetWindowTitle() const {
return app_window_->GetTitle();
}
bool NativeAppWindowViews::ShouldShowWindowTitle() const {
return false;
}
bool NativeAppWindowViews::ShouldSaveWindowPlacement() const {
return true;
}
void NativeAppWindowViews::SaveWindowPlacement(const gfx::Rect& bounds,
ui::WindowShowState show_state) {
views::WidgetDelegate::SaveWindowPlacement(bounds, show_state);
app_window_->OnNativeWindowChanged();
}
bool NativeAppWindowViews::ShouldDescendIntoChildForEventHandling(
gfx::NativeView child,
const gfx::Point& location) {
#if defined(USE_AURA)
if (child->Contains(web_view_->web_contents()->GetNativeView())) {
// App window should claim mouse events that fall within the draggable
// region.
return !draggable_region_.get() ||
!draggable_region_->contains(location.x(), location.y());
}
#endif
return true;
}
// WidgetObserver implementation.
void NativeAppWindowViews::OnWidgetDestroying(views::Widget* widget) {
for (auto& observer : observer_list_)
observer.OnHostDestroying();
}
void NativeAppWindowViews::OnWidgetVisibilityChanged(views::Widget* widget,
bool visible) {
app_window_->OnNativeWindowChanged();
}
void NativeAppWindowViews::OnWidgetActivationChanged(views::Widget* widget,
bool active) {
app_window_->OnNativeWindowChanged();
if (active)
app_window_->OnNativeWindowActivated();
}
// WebContentsObserver implementation.
void NativeAppWindowViews::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
if (render_frame_host->GetParentOrOuterDocument())
return;
if (app_window_->requested_alpha_enabled() && CanHaveAlphaEnabled()) {
render_frame_host->GetView()->SetBackgroundColor(SK_ColorTRANSPARENT);
} else if (app_window_->show_on_lock_screen()) {
// When shown on the lock screen, app windows will be shown on top of black
// background - to avoid a white flash while launching the app window,
// initialize it with black background color.
render_frame_host->GetView()->SetBackgroundColor(SK_ColorBLACK);
}
if (frameless_) {
mojo::Remote<extensions::mojom::AppWindow> app_window;
render_frame_host->GetRemoteInterfaces()->GetInterface(
app_window.BindNewPipeAndPassReceiver());
app_window->SetSupportsDraggableRegions(true);
}
}
// views::View implementation.
gfx::Size NativeAppWindowViews::GetMinimumSize() const {
return size_constraints_.GetMinimumSize();
}
gfx::Size NativeAppWindowViews::GetMaximumSize() const {
return size_constraints_.GetMaximumSize();
}
void NativeAppWindowViews::OnBoundsChanged(const gfx::Rect& previous_bounds) {
OnViewWasResized();
}
void NativeAppWindowViews::OnFocus() {
web_view_->RequestFocus();
}
// NativeAppWindow implementation.
void NativeAppWindowViews::SetFullscreen(int fullscreen_types) {
// Stub implementation. See also ChromeNativeAppWindowViews.
widget_->SetFullscreen(fullscreen_types !=
extensions::AppWindow::FULLSCREEN_TYPE_NONE);
}
bool NativeAppWindowViews::IsFullscreenOrPending() const {
// Stub implementation. See also ChromeNativeAppWindowViews.
return widget_->IsFullscreen();
}
void NativeAppWindowViews::UpdateWindowIcon() {
widget_->UpdateWindowIcon();
}
void NativeAppWindowViews::UpdateWindowTitle() {
widget_->UpdateWindowTitle();
}
void NativeAppWindowViews::DraggableRegionsChanged(
const std::vector<blink::mojom::DraggableRegionPtr>& regions) {
// Draggable region is not supported for non-frameless window.
if (!frameless_)
return;
draggable_region_.reset(
extensions::AppWindow::RawDraggableRegionsToSkRegion(regions));
OnViewWasResized();
}
SkRegion* NativeAppWindowViews::GetDraggableRegion() {
return draggable_region_.get();
}
void NativeAppWindowViews::UpdateShape(std::unique_ptr<ShapeRects> rects) {
// Stub implementation. See also ChromeNativeAppWindowViews.
}
bool NativeAppWindowViews::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
event, GetFocusManager());
}
bool NativeAppWindowViews::IsFrameless() const {
return frameless_;
}
bool NativeAppWindowViews::HasFrameColor() const {
return false;
}
SkColor NativeAppWindowViews::ActiveFrameColor() const {
return SK_ColorBLACK;
}
SkColor NativeAppWindowViews::InactiveFrameColor() const {
return SK_ColorBLACK;
}
gfx::Insets NativeAppWindowViews::GetFrameInsets() const {
if (frameless_)
return gfx::Insets();
// The pretend client_bounds passed in need to be large enough to ensure that
// GetWindowBoundsForClientBounds() doesn't decide that it needs more than
// the specified amount of space to fit the window controls in, and return a
// number larger than the real frame insets. Most window controls are smaller
// than 1000x1000px, so this should be big enough.
gfx::Rect client_bounds = gfx::Rect(1000, 1000);
gfx::Rect window_bounds =
widget_->non_client_view()->GetWindowBoundsForClientBounds(client_bounds);
return window_bounds.InsetsFrom(client_bounds);
}
gfx::Size NativeAppWindowViews::GetContentMinimumSize() const {
return size_constraints_.GetMinimumSize();
}
gfx::Size NativeAppWindowViews::GetContentMaximumSize() const {
return size_constraints_.GetMaximumSize();
}
void NativeAppWindowViews::SetContentSizeConstraints(
const gfx::Size& min_size,
const gfx::Size& max_size) {
size_constraints_.set_minimum_size(min_size);
size_constraints_.set_maximum_size(max_size);
SetCanMaximize(GetCanMaximizeWindow());
// Intentionally the same as maximize.
SetCanFullscreen(GetCanMaximizeWindow());
SetCanResize(GetCanResizeWindow());
widget_->OnSizeConstraintsChanged();
}
bool NativeAppWindowViews::CanHaveAlphaEnabled() const {
return views::Widget::IsWindowCompositingSupported();
}
void NativeAppWindowViews::SetVisibleOnAllWorkspaces(bool always_visible) {
widget_->SetVisibleOnAllWorkspaces(always_visible);
}
void NativeAppWindowViews::SetActivateOnPointer(bool activate_on_pointer) {}
gfx::NativeView NativeAppWindowViews::GetHostView() const {
return widget_->GetNativeView();
}
gfx::Point NativeAppWindowViews::GetDialogPosition(const gfx::Size& size) {
gfx::Size app_window_size = widget_->GetWindowBoundsInScreen().size();
return gfx::Point((app_window_size.width() - size.width()) / 2,
(app_window_size.height() - size.height()) / 2);
}
gfx::Size NativeAppWindowViews::GetMaximumDialogSize() {
return widget_->GetWindowBoundsInScreen().size();
}
void NativeAppWindowViews::AddObserver(
web_modal::ModalDialogHostObserver* observer) {
observer_list_.AddObserver(observer);
}
void NativeAppWindowViews::RemoveObserver(
web_modal::ModalDialogHostObserver* observer) {
observer_list_.RemoveObserver(observer);
}
void NativeAppWindowViews::OnWidgetHasHitTestMaskChanged() {
SetCanMaximize(GetCanMaximizeWindow());
// Intentionally the same as maximize.
SetCanFullscreen(GetCanMaximizeWindow());
SetCanResize(GetCanResizeWindow());
}
void NativeAppWindowViews::OnViewWasResized() {
for (auto& observer : observer_list_)
observer.OnPositionRequiresUpdate();
}
bool NativeAppWindowViews::GetCanResizeWindow() const {
return resizable_ && !size_constraints_.HasFixedSize() &&
!WidgetHasHitTestMask();
}
bool NativeAppWindowViews::GetCanMaximizeWindow() const {
return resizable_ && !size_constraints_.HasMaximumSize() &&
!WidgetHasHitTestMask();
}
BEGIN_METADATA(NativeAppWindowViews)
ADD_READONLY_PROPERTY_METADATA(bool, CanMaximizeWindow)
ADD_READONLY_PROPERTY_METADATA(bool, CanResizeWindow)
END_METADATA
} // namespace native_app_window