1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #ifndef nsCocoaUtils_h_ 7 #define nsCocoaUtils_h_ 8 9 #import <Cocoa/Cocoa.h> 10 11 #include "nsRect.h" 12 #include "imgIContainer.h" 13 #include "nsTArray.h" 14 #include "Units.h" 15 16 // This must be the last include: 17 #include "nsObjCExceptions.h" 18 19 #include "mozilla/EventForwards.h" 20 #include "mozilla/StaticMutex.h" 21 #include "mozilla/StaticPtr.h" 22 #include "nsIWidget.h" 23 24 // Declare the backingScaleFactor method that we want to call 25 // on NSView/Window/Screen objects, if they recognize it. 26 @interface NSObject (BackingScaleFactorCategory) 27 - (CGFloat)backingScaleFactor; 28 @end 29 30 class nsIWidget; 31 32 namespace mozilla { 33 class TimeStamp; 34 namespace gfx { 35 class SourceSurface; 36 } // namespace gfx 37 namespace dom { 38 class Promise; 39 } // namespace dom 40 } // namespace mozilla 41 42 using mozilla::StaticAutoPtr; 43 using mozilla::StaticMutex; 44 45 // Used to retain a Cocoa object for the remainder of a method's execution. 46 class nsAutoRetainCocoaObject { 47 public: nsAutoRetainCocoaObject(id anObject)48 explicit nsAutoRetainCocoaObject(id anObject) { 49 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 50 mObject = [anObject retain]; 51 NS_OBJC_END_TRY_IGNORE_BLOCK; 52 } ~nsAutoRetainCocoaObject()53 ~nsAutoRetainCocoaObject() { 54 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK; 55 [mObject release]; 56 NS_OBJC_END_TRY_IGNORE_BLOCK; 57 } 58 59 private: 60 id mObject; // [STRONG] 61 }; 62 63 // Provide a local autorelease pool for the remainder of a method's execution. 64 class nsAutoreleasePool { 65 public: nsAutoreleasePool()66 nsAutoreleasePool() { mLocalPool = [[NSAutoreleasePool alloc] init]; } ~nsAutoreleasePool()67 ~nsAutoreleasePool() { [mLocalPool release]; } 68 69 private: 70 NSAutoreleasePool* mLocalPool; 71 }; 72 73 @interface NSApplication (Undocumented) 74 75 // Present in all versions of OS X from (at least) 10.2.8 through 10.5. 76 - (BOOL)_isRunningModal; 77 - (BOOL)_isRunningAppModal; 78 79 // Send an event to the current Cocoa app-modal session. Present in all 80 // versions of OS X from (at least) 10.2.8 through 10.5. 81 - (void)_modalSession:(NSModalSession)aSession sendEvent:(NSEvent*)theEvent; 82 83 @end 84 85 struct KeyBindingsCommand { 86 SEL selector; 87 id data; 88 }; 89 90 @interface NativeKeyBindingsRecorder : NSResponder { 91 @private 92 nsTArray<KeyBindingsCommand>* mCommands; 93 } 94 95 - (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands; 96 97 - (void)doCommandBySelector:(SEL)aSelector; 98 99 - (void)insertText:(id)aString; 100 101 @end // NativeKeyBindingsRecorder 102 103 #if !defined(MAC_OS_X_VERSION_10_14) || MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_14 104 typedef NSString* AVMediaType; 105 #endif 106 107 class nsCocoaUtils { 108 typedef mozilla::gfx::SourceSurface SourceSurface; 109 typedef mozilla::LayoutDeviceIntPoint LayoutDeviceIntPoint; 110 typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect; 111 typedef mozilla::dom::Promise Promise; 112 typedef StaticAutoPtr<nsTArray<RefPtr<Promise>>> PromiseArray; 113 114 public: 115 // Get the backing scale factor from an object that supports this selector 116 // (NSView/Window/Screen, on 10.7 or later), returning 1.0 if not supported GetBackingScaleFactor(id aObject)117 static CGFloat GetBackingScaleFactor(id aObject) { 118 if (HiDPIEnabled() && [aObject respondsToSelector:@selector(backingScaleFactor)]) { 119 return [aObject backingScaleFactor]; 120 } 121 return 1.0; 122 } 123 124 // Conversions between Cocoa points and device pixels, given the backing 125 // scale factor from a view/window/screen. CocoaPointsToDevPixels(CGFloat aPts,CGFloat aBackingScale)126 static int32_t CocoaPointsToDevPixels(CGFloat aPts, CGFloat aBackingScale) { 127 return NSToIntRound(aPts * aBackingScale); 128 } 129 CocoaPointsToDevPixels(const NSPoint & aPt,CGFloat aBackingScale)130 static LayoutDeviceIntPoint CocoaPointsToDevPixels(const NSPoint& aPt, CGFloat aBackingScale) { 131 return LayoutDeviceIntPoint(NSToIntRound(aPt.x * aBackingScale), 132 NSToIntRound(aPt.y * aBackingScale)); 133 } 134 CocoaPointsToDevPixelsRoundDown(const NSPoint & aPt,CGFloat aBackingScale)135 static LayoutDeviceIntPoint CocoaPointsToDevPixelsRoundDown(const NSPoint& aPt, 136 CGFloat aBackingScale) { 137 return LayoutDeviceIntPoint(NSToIntFloor(aPt.x * aBackingScale), 138 NSToIntFloor(aPt.y * aBackingScale)); 139 } 140 CocoaPointsToDevPixels(const NSRect & aRect,CGFloat aBackingScale)141 static LayoutDeviceIntRect CocoaPointsToDevPixels(const NSRect& aRect, CGFloat aBackingScale) { 142 return LayoutDeviceIntRect(NSToIntRound(aRect.origin.x * aBackingScale), 143 NSToIntRound(aRect.origin.y * aBackingScale), 144 NSToIntRound(aRect.size.width * aBackingScale), 145 NSToIntRound(aRect.size.height * aBackingScale)); 146 } 147 DevPixelsToCocoaPoints(int32_t aPixels,CGFloat aBackingScale)148 static CGFloat DevPixelsToCocoaPoints(int32_t aPixels, CGFloat aBackingScale) { 149 return (CGFloat)aPixels / aBackingScale; 150 } 151 DevPixelsToCocoaPoints(const mozilla::LayoutDeviceIntPoint & aPt,CGFloat aBackingScale)152 static NSPoint DevPixelsToCocoaPoints(const mozilla::LayoutDeviceIntPoint& aPt, 153 CGFloat aBackingScale) { 154 return NSMakePoint((CGFloat)aPt.x / aBackingScale, (CGFloat)aPt.y / aBackingScale); 155 } 156 157 // Implements an NSPoint equivalent of -[NSWindow convertRectFromScreen:]. ConvertPointFromScreen(NSWindow * aWindow,const NSPoint & aPt)158 static NSPoint ConvertPointFromScreen(NSWindow* aWindow, const NSPoint& aPt) { 159 return [aWindow convertRectFromScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin; 160 } 161 162 // Implements an NSPoint equivalent of -[NSWindow convertRectToScreen:]. ConvertPointToScreen(NSWindow * aWindow,const NSPoint & aPt)163 static NSPoint ConvertPointToScreen(NSWindow* aWindow, const NSPoint& aPt) { 164 return [aWindow convertRectToScreen:NSMakeRect(aPt.x, aPt.y, 0, 0)].origin; 165 } 166 DevPixelsToCocoaPoints(const LayoutDeviceIntRect & aRect,CGFloat aBackingScale)167 static NSRect DevPixelsToCocoaPoints(const LayoutDeviceIntRect& aRect, CGFloat aBackingScale) { 168 return NSMakeRect((CGFloat)aRect.X() / aBackingScale, (CGFloat)aRect.Y() / aBackingScale, 169 (CGFloat)aRect.Width() / aBackingScale, 170 (CGFloat)aRect.Height() / aBackingScale); 171 } 172 173 // Returns the given y coordinate, which must be in screen coordinates, 174 // flipped from Gecko to Cocoa or Cocoa to Gecko. 175 static float FlippedScreenY(float y); 176 177 // The following functions come in "DevPix" variants that work with 178 // backing-store (device pixel) coordinates, as well as the original 179 // versions that expect coordinates in Cocoa points/CSS pixels. 180 // The difference becomes important in HiDPI display modes, where Cocoa 181 // points and backing-store pixels are no longer 1:1. 182 183 // Gecko rects (nsRect) contain an origin (x,y) in a coordinate 184 // system with (0,0) in the top-left of the primary screen. Cocoa rects 185 // (NSRect) contain an origin (x,y) in a coordinate system with (0,0) 186 // in the bottom-left of the primary screen. Both nsRect and NSRect 187 // contain width/height info, with no difference in their use. 188 // This function does no scaling, so the Gecko coordinates are 189 // expected to be desktop pixels, which are equal to Cocoa points 190 // (by definition). 191 static NSRect GeckoRectToCocoaRect(const mozilla::DesktopIntRect& geckoRect); 192 static NSPoint GeckoPointToCocoaPoint(const mozilla::DesktopPoint& aPoint); 193 194 // Converts aGeckoRect in dev pixels to points in Cocoa coordinates 195 static NSRect GeckoRectToCocoaRectDevPix(const mozilla::LayoutDeviceIntRect& aGeckoRect, 196 CGFloat aBackingScale); 197 198 // See explanation for geckoRectToCocoaRect, guess what this does... 199 static mozilla::DesktopIntRect CocoaRectToGeckoRect(const NSRect& cocoaRect); 200 201 static mozilla::LayoutDeviceIntRect CocoaRectToGeckoRectDevPix(const NSRect& aCocoaRect, 202 CGFloat aBackingScale); 203 204 // Gives the location for the event in screen coordinates. Do not call this 205 // unless the window the event was originally targeted at is still alive! 206 // anEvent may be nil -- in that case the current mouse location is returned. 207 static NSPoint ScreenLocationForEvent(NSEvent* anEvent); 208 209 // Determines if an event happened over a window, whether or not the event 210 // is for the window. Does not take window z-order into account. 211 static BOOL IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow); 212 213 // Events are set up so that their coordinates refer to the window to which they 214 // were originally sent. If we reroute the event somewhere else, we'll have 215 // to get the window coordinates this way. Do not call this unless the window 216 // the event was originally targeted at is still alive! 217 static NSPoint EventLocationForWindow(NSEvent* anEvent, NSWindow* aWindow); 218 219 static BOOL IsMomentumScrollEvent(NSEvent* aEvent); 220 static BOOL EventHasPhaseInformation(NSEvent* aEvent); 221 222 // Hides the Menu bar and the Dock. Multiple hide/show requests can be nested. 223 static void HideOSChromeOnScreen(bool aShouldHide); 224 225 static nsIWidget* GetHiddenWindowWidget(); 226 227 static void PrepareForNativeAppModalDialog(); 228 static void CleanUpAfterNativeAppModalDialog(); 229 230 // 3 utility functions to go from a frame of imgIContainer to CGImage and then to NSImage 231 // Convert imgIContainer -> CGImageRef, caller owns result 232 233 /** Creates a <code>CGImageRef</code> from a frame contained in an <code>imgIContainer</code>. 234 Copies the pixel data from the indicated frame of the <code>imgIContainer</code> into a new 235 <code>CGImageRef</code>. The caller owns the <code>CGImageRef</code>. 236 @param aFrame the frame to convert 237 @param aResult the resulting CGImageRef 238 @param aIsEntirelyBlack an outparam that, if non-null, will be set to a 239 bool that indicates whether the RGB values on all 240 pixels are zero 241 @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise 242 */ 243 static nsresult CreateCGImageFromSurface(SourceSurface* aSurface, CGImageRef* aResult, 244 bool* aIsEntirelyBlack = nullptr); 245 246 /** Creates a Cocoa <code>NSImage</code> from a <code>CGImageRef</code>. 247 Copies the pixel data from the <code>CGImageRef</code> into a new <code>NSImage</code>. 248 The caller owns the <code>NSImage</code>. 249 @param aInputImage the image to convert 250 @param aResult the resulting NSImage 251 @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise 252 */ 253 static nsresult CreateNSImageFromCGImage(CGImageRef aInputImage, NSImage** aResult); 254 255 /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>. 256 Combines the two methods above. The caller owns the <code>NSImage</code>. 257 @param aImage the image to extract a frame from 258 @param aWhichFrame the frame to extract (see imgIContainer FRAME_*) 259 @param aComputedStyle the ComputedStyle of the element that the image is for, to support SVG 260 context paint properties, can be null 261 @param aResult the resulting NSImage 262 @param scaleFactor the desired scale factor of the NSImage (2 for a retina display) 263 @param aIsEntirelyBlack an outparam that, if non-null, will be set to a 264 bool that indicates whether the RGB values on all 265 pixels are zero 266 @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise 267 */ 268 static nsresult CreateNSImageFromImageContainer(imgIContainer* aImage, uint32_t aWhichFrame, 269 const mozilla::ComputedStyle* aComputedStyle, 270 NSImage** aResult, CGFloat scaleFactor, 271 bool* aIsEntirelyBlack = nullptr); 272 273 /** Creates a Cocoa <code>NSImage</code> from a frame of an <code>imgIContainer</code>. 274 The new <code>NSImage</code> will have both a regular and HiDPI representation. 275 The caller owns the <code>NSImage</code>. 276 @param aImage the image to extract a frame from 277 @param aWhichFrame the frame to extract (see imgIContainer FRAME_*) 278 @param aComputedStyle the ComputedStyle of the element that the image is for, to support SVG 279 context paint properties, can be null 280 @param aResult the resulting NSImage 281 @param aIsEntirelyBlack an outparam that, if non-null, will be set to a 282 bool that indicates whether the RGB values on all 283 pixels are zero 284 @return NS_OK if the conversion worked, NS_ERROR_FAILURE otherwise 285 */ 286 static nsresult CreateDualRepresentationNSImageFromImageContainer( 287 imgIContainer* aImage, uint32_t aWhichFrame, const mozilla::ComputedStyle* aComputedStyle, 288 NSImage** aResult, bool* aIsEntirelyBlack = nullptr); 289 290 /** 291 * Returns nsAString for aSrc. 292 */ 293 static void GetStringForNSString(const NSString* aSrc, nsAString& aDist); 294 295 /** 296 * Makes NSString instance for aString. 297 */ 298 static NSString* ToNSString(const nsAString& aString); 299 300 /** 301 * Makes NSString instance for aCString. 302 */ 303 static NSString* ToNSString(const nsACString& aCString); 304 305 /** 306 * Returns NSRect for aGeckoRect. 307 * Just copies values between the two types; it does no coordinate-system 308 * conversion, so both rects must have the same coordinate origin/direction. 309 */ 310 static void GeckoRectToNSRect(const nsIntRect& aGeckoRect, NSRect& aOutCocoaRect); 311 312 /** 313 * Returns Gecko rect for aCocoaRect. 314 * Just copies values between the two types; it does no coordinate-system 315 * conversion, so both rects must have the same coordinate origin/direction. 316 */ 317 static void NSRectToGeckoRect(const NSRect& aCocoaRect, nsIntRect& aOutGeckoRect); 318 319 /** 320 * Makes NSEvent instance for aEventTytpe and aEvent. 321 */ 322 static NSEvent* MakeNewCocoaEventWithType(NSEventType aEventType, NSEvent* aEvent); 323 324 /** 325 * Makes a cocoa event from a widget keyboard event. 326 */ 327 static NSEvent* MakeNewCococaEventFromWidgetEvent(const mozilla::WidgetKeyboardEvent& aKeyEvent, 328 NSInteger aWindowNumber, 329 NSGraphicsContext* aContext); 330 331 /** 332 * Initializes WidgetInputEvent for aNativeEvent or aModifiers. 333 */ 334 static void InitInputEvent(mozilla::WidgetInputEvent& aInputEvent, NSEvent* aNativeEvent); 335 336 /** 337 * Converts the native modifiers from aNativeEvent into WidgetMouseEvent 338 * Modifiers. aNativeEvent can be null. 339 */ 340 static mozilla::Modifiers ModifiersForEvent(NSEvent* aNativeEvent); 341 342 /** 343 * ConvertToCarbonModifier() returns carbon modifier flags for the cocoa 344 * modifier flags. 345 * NOTE: The result never includes right*Key. 346 */ 347 static UInt32 ConvertToCarbonModifier(NSUInteger aCocoaModifier); 348 349 /** 350 * Whether to support HiDPI rendering. For testing purposes, to be removed 351 * once we're comfortable with the HiDPI behavior. 352 */ 353 static bool HiDPIEnabled(); 354 355 /** 356 * Keys can optionally be bound by system or user key bindings to one or more 357 * commands based on selectors. This collects any such commands in the 358 * provided array. 359 */ 360 static void GetCommandsFromKeyEvent(NSEvent* aEvent, nsTArray<KeyBindingsCommand>& aCommands); 361 362 /** 363 * Converts the string name of a Gecko key (like "VK_HOME") to the 364 * corresponding Cocoa Unicode character. 365 */ 366 static uint32_t ConvertGeckoNameToMacCharCode(const nsAString& aKeyCodeName); 367 368 /** 369 * Converts a Gecko key code (like NS_VK_HOME) to the corresponding Cocoa 370 * Unicode character. 371 */ 372 static uint32_t ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode); 373 374 /** 375 * Converts Gecko native modifier flags for `nsIWidget::SynthesizeNative*()` 376 * to native modifier flags of macOS. 377 */ 378 static NSEventModifierFlags ConvertWidgetModifiersToMacModifierFlags( 379 nsIWidget::Modifiers aNativeModifiers); 380 381 /** 382 * Get the mouse button, which depends on the event's type and buttonNumber. 383 * Returns MouseButton::ePrimary for non-mouse events. 384 */ 385 static mozilla::MouseButton ButtonForEvent(NSEvent* aEvent); 386 387 /** 388 * Convert string with font attribute to NSMutableAttributedString 389 */ 390 static NSMutableAttributedString* GetNSMutableAttributedString( 391 const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRanges, 392 const bool aIsVertical, const CGFloat aBackingScaleFactor); 393 394 /** 395 * Compute TimeStamp from an event's timestamp. 396 * If aEventTime is 0, this returns current timestamp. 397 */ 398 static mozilla::TimeStamp GetEventTimeStamp(NSTimeInterval aEventTime); 399 400 /** 401 * Check whether double clicking on the titlebar should cause the window to 402 * zoom (maximize). 403 */ 404 static bool ShouldZoomOnTitlebarDoubleClick(); 405 406 /** 407 * Check whether double clicking on the titlebar should cause the window to 408 * minimize. 409 */ 410 static bool ShouldMinimizeOnTitlebarDoubleClick(); 411 412 /** 413 * Get the current video capture permission status. 414 * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions. 415 */ 416 static nsresult GetVideoCapturePermissionState(uint16_t& aPermissionState); 417 418 /** 419 * Get the current audio capture permission status. 420 * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions. 421 */ 422 static nsresult GetAudioCapturePermissionState(uint16_t& aPermissionState); 423 424 /** 425 * Get the current screen capture permission status. 426 * Returns NS_ERROR_NOT_IMPLEMENTED on 10.14 and earlier macOS versions. 427 */ 428 static nsresult GetScreenCapturePermissionState(uint16_t& aPermissionState); 429 430 /** 431 * Request video capture permission from the OS. Caller must be running 432 * on the main thread and the promise will be resolved on the main thread. 433 * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions. 434 */ 435 static nsresult RequestVideoCapturePermission(RefPtr<Promise>& aPromise); 436 437 /** 438 * Request audio capture permission from the OS. Caller must be running 439 * on the main thread and the promise will be resolved on the main thread. 440 * Returns NS_ERROR_NOT_IMPLEMENTED on 10.13 and earlier macOS versions. 441 */ 442 static nsresult RequestAudioCapturePermission(RefPtr<Promise>& aPromise); 443 444 /** 445 * Request screen capture permission from the OS using an unreliable method. 446 */ 447 static nsresult MaybeRequestScreenCapturePermission(); 448 449 private: 450 /** 451 * Completion handlers used as an argument to the macOS API to 452 * request media capture permission. These are called asynchronously 453 * on an arbitrary dispatch queue. 454 */ 455 static void (^AudioCompletionHandler)(BOOL); 456 static void (^VideoCompletionHandler)(BOOL); 457 458 /** 459 * Called from the audio and video completion handlers in order to 460 * dispatch the handling back to the main thread. 461 */ 462 static void ResolveAudioCapturePromises(bool aGranted); 463 static void ResolveVideoCapturePromises(bool aGranted); 464 465 /** 466 * Main implementation for Request{Audio,Video}CapturePermission. 467 * @param aType the AVMediaType to request capture permission for 468 * @param aPromise the Promise to resolve when capture permission 469 * is either allowed or denied 470 * @param aPromiseList the array of promises to save |aPromise| in 471 * @param aHandler the block function (either ResolveAudioCapturePromises 472 * or ResolveVideoCapturePromises) to be used as 473 * the requestAccessForMediaType callback. 474 */ 475 static nsresult RequestCapturePermission(NSString* aType, RefPtr<Promise>& aPromise, 476 PromiseArray& aPromiseList, 477 void (^aHandler)(BOOL granted)); 478 /** 479 * Resolves the pending promises that are waiting for a response 480 * to a request video or audio capture permission. 481 */ 482 static void ResolveMediaCapturePromises(bool aGranted, PromiseArray& aPromiseList); 483 484 /** 485 * Array of promises waiting to be resolved due to a video capture request. 486 */ 487 static PromiseArray sVideoCapturePromises; 488 489 /** 490 * Array of promises waiting to be resolved due to an audio capture request. 491 */ 492 static PromiseArray sAudioCapturePromises; 493 494 /** 495 * Lock protecting |sVideoCapturePromises| and |sAudioCapturePromises|. 496 */ 497 static StaticMutex sMediaCaptureMutex; 498 }; 499 500 #endif // nsCocoaUtils_h_ 501