1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "components/remote_cocoa/app_shim/views_nswindow_delegate.h" 6 7#include "base/bind.h" 8#include "base/logging.h" 9#include "base/mac/mac_util.h" 10#include "base/threading/thread_task_runner_handle.h" 11#import "components/remote_cocoa/app_shim/bridged_content_view.h" 12#import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h" 13#include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h" 14#include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h" 15 16@implementation ViewsNSWindowDelegate 17 18- (instancetype)initWithBridgedNativeWidget: 19 (remote_cocoa::NativeWidgetNSWindowBridge*)parent { 20 DCHECK(parent); 21 if ((self = [super init])) { 22 _parent = parent; 23 } 24 return self; 25} 26 27- (NSCursor*)cursor { 28 return _cursor.get(); 29} 30 31- (void)setCursor:(NSCursor*)newCursor { 32 if (_cursor.get() == newCursor) 33 return; 34 35 _cursor.reset([newCursor retain]); 36 37 // The window has a tracking rect that was installed in -[BridgedContentView 38 // initWithView:] that uses the NSTrackingCursorUpdate option. In the case 39 // where the window is the key window, that tracking rect will cause 40 // -cursorUpdate: to be sent up the responder chain, which will cause the 41 // cursor to be set when the message gets to the NativeWidgetMacNSWindow. 42 NSWindow* window = _parent->ns_window(); 43 [window resetCursorRects]; 44 45 // However, if this window isn't the key window, that tracking area will have 46 // no effect. This is good if this window is just some top-level window that 47 // isn't key, but isn't so good if this window isn't key but is a child window 48 // of a window that is key. To handle that case, the case where the 49 // -cursorUpdate: message will never be sent, just set the cursor here. 50 // 51 // Only do this for non-key windows so that there will be no flickering 52 // between cursors set here and set elsewhere. 53 // 54 // (This is a known issue; see https://stackoverflow.com/questions/45712066/.) 55 if (![window isKeyWindow]) { 56 NSWindow* currentWindow = window; 57 // Walk up the window chain. If there is a key window in the window parent 58 // chain, then work around the issue and set the cursor. 59 while (true) { 60 NSWindow* parentWindow = [currentWindow parentWindow]; 61 if (!parentWindow) 62 break; 63 currentWindow = parentWindow; 64 if ([currentWindow isKeyWindow]) { 65 [(newCursor ? newCursor : [NSCursor arrowCursor]) set]; 66 break; 67 } 68 } 69 } 70} 71 72- (void)onWindowOrderChanged:(NSNotification*)notification { 73 _parent->OnVisibilityChanged(); 74} 75 76- (void)onSystemControlTintChanged:(NSNotification*)notification { 77 _parent->OnSystemControlTintChanged(); 78} 79 80- (void)sheetDidEnd:(NSWindow*)sheet 81 returnCode:(NSInteger)returnCode 82 contextInfo:(void*)contextInfo { 83 [sheet orderOut:nil]; 84 _parent->OnWindowWillClose(); 85} 86 87// NSWindowDelegate implementation. 88 89- (void)windowDidFailToEnterFullScreen:(NSWindow*)window { 90 // Cocoa should already have sent an (unexpected) windowDidExitFullScreen: 91 // notification, and the attempt to get back into fullscreen should fail. 92 // Nothing to do except verify |parent_| is no longer trying to fullscreen. 93 DCHECK(!_parent->target_fullscreen_state()); 94} 95 96- (void)windowDidFailToExitFullScreen:(NSWindow*)window { 97 // Unlike entering fullscreen, windowDidFailToExitFullScreen: is sent *before* 98 // windowDidExitFullScreen:. Also, failing to exit fullscreen just dumps the 99 // window out of fullscreen without an animation; still sending the expected, 100 // windowDidExitFullScreen: notification. So, again, nothing to do here. 101 DCHECK(!_parent->target_fullscreen_state()); 102} 103 104- (void)windowDidResize:(NSNotification*)notification { 105 _parent->OnSizeChanged(); 106} 107 108- (void)windowDidMove:(NSNotification*)notification { 109 // Note: windowDidMove: is sent only once at the end of a window drag. There 110 // is also windowWillMove: sent at the start, also once. When the window is 111 // being moved by the WindowServer live updates are not provided. 112 _parent->OnPositionChanged(); 113} 114 115- (void)windowDidBecomeKey:(NSNotification*)notification { 116 _parent->OnWindowKeyStatusChangedTo(true); 117} 118 119- (void)windowDidResignKey:(NSNotification*)notification { 120 // If our app is still active and we're still the key window, ignore this 121 // message, since it just means that a menu extra (on the "system status bar") 122 // was activated; we'll get another |-windowDidResignKey| if we ever really 123 // lose key window status. 124 if ([NSApp isActive] && ([NSApp keyWindow] == notification.object)) 125 return; 126 _parent->OnWindowKeyStatusChangedTo(false); 127} 128 129- (BOOL)windowShouldClose:(id)sender { 130 bool canWindowClose = true; 131 _parent->host()->GetCanWindowClose(&canWindowClose); 132 return canWindowClose; 133} 134 135- (void)windowWillClose:(NSNotification*)notification { 136 NSWindow* window = _parent->ns_window(); 137 if (NSWindow* sheetParent = [window sheetParent]) { 138 // On no! Something called -[NSWindow close] on a sheet rather than calling 139 // -[NSWindow endSheet:] on its parent. If the modal session is not ended 140 // then the parent will never be able to show another sheet. But calling 141 // -endSheet: here will block the thread with an animation, so post a task. 142 // Use a block: The argument to -endSheet: must be retained, since it's the 143 // window that is closing and -performSelector: won't retain the argument 144 // (putting |window| on the stack above causes this block to retain it). 145 base::ThreadTaskRunnerHandle::Get()->PostTask( 146 FROM_HERE, base::BindOnce(base::RetainBlock(^{ 147 [sheetParent endSheet:window]; 148 }))); 149 } 150 DCHECK([window isEqual:[notification object]]); 151 _parent->OnWindowWillClose(); 152 // |self| may be deleted here (it's NSObject, so who really knows). 153 // |parent_| _will_ be deleted for sure. 154 155 // Note OnWindowWillClose() will clear the NSWindow delegate. That is, |self|. 156 // That guarantees that the task possibly-posted above will never call into 157 // our -sheetDidEnd:. (The task's purpose is just to unblock the modal session 158 // on the parent window.) 159 DCHECK(![window delegate]); 160} 161 162- (void)windowDidMiniaturize:(NSNotification*)notification { 163 _parent->host()->OnWindowMiniaturizedChanged(true); 164 _parent->OnVisibilityChanged(); 165} 166 167- (void)windowDidDeminiaturize:(NSNotification*)notification { 168 _parent->host()->OnWindowMiniaturizedChanged(false); 169 _parent->OnVisibilityChanged(); 170} 171 172- (void)windowDidChangeBackingProperties:(NSNotification*)notification { 173 _parent->OnBackingPropertiesChanged(); 174} 175 176- (void)windowWillEnterFullScreen:(NSNotification*)notification { 177 _parent->OnFullscreenTransitionStart(true); 178} 179 180- (void)windowDidEnterFullScreen:(NSNotification*)notification { 181 _parent->OnFullscreenTransitionComplete(true); 182} 183 184- (void)windowWillExitFullScreen:(NSNotification*)notification { 185 _parent->OnFullscreenTransitionStart(false); 186} 187 188- (void)windowDidExitFullScreen:(NSNotification*)notification { 189 if (base::mac::IsOS10_12()) { 190 // There is a window activation/fullscreen bug present only in macOS 10.12 191 // that might cause a security surface to appear over the wrong parent 192 // window. As much as this code appears to be a no-op, it is not; it causes 193 // AppKit to shuffle all the windows around to properly obey the 194 // relationships that they should already be obeying. 195 [[NSApp orderedWindows][0] performSelector:@selector(orderFront:) 196 withObject:self 197 afterDelay:0]; 198 } 199 200 _parent->OnFullscreenTransitionComplete(false); 201} 202 203// Allow non-resizable windows (without NSResizableWindowMask) to fill the 204// screen in fullscreen mode. This only happens when 205// -[NSWindow toggleFullscreen:] is called since non-resizable windows have no 206// fullscreen button. Without this they would only enter fullscreen at their 207// current size. 208- (NSSize)window:(NSWindow*)window 209 willUseFullScreenContentSize:(NSSize)proposedSize { 210 return proposedSize; 211} 212 213// Override to correctly position modal dialogs. 214- (NSRect)window:(NSWindow*)window 215 willPositionSheet:(NSWindow*)sheet 216 usingRect:(NSRect)defaultSheetLocation { 217 int32_t sheetPositionY = 0; 218 _parent->host()->GetSheetOffsetY(&sheetPositionY); 219 NSView* view = [window contentView]; 220 NSPoint pointInView = NSMakePoint(0, NSMaxY([view bounds]) - sheetPositionY); 221 NSPoint pointInWindow = [view convertPoint:pointInView toView:nil]; 222 223 // As per NSWindowDelegate documentation, the origin indicates the top left 224 // point of the host frame in window coordinates. The width changes the 225 // animation from vertical to trapezoid if it is smaller than the width of the 226 // dialog. The height is ignored but should be set to zero. 227 return NSMakeRect(0, pointInWindow.y, NSWidth(defaultSheetLocation), 0); 228} 229 230@end 231