// 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 "ui/views/widget/native_widget_mac.h"
#include <ApplicationServices/ApplicationServices.h>
#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/functional/callback.h"
#include "base/lazy_instance.h"
#include "base/no_destructor.h"
#include "base/strings/sys_string_conversions.h"
#include "components/crash/core/common/crash_key.h"
#import "components/remote_cocoa/app_shim/bridged_content_view.h"
#import "components/remote_cocoa/app_shim/native_widget_mac_nswindow.h"
#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
#import "components/remote_cocoa/app_shim/views_nswindow_delegate.h"
#import "ui/base/cocoa/window_size_constants.h"
#include "ui/base/ime/init/input_method_factory.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/compositor/compositor.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/gestures/gesture_recognizer.h"
#include "ui/events/gestures/gesture_recognizer_impl_mac.h"
#include "ui/events/gestures/gesture_types.h"
#include "ui/gfx/font_list.h"
#import "ui/gfx/mac/coordinate_conversion.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_mac.h"
#import "ui/views/cocoa/drag_drop_client_mac.h"
#import "ui/views/cocoa/native_widget_mac_ns_window_host.h"
#include "ui/views/cocoa/text_input_host.h"
#include "ui/views/views_features.h"
#include "ui/views/widget/drop_helper.h"
#include "ui/views/widget/native_widget_delegate.h"
#include "ui/views/widget/widget_aura_utils.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/window/native_frame_view_mac.h"
using remote_cocoa::mojom::WindowVisibilityState;
namespace views {
namespace {
base::LazyInstance<base::RepeatingCallbackList<void(NativeWidgetMac*)>>::
DestructorAtExit g_init_native_widget_callbacks = LAZY_INSTANCE_INITIALIZER;
uint64_t StyleMaskForParams(const Widget::InitParams& params) {
// If the Widget is modal, it will be displayed as a sheet. This works best if
// it has NSWindowStyleMaskTitled. For example, with
// NSWindowStyleMaskBorderless, the parent window still accepts input.
// NSWindowStyleMaskFullSizeContentView ensures that calculating the modal's
// content rect doesn't account for a nonexistent title bar.
if (params.delegate &&
params.delegate->GetModalType() == ui::mojom::ModalType::kWindow) {
return NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
}
// TODO(tapted): Determine better masks when there are use cases for it.
if (params.remove_standard_frame)
return NSWindowStyleMaskBorderless;
if (params.type == Widget::InitParams::TYPE_WINDOW) {
return NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
}
return NSWindowStyleMaskBorderless;
}
CGWindowLevel CGWindowLevelForZOrderLevel(ui::ZOrderLevel level,
Widget::InitParams::Type type) {
switch (level) {
case ui::ZOrderLevel::kNormal:
return kCGNormalWindowLevel;
case ui::ZOrderLevel::kFloatingWindow:
if (type == Widget::InitParams::TYPE_MENU)
return kCGPopUpMenuWindowLevel;
else
return kCGFloatingWindowLevel;
case ui::ZOrderLevel::kFloatingUIElement:
if (type == Widget::InitParams::TYPE_DRAG)
return kCGDraggingWindowLevel;
else
return kCGStatusWindowLevel;
case ui::ZOrderLevel::kSecuritySurface:
return kCGScreenSaverWindowLevel - 1;
}
}
} // namespace
// Implements zoom following focus for macOS accessibility zoom.
class NativeWidgetMac::ZoomFocusMonitor : public FocusChangeListener {
public:
ZoomFocusMonitor() = default;
~ZoomFocusMonitor() override = default;
void OnWillChangeFocus(View* focused_before, View* focused_now) override {}
void OnDidChangeFocus(View* focused_before, View* focused_now) override {
if (!focused_now || !UAZoomEnabled())
return;
// Web content handles its own zooming.
if (strcmp("WebView", focused_now->GetClassName()) == 0)
return;
NSRect rect = NSRectFromCGRect(focused_now->GetBoundsInScreen().ToCGRect());
UAZoomChangeFocus(&rect, nullptr, kUAZoomFocusTypeOther);
}
};
////////////////////////////////////////////////////////////////////////////////
// NativeWidgetMac:
NativeWidgetMac::NativeWidgetMac(internal::NativeWidgetDelegate* delegate)
// TODO(crbug.com/346814969): Investigate where a null `delegate` should
// be allowed.
: delegate_(delegate ? delegate->AsWidget()->GetWeakPtr() : nullptr),
ns_window_host_(new NativeWidgetMacNSWindowHost(this)) {}
NativeWidgetMac::~NativeWidgetMac() {
if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET)
owned_delegate_.reset();
else
CloseNow();
}
void NativeWidgetMac::WindowDestroying() {
OnWindowDestroying(GetNativeWindow());
if (delegate_) {
delegate_->OnNativeWidgetDestroying();
}
}
void NativeWidgetMac::WindowDestroyed() {
DCHECK(GetNSWindowMojo());
SetFocusManager(nullptr);
ns_window_host_.reset();
// |OnNativeWidgetDestroyed| may delete |this| if the object does not own
// itself.
bool should_delete_this =
(ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) ||
(ownership_ == Widget::InitParams::CLIENT_OWNS_WIDGET);
if (delegate_) {
delegate_->OnNativeWidgetDestroyed();
}
if (should_delete_this)
delete this;
}
void NativeWidgetMac::OnWindowKeyStatusChanged(
bool is_key,
bool is_content_first_responder) {
Widget* widget = GetWidget();
if (!widget || !widget->OnNativeWidgetActivationChanged(is_key)) {
return;
}
// The contentView is the BridgedContentView hosting the views::RootView. The
// focus manager will already know if a native subview has focus.
if (!is_content_first_responder)
return;
if (is_key) {
widget->OnNativeFocus();
widget->GetFocusManager()->RestoreFocusedView();
} else {
widget->OnNativeBlur();
widget->GetFocusManager()->StoreFocusedView(true);
parent_key_lock_.reset();
}
}
int32_t NativeWidgetMac::SheetOffsetY() {
return 0;
}
void NativeWidgetMac::GetWindowFrameTitlebarHeight(
bool* override_titlebar_height,
float* titlebar_height) {
*override_titlebar_height = false;
*titlebar_height = 0;
}
bool NativeWidgetMac::WillExecuteCommand(
int32_t command,
WindowOpenDisposition window_open_disposition,
bool is_before_first_responder) {
// This is supported only by subclasses in chrome/browser/ui.
NOTIMPLEMENTED();
return false;
}
bool NativeWidgetMac::ExecuteCommand(
int32_t command,
WindowOpenDisposition window_open_disposition,
bool is_before_first_responder) {
// This is supported only by subclasses in chrome/browser/ui.
NOTIMPLEMENTED();
return false;
}
void NativeWidgetMac::InitNativeWidget(Widget::InitParams params) {
ownership_ = params.ownership;
if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) {
owned_delegate_ = base::WrapUnique(delegate_.get());
}
name_ = params.name;
type_ = params.type;
NativeWidgetMacNSWindowHost* parent_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(params.parent);
// Determine the factory through which to create the bridge
remote_cocoa::ApplicationHost* application_host =
parent_host ? parent_host->application_host()
: GetRemoteCocoaApplicationHost();
// Compute the parameters to describe the NSWindow.
auto create_window_params = remote_cocoa::mojom::CreateWindowParams::New();
create_window_params->window_class =
remote_cocoa::mojom::WindowClass::kDefault;
create_window_params->style_mask = StyleMaskForParams(params);
create_window_params->titlebar_appears_transparent = false;
create_window_params->window_title_hidden = false;
PopulateCreateWindowParams(params, create_window_params.get());
if (application_host) {
ns_window_host_->CreateRemoteNSWindow(application_host,
std::move(create_window_params));
} else {
NativeWidgetMacNSWindow* window =
CreateNSWindow(create_window_params.get());
ns_window_host_->CreateInProcessNSWindowBridge(window);
}
// If the z-order wasn't specifically set to something other than `kNormal`,
// then override it if it would leave the widget z-ordered incorrectly in some
// platform-specific corner cases.
if (params.parent &&
(!params.z_order || params.z_order == ui::ZOrderLevel::kNormal)) {
if (auto* parent_widget = Widget::GetWidgetForNativeView(params.parent)) {
// If our parent is z-ordered above us, then float a bit higher.
params.z_order =
std::max(params.z_order.value_or(params.EffectiveZOrderLevel()),
parent_widget->GetZOrderLevel());
}
}
ns_window_host_->SetParent(parent_host);
ns_window_host_->InitWindow(params,
ConvertBoundsToScreenIfNeeded(params.bounds));
OnWindowInitialized();
// Only set the z-order here if it is non-default since setting it may affect
// how the window is treated by Expose.
if (params.EffectiveZOrderLevel() != ui::ZOrderLevel::kNormal)
SetZOrderLevel(params.EffectiveZOrderLevel());
GetNSWindowMojo()->SetIgnoresMouseEvents(!params.accept_events);
GetNSWindowMojo()->SetVisibleOnAllSpaces(params.visible_on_all_workspaces);
delegate_->OnNativeWidgetCreated();
DCHECK(GetWidget()->GetRootView());
ns_window_host_->SetRootView(GetWidget()->GetRootView());
GetNSWindowMojo()->CreateContentView(ns_window_host_->GetRootViewNSViewId(),
GetWidget()->GetRootView()->bounds());
if (auto* focus_manager = GetWidget()->GetFocusManager()) {
GetNSWindowMojo()->MakeFirstResponder();
// Only one ZoomFocusMonitor is needed per FocusManager, so create one only
// for top-level widgets.
if (GetWidget()->is_top_level())
zoom_focus_monitor_ = std::make_unique<ZoomFocusMonitor>();
SetFocusManager(focus_manager);
}
ns_window_host_->CreateCompositor(params);
g_init_native_widget_callbacks.Get().Notify(this);
}
void NativeWidgetMac::OnWidgetInitDone() {
OnSizeConstraintsChanged();
ns_window_host_->OnWidgetInitDone();
}
void NativeWidgetMac::ReparentNativeViewImpl(gfx::NativeView new_parent) {
gfx::NativeView child = GetNativeView();
DCHECK_NE(child, new_parent);
DCHECK([new_parent.GetNativeNSView() window]);
CHECK(new_parent);
CHECK_NE([child.GetNativeNSView() superview], new_parent.GetNativeNSView());
NativeWidgetMacNSWindowHost* child_window_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(child);
DCHECK(child_window_host);
gfx::NativeView widget_view =
child_window_host->native_widget_mac()->GetNativeView();
DCHECK_EQ(child, widget_view);
gfx::NativeWindow widget_window =
child_window_host->native_widget_mac()->GetNativeWindow();
DCHECK(
[child.GetNativeNSView() isDescendantOf:widget_view.GetNativeNSView()]);
DCHECK(widget_window && ![widget_window.GetNativeNSWindow() isSheet]);
NativeWidgetMacNSWindowHost* parent_window_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(new_parent);
// Early out for no-op changes.
if (child == widget_view &&
child_window_host->parent() == parent_window_host) {
return;
}
// First notify all the widgets that they are being disassociated from their
// previous parent.
Widget::Widgets widgets;
GetAllChildWidgets(child, &widgets);
for (Widget* widget : widgets) {
widget->NotifyNativeViewHierarchyWillChange();
}
child_window_host->SetParent(parent_window_host);
// And now, notify them that they have a brand new parent.
for (Widget* widget : widgets) {
widget->NotifyNativeViewHierarchyChanged();
}
}
std::unique_ptr<NonClientFrameView>
NativeWidgetMac::CreateNonClientFrameView() {
return GetWidget() ? std::make_unique<NativeFrameViewMac>(GetWidget())
: nullptr;
}
bool NativeWidgetMac::ShouldUseNativeFrame() const {
return true;
}
bool NativeWidgetMac::ShouldWindowContentsBeTransparent() const {
// On Windows, this returns true when Aero is enabled which draws the titlebar
// with translucency.
return false;
}
void NativeWidgetMac::FrameTypeChanged() {
if (!GetWidget()) {
return;
}
// This is called when the Theme has changed; forward the event to the root
// widget.
GetWidget()->ThemeChanged();
GetWidget()->GetRootView()->SchedulePaint();
}
Widget* NativeWidgetMac::GetWidget() {
return delegate_ ? delegate_->AsWidget() : nullptr;
}
const Widget* NativeWidgetMac::GetWidget() const {
return delegate_ ? delegate_->AsWidget() : nullptr;
}
gfx::NativeView NativeWidgetMac::GetNativeView() const {
// When a widget becomes a subwidget, its contentView moves to an another
// NSWindow. When this happens, the window's contentView will be nil.
// Return the cached original contentView instead.
NSView* contentView = (__bridge NSView*)GetNativeWindowProperty(
views::NativeWidgetMacNSWindowHost::kMovedContentNSView);
if (contentView) {
return gfx::NativeView(contentView);
}
// Returns a BridgedContentView, unless there is no views::RootView set.
return [GetNativeWindow().GetNativeNSWindow() contentView];
}
gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
return ns_window_host_ ? ns_window_host_->GetInProcessNSWindow() : nil;
}
Widget* NativeWidgetMac::GetTopLevelWidget() {
NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView());
return native_widget ? native_widget->GetWidget() : nullptr;
}
const ui::Compositor* NativeWidgetMac::GetCompositor() const {
return ns_window_host_ && ns_window_host_->layer()
? ns_window_host_->layer()->GetCompositor()
: nullptr;
}
const ui::Layer* NativeWidgetMac::GetLayer() const {
return ns_window_host_ ? ns_window_host_->layer() : nullptr;
}
void NativeWidgetMac::ReorderNativeViews() {
if (ns_window_host_)
ns_window_host_->ReorderChildViews();
}
void NativeWidgetMac::ViewRemoved(View* view) {
DragDropClientMac* client =
ns_window_host_ ? ns_window_host_->drag_drop_client() : nullptr;
if (client)
client->drop_helper()->ResetTargetViewIfEquals(view);
}
void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) {
if (ns_window_host_)
ns_window_host_->SetNativeWindowProperty(name, value);
}
void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const {
if (ns_window_host_)
return ns_window_host_->GetNativeWindowProperty(name);
return nullptr;
}
TooltipManager* NativeWidgetMac::GetTooltipManager() const {
if (ns_window_host_)
return ns_window_host_->tooltip_manager();
return nullptr;
}
void NativeWidgetMac::SetCapture() {
if (GetNSWindowMojo())
GetNSWindowMojo()->AcquireCapture();
}
void NativeWidgetMac::ReleaseCapture() {
if (GetNSWindowMojo())
GetNSWindowMojo()->ReleaseCapture();
}
bool NativeWidgetMac::HasCapture() const {
return ns_window_host_ && ns_window_host_->IsMouseCaptureActive();
}
ui::InputMethod* NativeWidgetMac::GetInputMethod() {
if (!input_method_) {
input_method_ = ui::CreateInputMethod(this, gfx::kNullAcceleratedWidget);
// For now, use always-focused mode on Mac for the input method.
// TODO(tapted): Move this to OnWindowKeyStatusChangedTo() and balance.
input_method_->OnFocus();
}
return input_method_.get();
}
void NativeWidgetMac::CenterWindow(const gfx::Size& size) {
if (GetNSWindowMojo() && GetWidget()) {
GetNSWindowMojo()->SetSizeAndCenter(size, GetWidget()->GetMinimumSize());
}
}
void NativeWidgetMac::GetWindowPlacement(
gfx::Rect* bounds,
ui::WindowShowState* show_state) const {
*bounds = GetRestoredBounds();
if (IsFullscreen())
*show_state = ui::SHOW_STATE_FULLSCREEN;
else if (IsMinimized())
*show_state = ui::SHOW_STATE_MINIMIZED;
else
*show_state = ui::SHOW_STATE_NORMAL;
}
bool NativeWidgetMac::SetWindowTitle(const std::u16string& title) {
if (!ns_window_host_)
return false;
return ns_window_host_->SetWindowTitle(title);
}
void NativeWidgetMac::SetWindowIcons(const gfx::ImageSkia& window_icon,
const gfx::ImageSkia& app_icon) {
// Per-window icons are not really a thing on Mac, so do nothing.
// TODO(tapted): Investigate whether to use NSWindowDocumentIconButton to set
// an icon next to the window title. See http://crbug.com/766897.
}
void NativeWidgetMac::InitModalType(ui::mojom::ModalType modal_type) {
if (modal_type == ui::mojom::ModalType::kNone) {
return;
}
// System modal windows not implemented (or used) on Mac.
DCHECK_NE(ui::mojom::ModalType::kSystem, modal_type);
// A peculiarity of the constrained window framework is that it permits a
// dialog of MODAL_TYPE_WINDOW to have a null parent window; falling back to
// a non-modal window in this case.
DCHECK(ns_window_host_->parent() ||
modal_type == ui::mojom::ModalType::kWindow);
// Everything happens upon show.
}
const gfx::ImageSkia* NativeWidgetMac::GetWindowIcon() {
return nullptr;
}
const gfx::ImageSkia* NativeWidgetMac::GetWindowAppIcon() {
return nullptr;
}
gfx::Rect NativeWidgetMac::GetWindowBoundsInScreen() const {
return ns_window_host_ ? ns_window_host_->GetWindowBoundsInScreen()
: gfx::Rect();
}
gfx::Rect NativeWidgetMac::GetClientAreaBoundsInScreen() const {
return ns_window_host_ ? ns_window_host_->GetContentBoundsInScreen()
: gfx::Rect();
}
gfx::Rect NativeWidgetMac::GetRestoredBounds() const {
return ns_window_host_ ? ns_window_host_->GetRestoredBounds() : gfx::Rect();
}
std::string NativeWidgetMac::GetWorkspace() const {
return ns_window_host_ ? base::Base64Encode(
ns_window_host_->GetWindowStateRestorationData())
: std::string();
}
gfx::Rect NativeWidgetMac::ConvertBoundsToScreenIfNeeded(
const gfx::Rect& bounds) const {
// If there isn't a parent widget, then bounds cannot be relative to the
// parent.
if (!ns_window_host_ || !ns_window_host_->parent() || !GetWidget())
return bounds;
// Replicate the logic in desktop_aura/desktop_screen_position_client.cc.
if (GetAuraWindowTypeForWidgetType(type_) ==
aura::client::WINDOW_TYPE_POPUP ||
GetWidget()->is_top_level()) {
return bounds;
}
// Empty bounds are only allowed to be specified at initialization and are
// expected not to be translated.
if (bounds.IsEmpty())
return bounds;
gfx::Rect bounds_in_screen = bounds;
bounds_in_screen.Offset(
ns_window_host_->parent()->GetWindowBoundsInScreen().OffsetFromOrigin());
return bounds_in_screen;
}
void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) {
if (!ns_window_host_)
return;
ns_window_host_->SetBoundsInScreen(ConvertBoundsToScreenIfNeeded(bounds));
}
void NativeWidgetMac::SetBoundsConstrained(const gfx::Rect& bounds) {
if (!ns_window_host_)
return;
gfx::Rect new_bounds(bounds);
if (ns_window_host_->parent()) {
new_bounds.AdjustToFit(
gfx::Rect(ns_window_host_->parent()->GetWindowBoundsInScreen().size()));
} else {
new_bounds = ConstrainBoundsToDisplayWorkArea(new_bounds);
}
SetBounds(new_bounds);
}
void NativeWidgetMac::SetSize(const gfx::Size& size) {
if (!ns_window_host_)
return;
ns_window_host_->SetSize(size);
}
void NativeWidgetMac::StackAbove(gfx::NativeView native_view) {
if (!GetNSWindowMojo())
return;
auto* sibling_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
if (!sibling_host) {
// This will only work if |this| is in-process.
DCHECK(!ns_window_host_->application_host());
NSInteger view_parent = native_view.GetNativeNSView().window.windowNumber;
[GetNativeWindow().GetNativeNSWindow() orderWindow:NSWindowAbove
relativeTo:view_parent];
return;
}
CHECK_EQ(ns_window_host_->application_host(),
sibling_host->application_host())
<< "|native_view|'s NativeWidgetMacNSWindowHost isn't same "
"process |this|";
// Check if |native_view|'s NativeWidgetMacNSWindowHost corresponds to the
// same process as |this|.
GetNSWindowMojo()->StackAbove(sibling_host->bridged_native_widget_id());
return;
}
void NativeWidgetMac::StackAtTop() {
if (GetNSWindowMojo())
GetNSWindowMojo()->StackAtTop();
}
bool NativeWidgetMac::IsStackedAbove(gfx::NativeView native_view) {
if (!GetNSWindowMojo())
return false;
// -[NSApplication orderedWindows] are ordered front-to-back.
NSWindow* first = GetNativeWindow().GetNativeNSWindow();
NSWindow* second = [native_view.GetNativeNSView() window];
for (NSWindow* window in [NSApp orderedWindows]) {
if (window == second)
return !first;
if (window == first)
first = nil;
}
return false;
}
void NativeWidgetMac::SetShape(std::unique_ptr<Widget::ShapeRects> shape) {
NOTIMPLEMENTED();
}
void NativeWidgetMac::Close() {
if (GetNSWindowMojo())
GetNSWindowMojo()->CloseWindow();
}
void NativeWidgetMac::CloseNow() {
if (ns_window_host_)
ns_window_host_->CloseWindowNow();
// Note: |ns_window_host_| will be deleted here, and |this| will be deleted
// here when ownership_ == NATIVE_WIDGET_OWNS_WIDGET,
}
void NativeWidgetMac::Show(ui::WindowShowState show_state,
const gfx::Rect& restore_bounds) {
if (!GetNSWindowHost() || !delegate_) {
return;
}
switch (show_state) {
case ui::SHOW_STATE_DEFAULT:
case ui::SHOW_STATE_NORMAL:
case ui::SHOW_STATE_INACTIVE:
case ui::SHOW_STATE_MINIMIZED:
break;
case ui::SHOW_STATE_MAXIMIZED:
case ui::SHOW_STATE_FULLSCREEN:
NOTIMPLEMENTED();
break;
case ui::SHOW_STATE_END:
NOTREACHED();
}
auto window_state = WindowVisibilityState::kShowAndActivateWindow;
if (show_state == ui::SHOW_STATE_INACTIVE) {
window_state = WindowVisibilityState::kShowInactive;
} else if (show_state == ui::SHOW_STATE_MINIMIZED) {
window_state = WindowVisibilityState::kMiniaturizeWindow;
} else if (show_state == ui::SHOW_STATE_DEFAULT) {
window_state = delegate_->CanActivate()
? window_state
: WindowVisibilityState::kShowInactive;
}
GetNSWindowHost()->SetVisibilityState(window_state);
// Ignore the SetInitialFocus() result. BridgedContentView should get
// firstResponder status regardless.
delegate_->SetInitialFocus(show_state);
}
void NativeWidgetMac::Hide() {
if (!GetNSWindowHost()) {
return;
}
GetNSWindowHost()->SetVisibilityState(WindowVisibilityState::kHideWindow);
}
bool NativeWidgetMac::IsVisible() const {
return ns_window_host_ && ns_window_host_->IsVisible();
}
void NativeWidgetMac::Activate() {
if (!GetNSWindowHost()) {
return;
}
GetNSWindowHost()->SetVisibilityState(
WindowVisibilityState::kShowAndActivateWindow);
}
void NativeWidgetMac::Deactivate() {
NOTIMPLEMENTED();
}
bool NativeWidgetMac::IsActive() const {
return ns_window_host_ ? ns_window_host_->IsWindowKey() : false;
}
void NativeWidgetMac::SetZOrderLevel(ui::ZOrderLevel order) {
if (!GetNSWindowMojo())
return;
z_order_level_ = order;
GetNSWindowMojo()->SetWindowLevel(CGWindowLevelForZOrderLevel(order, type_));
}
ui::ZOrderLevel NativeWidgetMac::GetZOrderLevel() const {
return z_order_level_;
}
void NativeWidgetMac::SetVisibleOnAllWorkspaces(bool always_visible) {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->SetVisibleOnAllSpaces(always_visible);
}
bool NativeWidgetMac::IsVisibleOnAllWorkspaces() const {
return false;
}
void NativeWidgetMac::Maximize() {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->SetZoomed(true);
}
void NativeWidgetMac::Minimize() {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->SetMiniaturized(true);
}
bool NativeWidgetMac::IsMaximized() const {
if (!ns_window_host_)
return false;
return ns_window_host_->IsZoomed();
}
bool NativeWidgetMac::IsMinimized() const {
if (!ns_window_host_)
return false;
return ns_window_host_->IsMiniaturized();
}
void NativeWidgetMac::Restore() {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->ExitFullscreen();
GetNSWindowMojo()->SetMiniaturized(false);
GetNSWindowMojo()->SetZoomed(false);
}
void NativeWidgetMac::SetFullscreen(bool fullscreen,
int64_t target_display_id) {
if (!ns_window_host_)
return;
ns_window_host_->SetFullscreen(fullscreen, target_display_id);
}
bool NativeWidgetMac::IsFullscreen() const {
return ns_window_host_ && ns_window_host_->target_fullscreen_state();
}
void NativeWidgetMac::SetCanAppearInExistingFullscreenSpaces(
bool can_appear_in_existing_fullscreen_spaces) {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->SetCanAppearInExistingFullscreenSpaces(
can_appear_in_existing_fullscreen_spaces);
}
void NativeWidgetMac::SetOpacity(float opacity) {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->SetOpacity(opacity);
}
void NativeWidgetMac::SetAspectRatio(const gfx::SizeF& aspect_ratio,
const gfx::Size& excluded_margin) {
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->SetAspectRatio(aspect_ratio, excluded_margin);
}
void NativeWidgetMac::FlashFrame(bool flash_frame) {
NOTIMPLEMENTED();
}
void NativeWidgetMac::RunShellDrag(std::unique_ptr<ui::OSExchangeData> data,
const gfx::Point& location,
int operation,
ui::mojom::DragEventSource source) {
if (!ns_window_host_)
return;
ns_window_host_->drag_drop_client()->StartDragAndDrop(std::move(data),
operation, source);
}
void NativeWidgetMac::CancelShellDrag(View* view) {
if (!ns_window_host_) {
return;
}
ns_window_host_->drag_drop_client()->EndDrag();
}
void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) {
// |rect| is relative to client area of the window.
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
NSRect client_rect = [window contentRectForFrameRect:[window frame]];
NSRect target_rect = rect.ToCGRect();
// Convert to Appkit coordinate system (origin at bottom left).
target_rect.origin.y =
NSHeight(client_rect) - target_rect.origin.y - NSHeight(target_rect);
[GetNativeView().GetNativeNSView() setNeedsDisplayInRect:target_rect];
if (ns_window_host_ && ns_window_host_->layer())
ns_window_host_->layer()->SchedulePaint(rect);
}
void NativeWidgetMac::ScheduleLayout() {
ui::Compositor* compositor = GetCompositor();
if (compositor)
compositor->ScheduleDraw();
}
void NativeWidgetMac::SetCursor(const ui::Cursor& cursor) {
if (GetNSWindowMojo())
GetNSWindowMojo()->SetCursor(cursor);
}
void NativeWidgetMac::ShowEmojiPanel() {
// We must plumb the call to ui::ShowEmojiPanel() over the bridge so that it
// is called from the correct process.
if (GetNSWindowMojo())
GetNSWindowMojo()->ShowEmojiPanel();
}
bool NativeWidgetMac::IsMouseEventsEnabled() const {
// On platforms with touch, mouse events get disabled and calls to this method
// can affect hover states. Since there is no touch on desktop Mac, this is
// always true. Touch on Mac is tracked in http://crbug.com/445520.
return true;
}
bool NativeWidgetMac::IsMouseButtonDown() const {
return [NSEvent pressedMouseButtons] != 0;
}
void NativeWidgetMac::ClearNativeFocus() {
// To quote DesktopWindowTreeHostX11, "This method is weird and misnamed."
// The goal is to set focus to the content window, thereby removing focus from
// any NSView in the window that doesn't belong to toolkit-views.
if (!GetNSWindowMojo())
return;
GetNSWindowMojo()->MakeFirstResponder();
}
gfx::Rect NativeWidgetMac::GetWorkAreaBoundsInScreen() const {
return ns_window_host_ ? ns_window_host_->GetCurrentDisplay().work_area()
: gfx::Rect();
}
Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop(
const gfx::Vector2d& drag_offset,
Widget::MoveLoopSource source,
Widget::MoveLoopEscapeBehavior escape_behavior) {
if (!GetInProcessNSWindowBridge())
return Widget::MoveLoopResult::kCanceled;
ReleaseCapture();
return GetInProcessNSWindowBridge()->RunMoveLoop(drag_offset)
? Widget::MoveLoopResult::kSuccessful
: Widget::MoveLoopResult::kCanceled;
}
void NativeWidgetMac::EndMoveLoop() {
if (GetInProcessNSWindowBridge())
GetInProcessNSWindowBridge()->EndMoveLoop();
}
void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) {
if (GetNSWindowMojo())
GetNSWindowMojo()->SetAnimationEnabled(value);
}
void NativeWidgetMac::SetVisibilityAnimationDuration(
const base::TimeDelta& duration) {
NOTIMPLEMENTED();
}
void NativeWidgetMac::SetVisibilityAnimationTransition(
Widget::VisibilityTransition widget_transitions) {
remote_cocoa::mojom::VisibilityTransition transitions =
remote_cocoa::mojom::VisibilityTransition::kNone;
switch (widget_transitions) {
case Widget::ANIMATE_NONE:
transitions = remote_cocoa::mojom::VisibilityTransition::kNone;
break;
case Widget::ANIMATE_SHOW:
transitions = remote_cocoa::mojom::VisibilityTransition::kShow;
break;
case Widget::ANIMATE_HIDE:
transitions = remote_cocoa::mojom::VisibilityTransition::kHide;
break;
case Widget::ANIMATE_BOTH:
transitions = remote_cocoa::mojom::VisibilityTransition::kBoth;
break;
}
if (GetNSWindowMojo())
GetNSWindowMojo()->SetTransitionsToAnimate(transitions);
}
ui::GestureRecognizer* NativeWidgetMac::GetGestureRecognizer() {
static base::NoDestructor<ui::GestureRecognizerImplMac> recognizer;
return recognizer.get();
}
ui::GestureConsumer* NativeWidgetMac::GetGestureConsumer() {
NOTIMPLEMENTED();
return nullptr;
}
void NativeWidgetMac::OnSizeConstraintsChanged() {
if (!GetNSWindowMojo() || !GetWidget()) {
return;
}
Widget* widget = GetWidget();
GetNSWindowMojo()->SetSizeConstraints(
widget->GetMinimumSize(), widget->GetMaximumSize(),
widget->widget_delegate()->CanResize(),
widget->widget_delegate()->CanMaximize());
}
void NativeWidgetMac::OnNativeViewHierarchyWillChange() {
// If this is not top-level, then the FocusManager may change, so remove our
// listeners.
if (GetWidget() && !GetWidget()->is_top_level()) {
SetFocusManager(nullptr);
}
parent_key_lock_.reset();
}
void NativeWidgetMac::OnNativeViewHierarchyChanged() {
if (GetWidget() && !GetWidget()->is_top_level()) {
SetFocusManager(GetWidget()->GetFocusManager());
}
}
bool NativeWidgetMac::SetAllowScreenshots(bool allow) {
if (ns_window_host_) {
ns_window_host_->SetAllowScreenshots(allow);
return true;
}
return false;
}
bool NativeWidgetMac::AreScreenshotsAllowed() {
if (ns_window_host_) {
return ns_window_host_->AllowScreenshots();
}
return true;
}
std::string NativeWidgetMac::GetName() const {
return name_;
}
base::WeakPtr<internal::NativeWidgetPrivate> NativeWidgetMac::GetWeakPtr() {
return weak_factory.GetWeakPtr();
}
// static
base::CallbackListSubscription
NativeWidgetMac::RegisterInitNativeWidgetCallback(
const base::RepeatingCallback<void(NativeWidgetMac*)>& callback) {
DCHECK(!callback.is_null());
return g_init_native_widget_callbacks.Get().Add(callback);
}
void NativeWidgetMac::PopulateCreateWindowParams(
const Widget::InitParams& widget_params,
remote_cocoa::mojom::CreateWindowParams* params) {
if (widget_params.is_overlay) {
params->window_class = remote_cocoa::mojom::WindowClass::kOverlay;
}
}
NativeWidgetMacNSWindow* NativeWidgetMac::CreateNSWindow(
const remote_cocoa::mojom::CreateWindowParams* params) {
return remote_cocoa::NativeWidgetNSWindowBridge::CreateNSWindow(params);
}
remote_cocoa::ApplicationHost*
NativeWidgetMac::GetRemoteCocoaApplicationHost() {
return nullptr;
}
remote_cocoa::mojom::NativeWidgetNSWindow* NativeWidgetMac::GetNSWindowMojo()
const {
return ns_window_host_ ? ns_window_host_->GetNSWindowMojo() : nullptr;
}
remote_cocoa::NativeWidgetNSWindowBridge*
NativeWidgetMac::GetInProcessNSWindowBridge() const {
return ns_window_host_ ? ns_window_host_->GetInProcessNSWindowBridge()
: nullptr;
}
void NativeWidgetMac::SetFocusManager(FocusManager* new_focus_manager) {
if (focus_manager_) {
if (View* old_focus = focus_manager_->GetFocusedView())
OnDidChangeFocus(old_focus, nullptr);
focus_manager_->RemoveFocusChangeListener(this);
if (zoom_focus_monitor_)
focus_manager_->RemoveFocusChangeListener(zoom_focus_monitor_.get());
}
focus_manager_ = new_focus_manager;
if (focus_manager_) {
if (View* new_focus = focus_manager_->GetFocusedView())
OnDidChangeFocus(nullptr, new_focus);
focus_manager_->AddFocusChangeListener(this);
if (zoom_focus_monitor_)
focus_manager_->AddFocusChangeListener(zoom_focus_monitor_.get());
if (!widget_observation_.IsObserving()) {
CHECK(GetWidget());
widget_observation_.Observe(GetWidget());
}
}
}
void NativeWidgetMac::OnWillChangeFocus(View* focused_before,
View* focused_now) {}
void NativeWidgetMac::OnDidChangeFocus(View* focused_before,
View* focused_now) {
ui::InputMethod* input_method = GetWidget()->GetInputMethod();
if (!input_method)
return;
ui::TextInputClient* new_text_input_client =
input_method->GetTextInputClient();
// Sanity check: For a top level widget, when focus moves away from the widget
// (i.e. |focused_now| is nil), then the textInputClient will be cleared.
DCHECK(!!focused_now || !new_text_input_client ||
!GetWidget()->is_top_level());
if (ns_window_host_) {
ns_window_host_->text_input_host()->SetTextInputClient(
new_text_input_client);
}
}
ui::EventDispatchDetails NativeWidgetMac::DispatchKeyEventPostIME(
ui::KeyEvent* key) {
DCHECK(focus_manager_);
if (!focus_manager_->OnKeyEvent(*key))
key->StopPropagation();
else
GetWidget()->OnKeyEvent(key);
return ui::EventDispatchDetails();
}
void NativeWidgetMac::OnWidgetDestroyed(Widget* widget) {
widget_observation_.Reset();
// The `widget` owns the `FocusManager`. As such, `NativeWidgetMac` must
// unregister itself as a focus change listener here if it hasn't done so
// already, or it may retain a dead focus manager pointer (risking
// use-after-free).
SetFocusManager(nullptr);
}
////////////////////////////////////////////////////////////////////////////////
// Widget:
// static
void Widget::CloseAllSecondaryWidgets() {
NSArray* starting_windows = [NSApp windows]; // Creates an autoreleased copy.
for (NSWindow* window in starting_windows) {
// Ignore any windows that couldn't have been created by NativeWidgetMac or
// a subclass. GetNativeWidgetForNativeWindow() will later interrogate the
// NSWindow delegate, but we can't trust that delegate to be a valid object.
if (![window isKindOfClass:[NativeWidgetMacNSWindow class]])
continue;
// Record a crash key to detect when client code may destroy a
// WidgetObserver without removing it (possibly leaking the Widget).
// A crash can occur in generic Widget teardown paths when trying to notify.
// See http://crbug.com/808318.
static crash_reporter::CrashKeyString<256> window_info_key("windowInfo");
std::string value = base::SysNSStringToUTF8(
[NSString stringWithFormat:@"Closing %@ (%@)", [window title],
[window className]]);
crash_reporter::ScopedCrashKeyString scopedWindowKey(&window_info_key,
value);
Widget* widget = GetWidgetForNativeWindow(window);
if (widget && widget->is_secondary_widget())
[window close];
}
}
namespace internal {
////////////////////////////////////////////////////////////////////////////////
// internal::NativeWidgetPrivate:
// static
NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget(
internal::NativeWidgetDelegate* delegate) {
return new NativeWidgetMac(delegate);
}
// static
NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView(
gfx::NativeView native_view) {
return GetNativeWidgetForNativeWindow([native_view.GetNativeNSView() window]);
}
// static
NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
gfx::NativeWindow window) {
if (NativeWidgetMacNSWindowHost* ns_window_host_impl =
NativeWidgetMacNSWindowHost::GetFromNativeWindow(window)) {
return ns_window_host_impl->native_widget_mac();
}
return nullptr; // Not created by NativeWidgetMac.
}
// static
NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget(
gfx::NativeView native_view) {
NativeWidgetMacNSWindowHost* window_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
if (!window_host)
return nullptr;
while (window_host->parent()) {
if (!window_host->native_widget_mac()->GetWidget()) {
return nullptr;
}
if (window_host->native_widget_mac()->GetWidget()->is_top_level())
break;
window_host = window_host->parent();
}
return window_host->native_widget_mac();
}
// static
void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view,
Widget::Widgets* children) {
NativeWidgetMacNSWindowHost* window_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
if (!window_host) {
NSView* ns_view = native_view.GetNativeNSView();
// The NSWindow is not itself a views::Widget, but it may have children that
// are. Support returning Widgets that are parented to the NSWindow, except:
// - Ignore requests for children of an NSView that is not a contentView.
// - We do not add a Widget for |native_view| to |children| (there is none).
if ([[ns_view window] contentView] != ns_view)
return;
// Collect -sheets and -childWindows. A window should never appear in both,
// since that causes AppKit to glitch.
NSArray* sheet_children = [[ns_view window] sheets];
for (NSWindow* native_child in sheet_children)
GetAllChildWidgets([native_child contentView], children);
for (NSWindow* native_child in [[ns_view window] childWindows]) {
DCHECK(![sheet_children containsObject:native_child]);
GetAllChildWidgets([native_child contentView], children);
}
return;
}
// If |native_view| is a subview of the contentView, it will share an
// NSWindow, but will itself be a native child of the Widget. That is, adding
// window_host->..->GetWidget() to |children| would be adding the _parent_ of
// |native_view|, not the Widget for |native_view|. |native_view| doesn't have
// a corresponding Widget of its own in this case (and so can't have Widget
// children of its own on Mac).
if (window_host->native_widget_mac()->GetNativeView() != native_view)
return;
// Code expects widget for |native_view| to be added to |children|.
if (window_host->native_widget_mac()->GetWidget())
children->insert(window_host->native_widget_mac()->GetWidget());
// When the NSWindow *is* a Widget, only consider children(). I.e. do not
// look through -[NSWindow childWindows] as done for the (!window_host) case
// above. -childWindows does not support hidden windows, and anything in there
// which is not in children() would have been added by AppKit.
for (NativeWidgetMacNSWindowHost* child : window_host->children())
GetAllChildWidgets(child->native_widget_mac()->GetNativeView(), children);
}
// static
void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view,
Widget::Widgets* owned) {
NativeWidgetMacNSWindowHost* window_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
if (!window_host) {
GetAllChildWidgets(native_view, owned);
return;
}
if (window_host->native_widget_mac()->GetNativeView() != native_view)
return;
for (NativeWidgetMacNSWindowHost* child : window_host->children())
GetAllChildWidgets(child->native_widget_mac()->GetNativeView(), owned);
}
// static
void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView child,
gfx::NativeView new_parent) {
Widget::GetWidgetForNativeView(child)
->native_widget_private()
->ReparentNativeViewImpl(new_parent);
}
// static
gfx::NativeView NativeWidgetPrivate::GetGlobalCapture(
gfx::NativeView native_view) {
return NativeWidgetMacNSWindowHost::GetGlobalCaptureView();
}
} // namespace internal
} // namespace views