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