blob: e40ec25c63a820a8a767f4717679dfda24f3187a [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 "ui/views/widget/native_widget_mac.h"
#include <ApplicationServices/ApplicationServices.h>
#import <Cocoa/Cocoa.h>
#include <CoreFoundation/CoreFoundation.h>
#include <optional>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/check_op.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/callback.h"
#include "base/lazy_instance.h"
#include "base/no_destructor.h"
#include "base/notimplemented.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/ime/text_input_client.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/color/color_provider_key.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/color_utils.h"
#include "ui/gfx/font_list.h"
#import "ui/gfx/mac/coordinate_conversion.h"
#include "ui/gfx/native_ui_types.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.
// A sheet will not have a titlebar despite being NSWindowStyleMaskTitled.
// NSWindowStyleMaskFullSizeContentView ensures that calculating the modal's
// content rect doesn't account for a nonexistent title bar.
// TODO(crbug.com/390441085): a window-modal should always have a parent.
// Otherwise, it will not be displayed as a sheet. Then it will show a native
// titlebar, which will cover the content.
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 (focused_now->GetClassName() == "WebView") {
return;
}
NSRect rect = 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();
}
}
void NativeWidgetMac::OnWindowWillStartLiveResize() {
if (delegate_) {
delegate_->OnNativeWidgetUserResizeStarted();
}
}
void NativeWidgetMac::OnWindowDidEndLiveResize() {
if (delegate_) {
delegate_->OnNativeWidgetUserResizeEnded();
}
}
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;
}
gfx::NativeViewAccessible NativeWidgetMac::GetNativeViewAccessibleForNSView()
const {
return ns_window_host_->GetNativeViewAccessibleForNSView();
}
gfx::NativeViewAccessible NativeWidgetMac::GetNativeViewAccessibleForNSWindow()
const {
return ns_window_host_->GetNativeViewAccessibleForNSWindow();
}
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());
std::optional<int> corner_radius;
if (params.rounded_corners) {
CHECK_EQ(params.rounded_corners->upper_left(),
params.rounded_corners->upper_right());
CHECK_EQ(params.rounded_corners->upper_left(),
params.rounded_corners->lower_left());
CHECK_EQ(params.rounded_corners->lower_left(),
params.rounded_corners->lower_right());
corner_radius = params.rounded_corners->upper_left();
}
GetNSWindowMojo()->CreateContentView(ns_window_host_->GetRootViewNSViewId(),
GetWidget()->GetRootView()->bounds(),
corner_radius);
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);
if (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);
DCHECK(![widget_window.GetNativeNSWindow() isSheet]);
NativeWidgetMacNSWindowHost* new_parent_window_host =
new_parent ? NativeWidgetMacNSWindowHost::GetFromNativeView(new_parent)
: nullptr;
// Early out for no-op changes.
if (new_parent_window_host && child == widget_view &&
child_window_host->parent() == new_parent_window_host) {
return;
}
// First notify all the widgets that they are being disassociated from their
// previous parent.
Widget::Widgets widgets = GetAllChildWidgets(child);
for (Widget* widget : widgets) {
widget->NotifyNativeViewHierarchyWillChange();
}
child_window_host->SetParent(new_parent_window_host);
// And now, notify them that they have a brand new parent.
for (Widget* widget : widgets) {
widget->NotifyNativeViewHierarchyChanged();
}
}
std::unique_ptr<FrameView> NativeWidgetMac::CreateFrameView() {
return GetWidget() ? std::make_unique<NativeFrameViewMac>(GetWidget(),
/*client=*/nullptr)
: 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 gfx::NativeView(GetNativeWindow().GetNativeNSWindow().contentView);
}
gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const {
return gfx::NativeWindow(
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::mojom::WindowShowState* show_state) const {
*bounds = GetRestoredBounds();
if (IsFullscreen()) {
*show_state = ui::mojom::WindowShowState::kFullscreen;
} else if (IsMinimized()) {
*show_state = ui::mojom::WindowShowState::kMinimized;
} else {
*show_state = ui::mojom::WindowShowState::kNormal;
}
}
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.
}
void NativeWidgetMac::SetBackgroundColor(SkColor background_color) {
if (ns_window_host_) {
// The NSAppearance of a NSWindow affects traffic light contrast. The
// NSAppearance is determined by the color mode set on the window host. In
// macOS 26, if the color mode is light but the window has a dark background
// color, traffic lights in inactive windows lose contrast and become
// invisible. Therefore, if an explicit `background_color` is available,
// override the color mode to match that background's luminance.
ui::ColorProviderKey::ColorMode frame_color_mode =
color_utils::IsDark(background_color)
? ui::ColorProviderKey::ColorMode::kDark
: ui::ColorProviderKey::ColorMode::kLight;
ns_window_host_->SetColorMode(frame_color_mode);
}
}
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::mojom::WindowShowState show_state,
const gfx::Rect& restore_bounds) {
if (!GetNSWindowHost() || !delegate_) {
return;
}
switch (show_state) {
case ui::mojom::WindowShowState::kDefault:
case ui::mojom::WindowShowState::kNormal:
case ui::mojom::WindowShowState::kInactive:
case ui::mojom::WindowShowState::kMinimized:
break;
case ui::mojom::WindowShowState::kMaximized:
case ui::mojom::WindowShowState::kFullscreen:
NOTIMPLEMENTED();
break;
case ui::mojom::WindowShowState::kEnd:
NOTREACHED();
}
auto window_state = WindowVisibilityState::kShowAndActivateWindow;
if (show_state == ui::mojom::WindowShowState::kInactive) {
window_state = WindowVisibilityState::kShowInactive;
} else if (show_state == ui::mojom::WindowShowState::kMinimized) {
window_state = WindowVisibilityState::kMiniaturizeWindow;
} else if (show_state == ui::mojom::WindowShowState::kDefault) {
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();
}
bool NativeWidgetMac::IsVisibleOnScreen() const {
return ns_window_host_ && ns_window_host_->IsVisibleOnScreen();
}
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::SetActivationIndependence(bool independence) {
if (!GetNSWindowMojo()) {
return;
}
GetNSWindowMojo()->SetActivationIndependence(independence);
}
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;
}
bool NativeWidgetMac::IsDesktopNativeWidget() const {
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;
}
params->animation_enabled = widget_params.animation_enabled;
}
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);
}
}
void NativeWidgetMac::OnFocusManagerDestroying(FocusManager* focus_manager) {
// TODO(crbug.com/348369180): mac fullscreen overlay widget should be
// destroyed before its parent widget, subsequently stopping observing the
// parent's focus manager. However, this is not happening for unknown reasons.
CHECK_EQ(focus_manager, focus_manager_);
focus_manager->RemoveFocusChangeListener(this);
focus_manager_ = nullptr;
}
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::CloseAllWidgets() {
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);
// It is necessary to call `GetNativeWidgetForNativeWindow()` below as the
// views::Widget may be destroyed independently from its NativeWidget (see
// CLIENT_OWNS_WIDGET), and in this case `GetWidgetForNativeWindow()` will
// return null.
if (internal::NativeWidgetPrivate* native_widget =
internal::NativeWidgetPrivate::GetNativeWidgetForNativeWindow(
gfx::NativeWindow(window))) {
// `CloseNow()` will destroy both in-process and remote NSWindows.
native_widget->CloseNow();
}
}
}
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(
gfx::NativeWindow([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
Widget::Widgets 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 children;
}
// 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) {
children.merge(
GetAllChildWidgets(gfx::NativeView(native_child.contentView)));
}
for (NSWindow* native_child in ns_view.window.childWindows) {
DCHECK(![sheet_children containsObject:native_child]);
children.merge(
GetAllChildWidgets(gfx::NativeView(native_child.contentView)));
}
return children;
}
// 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 children;
}
// 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()) {
children.merge(
GetAllChildWidgets(child->native_widget_mac()->GetNativeView()));
}
return children;
}
// static
Widget::Widgets NativeWidgetPrivate::GetAllOwnedWidgets(
gfx::NativeView native_view) {
NativeWidgetMacNSWindowHost* window_host =
NativeWidgetMacNSWindowHost::GetFromNativeView(native_view);
if (!window_host) {
return GetAllChildWidgets(native_view);
}
if (window_host->native_widget_mac()->GetNativeView() != native_view) {
return Widget::Widgets();
}
Widget::Widgets owned;
for (NativeWidgetMacNSWindowHost* child : window_host->children()) {
owned.merge(
GetAllChildWidgets(child->native_widget_mac()->GetNativeView()));
}
return 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 gfx::NativeView(NativeWidgetMacNSWindowHost::GetGlobalCaptureView());
}
} // namespace internal
} // namespace views