Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / cocoa / nsCocoaUtils.mm
blob1ed109e3d20539c70c3f219dc48d170fef640eab
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/. */
6 #import <AVFoundation/AVFoundation.h>
8 #include <cmath>
10 #include "AppleUtils.h"
11 #include "gfx2DGlue.h"
12 #include "gfxContext.h"
13 #include "gfxPlatform.h"
14 #include "gfxUtils.h"
15 #include "ImageRegion.h"
16 #include "nsClipboard.h"
17 #include "nsCocoaUtils.h"
18 #include "nsChildView.h"
19 #include "nsMenuBarX.h"
20 #include "nsCocoaWindow.h"
21 #include "nsCOMPtr.h"
22 #include "nsIInterfaceRequestorUtils.h"
23 #include "nsIAppShellService.h"
24 #include "nsIOSPermissionRequest.h"
25 #include "nsIRunnable.h"
26 #include "nsIAppWindow.h"
27 #include "nsIBaseWindow.h"
28 #include "nsITransferable.h"
29 #include "nsMenuUtilsX.h"
30 #include "nsNetUtil.h"
31 #include "nsPrimitiveHelpers.h"
32 #include "nsToolkit.h"
33 #include "nsCRT.h"
34 #include "mozilla/ClearOnShutdown.h"
35 #include "mozilla/Logging.h"
36 #include "mozilla/MiscEvents.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/TextEvents.h"
39 #include "mozilla/StaticMutex.h"
40 #include "mozilla/StaticPrefs_media.h"
41 #include "mozilla/SVGImageContext.h"
42 #include "mozilla/dom/Promise.h"
43 #include "mozilla/gfx/2D.h"
45 using namespace mozilla;
46 using namespace mozilla::widget;
48 using mozilla::dom::Promise;
49 using mozilla::gfx::DataSourceSurface;
50 using mozilla::gfx::DrawTarget;
51 using mozilla::gfx::IntPoint;
52 using mozilla::gfx::IntRect;
53 using mozilla::gfx::IntSize;
54 using mozilla::gfx::SamplingFilter;
55 using mozilla::gfx::SourceSurface;
56 using mozilla::gfx::SurfaceFormat;
57 using mozilla::image::ImageRegion;
59 LazyLogModule gCocoaUtilsLog("nsCocoaUtils");
60 #undef LOG
61 #define LOG(...) MOZ_LOG(gCocoaUtilsLog, LogLevel::Debug, (__VA_ARGS__))
64  * For each audio and video capture request, we hold an owning reference
65  * to a promise to be resolved when the request's async callback is invoked.
66  * sVideoCapturePromises and sAudioCapturePromises are arrays of video and
67  * audio promises waiting for to be resolved. Each array is protected by a
68  * mutex.
69  */
70 nsCocoaUtils::PromiseArray nsCocoaUtils::sVideoCapturePromises;
71 nsCocoaUtils::PromiseArray nsCocoaUtils::sAudioCapturePromises;
72 StaticMutex nsCocoaUtils::sMediaCaptureMutex;
74 /**
75  * Pasteboard types
76  */
77 NSString* const kPublicUrlPboardType = @"public.url";
78 NSString* const kPublicUrlNamePboardType = @"public.url-name";
79 NSString* const kPasteboardConcealedType = @"org.nspasteboard.ConcealedType";
80 NSString* const kUrlsWithTitlesPboardType = @"WebURLsWithTitlesPboardType";
81 NSString* const kMozWildcardPboardType = @"org.mozilla.MozillaWildcard";
82 NSString* const kMozCustomTypesPboardType = @"org.mozilla.custom-clipdata";
83 NSString* const kMozFileUrlsPboardType = @"org.mozilla.file-urls";
85 @implementation UTIHelper
87 + (NSString*)stringFromPboardType:(NSString*)aType {
88   if ([aType isEqualToString:kMozWildcardPboardType] ||
89       [aType isEqualToString:kMozCustomTypesPboardType] ||
90       [aType isEqualToString:kPasteboardConcealedType] ||
91       [aType isEqualToString:kPublicUrlPboardType] ||
92       [aType isEqualToString:kPublicUrlNamePboardType] ||
93       [aType isEqualToString:kMozFileUrlsPboardType] ||
94       [aType isEqualToString:(NSString*)kPasteboardTypeFileURLPromise] ||
95       [aType isEqualToString:(NSString*)kPasteboardTypeFilePromiseContent] ||
96       [aType isEqualToString:(NSString*)kUTTypeFileURL] ||
97       [aType isEqualToString:NSStringPboardType] ||
98       [aType isEqualToString:NSPasteboardTypeString] ||
99       [aType isEqualToString:NSPasteboardTypeHTML] ||
100       [aType isEqualToString:NSPasteboardTypeRTF] ||
101       [aType isEqualToString:NSPasteboardTypeTIFF] ||
102       [aType isEqualToString:NSPasteboardTypePNG]) {
103     return [NSString stringWithString:aType];
104   }
105   NSString* dynamicType = (NSString*)UTTypeCreatePreferredIdentifierForTag(
106       kUTTagClassNSPboardType, (CFStringRef)aType, kUTTypeData);
107   NSString* result = [NSString stringWithString:dynamicType];
108   [dynamicType release];
109   return result;
112 @end  // UTIHelper
114 static float MenuBarScreenHeight() {
115   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
117   NSArray* allScreens = [NSScreen screens];
118   if ([allScreens count]) {
119     return [[allScreens objectAtIndex:0] frame].size.height;
120   }
122   return 0.0;
124   NS_OBJC_END_TRY_BLOCK_RETURN(0.0);
127 float nsCocoaUtils::FlippedScreenY(float y) {
128   return MenuBarScreenHeight() - y;
131 NSRect nsCocoaUtils::GeckoRectToCocoaRect(const DesktopIntRect& geckoRect) {
132   // We only need to change the Y coordinate by starting with the primary screen
133   // height and subtracting the gecko Y coordinate of the bottom of the rect.
134   return NSMakeRect(geckoRect.x, MenuBarScreenHeight() - geckoRect.YMost(),
135                     geckoRect.width, geckoRect.height);
138 NSPoint nsCocoaUtils::GeckoPointToCocoaPoint(
139     const mozilla::DesktopPoint& aPoint) {
140   return NSMakePoint(aPoint.x, MenuBarScreenHeight() - aPoint.y);
143 NSRect nsCocoaUtils::GeckoRectToCocoaRectDevPix(
144     const LayoutDeviceIntRect& aGeckoRect, CGFloat aBackingScale) {
145   return NSMakeRect(aGeckoRect.x / aBackingScale,
146                     MenuBarScreenHeight() - aGeckoRect.YMost() / aBackingScale,
147                     aGeckoRect.width / aBackingScale,
148                     aGeckoRect.height / aBackingScale);
151 DesktopIntRect nsCocoaUtils::CocoaRectToGeckoRect(const NSRect& cocoaRect) {
152   // We only need to change the Y coordinate by starting with the primary screen
153   // height and subtracting both the cocoa y origin and the height of the
154   // cocoa rect.
155   DesktopIntRect rect;
156   rect.x = NSToIntRound(cocoaRect.origin.x);
157   rect.y =
158       NSToIntRound(FlippedScreenY(cocoaRect.origin.y + cocoaRect.size.height));
159   rect.width = NSToIntRound(cocoaRect.origin.x + cocoaRect.size.width) - rect.x;
160   rect.height = NSToIntRound(FlippedScreenY(cocoaRect.origin.y)) - rect.y;
161   return rect;
164 LayoutDeviceIntRect nsCocoaUtils::CocoaRectToGeckoRectDevPix(
165     const NSRect& aCocoaRect, CGFloat aBackingScale) {
166   LayoutDeviceIntRect rect;
167   rect.x = NSToIntRound(aCocoaRect.origin.x * aBackingScale);
168   rect.y = NSToIntRound(
169       FlippedScreenY(aCocoaRect.origin.y + aCocoaRect.size.height) *
170       aBackingScale);
171   rect.width = NSToIntRound((aCocoaRect.origin.x + aCocoaRect.size.width) *
172                             aBackingScale) -
173                rect.x;
174   rect.height =
175       NSToIntRound(FlippedScreenY(aCocoaRect.origin.y) * aBackingScale) -
176       rect.y;
177   return rect;
180 NSPoint nsCocoaUtils::ScreenLocationForEvent(NSEvent* anEvent) {
181   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
183   // Don't trust mouse locations of mouse move events, see bug 443178.
184   if (!anEvent || [anEvent type] == NSEventTypeMouseMoved)
185     return [NSEvent mouseLocation];
187   // Pin momentum scroll events to the location of the last user-controlled
188   // scroll event.
189   if (IsMomentumScrollEvent(anEvent))
190     return ChildViewMouseTracker::sLastScrollEventScreenLocation;
192   return nsCocoaUtils::ConvertPointToScreen([anEvent window],
193                                             [anEvent locationInWindow]);
195   NS_OBJC_END_TRY_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
198 BOOL nsCocoaUtils::IsEventOverWindow(NSEvent* anEvent, NSWindow* aWindow) {
199   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
201   return NSPointInRect(ScreenLocationForEvent(anEvent), [aWindow frame]);
203   NS_OBJC_END_TRY_BLOCK_RETURN(NO);
206 NSPoint nsCocoaUtils::EventLocationForWindow(NSEvent* anEvent,
207                                              NSWindow* aWindow) {
208   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
210   return nsCocoaUtils::ConvertPointFromScreen(aWindow,
211                                               ScreenLocationForEvent(anEvent));
213   NS_OBJC_END_TRY_BLOCK_RETURN(NSMakePoint(0.0, 0.0));
216 BOOL nsCocoaUtils::IsMomentumScrollEvent(NSEvent* aEvent) {
217   return [aEvent type] == NSEventTypeScrollWheel &&
218          [aEvent momentumPhase] != NSEventPhaseNone;
221 BOOL nsCocoaUtils::EventHasPhaseInformation(NSEvent* aEvent) {
222   return [aEvent phase] != NSEventPhaseNone ||
223          [aEvent momentumPhase] != NSEventPhaseNone;
226 void nsCocoaUtils::HideOSChromeOnScreen(bool aShouldHide) {
227   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
229   // Keep track of how many hiding requests have been made, so that they can
230   // be nested.
231   static int sHiddenCount = 0;
233   sHiddenCount += aShouldHide ? 1 : -1;
234   NS_ASSERTION(sHiddenCount >= 0, "Unbalanced HideMenuAndDockForWindow calls");
236   NSApplicationPresentationOptions options =
237       sHiddenCount <= 0 ? NSApplicationPresentationDefault
238                         : NSApplicationPresentationHideDock |
239                               NSApplicationPresentationHideMenuBar;
240   [NSApp setPresentationOptions:options];
242   NS_OBJC_END_TRY_IGNORE_BLOCK;
245 #define NS_APPSHELLSERVICE_CONTRACTID "@mozilla.org/appshell/appShellService;1"
246 nsIWidget* nsCocoaUtils::GetHiddenWindowWidget() {
247   nsCOMPtr<nsIAppShellService> appShell(
248       do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
249   if (!appShell) {
250     NS_WARNING(
251         "Couldn't get AppShellService in order to get hidden window ref");
252     return nullptr;
253   }
255   nsCOMPtr<nsIAppWindow> hiddenWindow;
256   appShell->GetHiddenWindow(getter_AddRefs(hiddenWindow));
257   if (!hiddenWindow) {
258     // Don't warn, this happens during shutdown, bug 358607.
259     return nullptr;
260   }
262   nsCOMPtr<nsIBaseWindow> baseHiddenWindow;
263   baseHiddenWindow = do_GetInterface(hiddenWindow);
264   if (!baseHiddenWindow) {
265     NS_WARNING("Couldn't get nsIBaseWindow from hidden window (nsIAppWindow)");
266     return nullptr;
267   }
269   nsCOMPtr<nsIWidget> hiddenWindowWidget;
270   if (NS_FAILED(baseHiddenWindow->GetMainWidget(
271           getter_AddRefs(hiddenWindowWidget)))) {
272     NS_WARNING("Couldn't get nsIWidget from hidden window (nsIBaseWindow)");
273     return nullptr;
274   }
276   return hiddenWindowWidget;
279 BOOL nsCocoaUtils::WasLaunchedAtLogin() {
280   ProcessSerialNumber processSerialNumber = {0, kCurrentProcess};
281   ProcessInfoRec processInfoRec = {};
282   processInfoRec.processInfoLength = sizeof(processInfoRec);
284   // There is currently no replacement for ::GetProcessInformation, which has
285   // been deprecated since macOS 10.9.
286   if (::GetProcessInformation(&processSerialNumber, &processInfoRec) == noErr) {
287     ProcessInfoRec parentProcessInfo = {};
288     parentProcessInfo.processInfoLength = sizeof(parentProcessInfo);
289     if (::GetProcessInformation(&processInfoRec.processLauncher,
290                                 &parentProcessInfo) == noErr) {
291       return parentProcessInfo.processSignature == 'lgnw';
292     }
293   }
294   return NO;
297 BOOL nsCocoaUtils::ShouldRestoreStateDueToLaunchAtLogin() {
298   // Check if we were launched by macOS as a result of having
299   // "Reopen windows..." selected during a restart.
300   if (!WasLaunchedAtLogin()) {
301     return NO;
302   }
304   CFStringRef lgnwPlistName = CFSTR("com.apple.loginwindow");
305   CFStringRef saveStateKey = CFSTR("TALLogoutSavesState");
306   CFPropertyListRef lgnwPlist = (CFPropertyListRef)(::CFPreferencesCopyAppValue(
307       saveStateKey, lgnwPlistName));
308   // The .plist doesn't exist unless the user changed the "Reopen windows..."
309   // preference. If it doesn't exist, restore by default (as this is the macOS
310   // default).
311   // https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/CustomLogin.html
312   if (!lgnwPlist) {
313     return YES;
314   }
316   if (CFBooleanRef shouldRestoreState = static_cast<CFBooleanRef>(lgnwPlist)) {
317     return ::CFBooleanGetValue(shouldRestoreState);
318   }
320   return NO;
323 void nsCocoaUtils::PrepareForNativeAppModalDialog() {
324   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
326   // Don't do anything if this is embedding. We'll assume that if there is no
327   // hidden window we shouldn't do anything, and that should cover the embedding
328   // case.
329   nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
330   if (!hiddenWindowMenuBar) return;
332   // First put up the hidden window menu bar so that app menu event handling is
333   // correct.
334   hiddenWindowMenuBar->Paint();
336   NSMenu* mainMenu = [NSApp mainMenu];
337   NS_ASSERTION(
338       [mainMenu numberOfItems] > 0,
339       "Main menu does not have any items, something is terribly wrong!");
341   // Create new menu bar for use with modal dialog
342   NSMenu* newMenuBar = [[GeckoNSMenu alloc] initWithTitle:@""];
344   // Swap in our app menu. Note that the event target is whatever window is up
345   // when the app modal dialog goes up.
346   NSMenuItem* firstMenuItem = [[mainMenu itemAtIndex:0] retain];
347   [mainMenu removeItemAtIndex:0];
348   [newMenuBar insertItem:firstMenuItem atIndex:0];
349   [firstMenuItem release];
351   // Add standard edit menu
352   [newMenuBar addItem:nsMenuUtilsX::GetStandardEditMenuItem()];
354   // Show the new menu bar
355   [NSApp setMainMenu:newMenuBar];
356   [newMenuBar release];
358   NS_OBJC_END_TRY_IGNORE_BLOCK;
361 void nsCocoaUtils::CleanUpAfterNativeAppModalDialog() {
362   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
364   // Don't do anything if this is embedding. We'll assume that if there is no
365   // hidden window we shouldn't do anything, and that should cover the embedding
366   // case.
367   nsMenuBarX* hiddenWindowMenuBar = nsMenuUtilsX::GetHiddenWindowMenuBar();
368   if (!hiddenWindowMenuBar) return;
370   NSWindow* mainWindow = [NSApp mainWindow];
371   if (!mainWindow) {
372     // We do an async paint in order to prevent crashes when macOS is actively
373     // enumerating the menu items in `NSApp.mainMenu`.
374     hiddenWindowMenuBar->PaintAsync();
375   } else {
376     [WindowDelegate paintMenubarForWindow:mainWindow];
377   }
379   NS_OBJC_END_TRY_IGNORE_BLOCK;
382 static void data_ss_release_callback(void* aDataSourceSurface, const void* data,
383                                      size_t size) {
384   if (aDataSourceSurface) {
385     static_cast<DataSourceSurface*>(aDataSourceSurface)->Unmap();
386     static_cast<DataSourceSurface*>(aDataSourceSurface)->Release();
387   }
390 // This function assumes little endian byte order.
391 static bool ComputeIsEntirelyBlack(const DataSourceSurface::MappedSurface& aMap,
392                                    const IntSize& aSize) {
393   for (int32_t y = 0; y < aSize.height; y++) {
394     size_t rowStart = y * aMap.mStride;
395     for (int32_t x = 0; x < aSize.width; x++) {
396       size_t index = rowStart + x * 4;
397       if (aMap.mData[index + 0] != 0 || aMap.mData[index + 1] != 0 ||
398           aMap.mData[index + 2] != 0) {
399         return false;
400       }
401     }
402   }
403   return true;
406 nsresult nsCocoaUtils::CreateCGImageFromSurface(SourceSurface* aSurface,
407                                                 CGImageRef* aResult,
408                                                 bool* aIsEntirelyBlack) {
409   RefPtr<DataSourceSurface> dataSurface;
411   if (aSurface->GetFormat() == SurfaceFormat::B8G8R8A8) {
412     dataSurface = aSurface->GetDataSurface();
413   } else {
414     // CGImageCreate only supports 16- and 32-bit bit-depth
415     // Convert format to SurfaceFormat::B8G8R8A8
416     dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(
417         aSurface, SurfaceFormat::B8G8R8A8);
418   }
420   NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
422   int32_t width = dataSurface->GetSize().width;
423   int32_t height = dataSurface->GetSize().height;
424   if (height < 1 || width < 1) {
425     return NS_ERROR_FAILURE;
426   }
428   DataSourceSurface::MappedSurface map;
429   if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
430     return NS_ERROR_FAILURE;
431   }
432   // The Unmap() call happens in data_ss_release_callback
434   if (aIsEntirelyBlack) {
435     *aIsEntirelyBlack = ComputeIsEntirelyBlack(map, dataSurface->GetSize());
436   }
438   // Create a CGImageRef with the bits from the image, taking into account
439   // the alpha ordering and endianness of the machine so we don't have to
440   // touch the bits ourselves.
441   CGDataProviderRef dataProvider = ::CGDataProviderCreateWithData(
442       dataSurface.forget().take(), map.mData, map.mStride * height,
443       data_ss_release_callback);
444   CGColorSpaceRef colorSpace =
445       ::CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
446   *aResult = ::CGImageCreate(
447       width, height, 8, 32, map.mStride, colorSpace,
448       kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst, dataProvider,
449       NULL, 0, kCGRenderingIntentDefault);
450   ::CGColorSpaceRelease(colorSpace);
451   ::CGDataProviderRelease(dataProvider);
452   return *aResult ? NS_OK : NS_ERROR_FAILURE;
455 nsresult nsCocoaUtils::CreateNSImageFromCGImage(CGImageRef aInputImage,
456                                                 NSImage** aResult) {
457   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
459   // Be very careful when creating the NSImage that the backing NSImageRep is
460   // exactly 1:1 with the input image. On a retina display, both [NSImage
461   // lockFocus] and [NSImage initWithCGImage:size:] will create an image with a
462   // 2x backing NSImageRep. This prevents NSCursor from recognizing a retina
463   // cursor, which only occurs if pixelsWide and pixelsHigh are exactly 2x the
464   // size of the NSImage.
465   //
466   // For example, if a 32x32 SVG cursor is rendered on a retina display, then
467   // aInputImage will be 64x64. The resulting NSImage will be scaled back down
468   // to 32x32 so it stays the correct size on the screen by changing its size
469   // (resizing a NSImage only scales the image and doesn't resample the data).
470   // If aInputImage is converted using [NSImage initWithCGImage:size:] then the
471   // bitmap will be 128x128 and NSCursor won't recognize a retina cursor, since
472   // it will expect a 64x64 bitmap.
474   int32_t width = ::CGImageGetWidth(aInputImage);
475   int32_t height = ::CGImageGetHeight(aInputImage);
476   NSRect imageRect = ::NSMakeRect(0.0, 0.0, width, height);
478   NSBitmapImageRep* offscreenRep = [[NSBitmapImageRep alloc]
479       initWithBitmapDataPlanes:NULL
480                     pixelsWide:width
481                     pixelsHigh:height
482                  bitsPerSample:8
483                samplesPerPixel:4
484                       hasAlpha:YES
485                       isPlanar:NO
486                 colorSpaceName:NSDeviceRGBColorSpace
487                   bitmapFormat:NSBitmapFormatAlphaFirst
488                    bytesPerRow:0
489                   bitsPerPixel:0];
491   NSGraphicsContext* context =
492       [NSGraphicsContext graphicsContextWithBitmapImageRep:offscreenRep];
493   [NSGraphicsContext saveGraphicsState];
494   [NSGraphicsContext setCurrentContext:context];
496   // Get the Quartz context and draw.
497   CGContextRef imageContext = [[NSGraphicsContext currentContext] CGContext];
498   ::CGContextDrawImage(imageContext, *(CGRect*)&imageRect, aInputImage);
500   [NSGraphicsContext restoreGraphicsState];
502   *aResult = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
503   [*aResult addRepresentation:offscreenRep];
504   [offscreenRep release];
505   return NS_OK;
507   NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
510 nsresult nsCocoaUtils::CreateNSImageFromImageContainer(
511     imgIContainer* aImage, uint32_t aWhichFrame,
512     const SVGImageContext* aSVGContext, const NSSize& aPreferredSize,
513     NSImage** aResult, CGFloat scaleFactor, bool* aIsEntirelyBlack) {
514   RefPtr<SourceSurface> surface;
515   int32_t width = 0;
516   int32_t height = 0;
517   {
518     const bool gotWidth = NS_SUCCEEDED(aImage->GetWidth(&width));
519     const bool gotHeight = NS_SUCCEEDED(aImage->GetHeight(&height));
520     if (auto ratio = aImage->GetIntrinsicRatio()) {
521       if (gotWidth != gotHeight) {
522         if (gotWidth) {
523           height = ratio.Inverted().ApplyTo(width);
524         } else {
525           width = ratio.ApplyTo(height);
526         }
527       } else if (!gotWidth) {
528         height = std::ceil(aPreferredSize.height);
529         width = ratio.ApplyTo(height);
530       }
531     }
532   }
534   // Render a vector image at the correct resolution on a retina display
535   if (aImage->GetType() == imgIContainer::TYPE_VECTOR) {
536     IntSize scaledSize =
537         IntSize::Ceil(width * scaleFactor, height * scaleFactor);
539     RefPtr<DrawTarget> drawTarget =
540         gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
541             scaledSize, SurfaceFormat::B8G8R8A8);
542     if (!drawTarget || !drawTarget->IsValid()) {
543       NS_ERROR("Failed to create valid DrawTarget");
544       return NS_ERROR_FAILURE;
545     }
547     gfxContext context(drawTarget);
549     UniquePtr<SVGImageContext> svgContext;
550     if (!aSVGContext) {
551       svgContext = MakeUnique<SVGImageContext>();
552       aSVGContext = svgContext.get();
553     }
555     mozilla::image::ImgDrawResult res =
556         aImage->Draw(&context, scaledSize, ImageRegion::Create(scaledSize),
557                      aWhichFrame, SamplingFilter::POINT, *aSVGContext,
558                      imgIContainer::FLAG_SYNC_DECODE, 1.0);
560     if (res != mozilla::image::ImgDrawResult::SUCCESS) {
561       return NS_ERROR_FAILURE;
562     }
564     surface = drawTarget->Snapshot();
565   } else {
566     surface =
567         aImage->GetFrame(aWhichFrame, imgIContainer::FLAG_SYNC_DECODE |
568                                           imgIContainer::FLAG_ASYNC_NOTIFY);
569   }
571   NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
573   CGImageRef imageRef = NULL;
574   nsresult rv = nsCocoaUtils::CreateCGImageFromSurface(surface, &imageRef,
575                                                        aIsEntirelyBlack);
576   if (NS_FAILED(rv) || !imageRef) {
577     return NS_ERROR_FAILURE;
578   }
580   rv = nsCocoaUtils::CreateNSImageFromCGImage(imageRef, aResult);
581   if (NS_FAILED(rv) || !aResult) {
582     return NS_ERROR_FAILURE;
583   }
584   ::CGImageRelease(imageRef);
586   // Ensure the image will be rendered the correct size on a retina display
587   NSSize size = NSMakeSize(width, height);
588   [*aResult setSize:size];
589   [[[*aResult representations] objectAtIndex:0] setSize:size];
590   return NS_OK;
593 nsresult nsCocoaUtils::CreateDualRepresentationNSImageFromImageContainer(
594     imgIContainer* aImage, uint32_t aWhichFrame,
595     const SVGImageContext* aSVGContext, const NSSize& aPreferredSize,
596     NSImage** aResult, bool* aIsEntirelyBlack) {
597   NSImage* newRepresentation = nil;
598   nsresult rv = CreateNSImageFromImageContainer(
599       aImage, aWhichFrame, aSVGContext, aPreferredSize, &newRepresentation,
600       1.0f, aIsEntirelyBlack);
601   if (NS_FAILED(rv) || !newRepresentation) {
602     return NS_ERROR_FAILURE;
603   }
605   NSSize size = newRepresentation.size;
606   *aResult = [[NSImage alloc] init];
607   [*aResult setSize:size];
609   [[[newRepresentation representations] objectAtIndex:0] setSize:size];
610   [*aResult
611       addRepresentation:[[newRepresentation representations] objectAtIndex:0]];
612   [newRepresentation release];
613   newRepresentation = nil;
615   rv = CreateNSImageFromImageContainer(aImage, aWhichFrame, aSVGContext,
616                                        aPreferredSize, &newRepresentation, 2.0f,
617                                        aIsEntirelyBlack);
618   if (NS_FAILED(rv) || !newRepresentation) {
619     return NS_ERROR_FAILURE;
620   }
622   [[[newRepresentation representations] objectAtIndex:0] setSize:size];
623   [*aResult
624       addRepresentation:[[newRepresentation representations] objectAtIndex:0]];
625   [newRepresentation release];
626   return NS_OK;
629 // static
630 NSURL* nsCocoaUtils::ToNSURL(const nsAString& aURLString) {
631   nsAutoCString encodedURLString;
632   nsresult rv = NS_GetSpecWithNSURLEncoding(encodedURLString,
633                                             NS_ConvertUTF16toUTF8(aURLString));
634   NS_ENSURE_SUCCESS(rv, nullptr);
636   NSString* encodedURLNSString = ToNSString(encodedURLString);
637   if (!encodedURLNSString) {
638     return nullptr;
639   }
641   return [NSURL URLWithString:encodedURLNSString];
644 // static
645 void nsCocoaUtils::GeckoRectToNSRect(const nsIntRect& aGeckoRect,
646                                      NSRect& aOutCocoaRect) {
647   aOutCocoaRect.origin.x = aGeckoRect.x;
648   aOutCocoaRect.origin.y = aGeckoRect.y;
649   aOutCocoaRect.size.width = aGeckoRect.width;
650   aOutCocoaRect.size.height = aGeckoRect.height;
653 // static
654 void nsCocoaUtils::NSRectToGeckoRect(const NSRect& aCocoaRect,
655                                      nsIntRect& aOutGeckoRect) {
656   aOutGeckoRect.x = NSToIntRound(aCocoaRect.origin.x);
657   aOutGeckoRect.y = NSToIntRound(aCocoaRect.origin.y);
658   aOutGeckoRect.width =
659       NSToIntRound(aCocoaRect.origin.x + aCocoaRect.size.width) -
660       aOutGeckoRect.x;
661   aOutGeckoRect.height =
662       NSToIntRound(aCocoaRect.origin.y + aCocoaRect.size.height) -
663       aOutGeckoRect.y;
666 // static
667 NSEvent* nsCocoaUtils::MakeNewCocoaEventWithType(NSEventType aEventType,
668                                                  NSEvent* aEvent) {
669   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
671   NSEvent* newEvent =
672       [NSEvent keyEventWithType:aEventType
673                              location:[aEvent locationInWindow]
674                         modifierFlags:[aEvent modifierFlags]
675                             timestamp:[aEvent timestamp]
676                          windowNumber:[aEvent windowNumber]
677                               context:nil
678                            characters:[aEvent characters]
679           charactersIgnoringModifiers:[aEvent charactersIgnoringModifiers]
680                             isARepeat:[aEvent isARepeat]
681                               keyCode:[aEvent keyCode]];
682   return newEvent;
684   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
687 // static
688 NSEvent* nsCocoaUtils::MakeNewCococaEventFromWidgetEvent(
689     const WidgetKeyboardEvent& aKeyEvent, NSInteger aWindowNumber,
690     NSGraphicsContext* aContext) {
691   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
693   NSEventType eventType;
694   if (aKeyEvent.mMessage == eKeyUp) {
695     eventType = NSEventTypeKeyUp;
696   } else {
697     eventType = NSEventTypeKeyDown;
698   }
700   static const uint32_t sModifierFlagMap[][2] = {
701       {MODIFIER_SHIFT, NSEventModifierFlagShift},
702       {MODIFIER_CONTROL, NSEventModifierFlagControl},
703       {MODIFIER_ALT, NSEventModifierFlagOption},
704       {MODIFIER_ALTGRAPH, NSEventModifierFlagOption},
705       {MODIFIER_META, NSEventModifierFlagCommand},
706       {MODIFIER_CAPSLOCK, NSEventModifierFlagCapsLock},
707       {MODIFIER_NUMLOCK, NSEventModifierFlagNumericPad},
708       {MODIFIER_FN, NSEventModifierFlagFunction}};
710   NSUInteger modifierFlags = 0;
711   for (uint32_t i = 0; i < std::size(sModifierFlagMap); ++i) {
712     if (aKeyEvent.mModifiers & sModifierFlagMap[i][0]) {
713       modifierFlags |= sModifierFlagMap[i][1];
714     }
715   }
717   NSString* characters;
718   if (aKeyEvent.mCharCode) {
719     characters =
720         [NSString stringWithCharacters:reinterpret_cast<const unichar*>(
721                                            &(aKeyEvent.mCharCode))
722                                 length:1];
723   } else {
724     uint32_t cocoaCharCode =
725         nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(aKeyEvent.mKeyCode);
726     characters = [NSString
727         stringWithCharacters:reinterpret_cast<const unichar*>(&cocoaCharCode)
728                       length:1];
729   }
731   return [NSEvent keyEventWithType:eventType
732                           location:NSMakePoint(0, 0)
733                      modifierFlags:modifierFlags
734                          timestamp:0
735                       windowNumber:aWindowNumber
736                            context:aContext
737                         characters:characters
738        charactersIgnoringModifiers:characters
739                          isARepeat:NO
740                            keyCode:0];  // Native key code not currently needed
742   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
745 // static
746 void nsCocoaUtils::InitInputEvent(WidgetInputEvent& aInputEvent,
747                                   NSEvent* aNativeEvent) {
748   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
750   aInputEvent.mModifiers = ModifiersForEvent(aNativeEvent);
751   aInputEvent.mTimeStamp = GetEventTimeStamp([aNativeEvent timestamp]);
753   NS_OBJC_END_TRY_IGNORE_BLOCK;
756 // static
757 Modifiers nsCocoaUtils::ModifiersForEvent(NSEvent* aNativeEvent) {
758   NSUInteger modifiers =
759       aNativeEvent ? [aNativeEvent modifierFlags] : [NSEvent modifierFlags];
760   Modifiers result = 0;
761   if (modifiers & NSEventModifierFlagShift) {
762     result |= MODIFIER_SHIFT;
763   }
764   if (modifiers & NSEventModifierFlagControl) {
765     result |= MODIFIER_CONTROL;
766   }
767   if (modifiers & NSEventModifierFlagOption) {
768     result |= MODIFIER_ALT;
769     // Mac's option key is similar to other platforms' AltGr key.
770     // Let's set AltGr flag when option key is pressed for consistency with
771     // other platforms.
772     result |= MODIFIER_ALTGRAPH;
773   }
774   if (modifiers & NSEventModifierFlagCommand) {
775     result |= MODIFIER_META;
776   }
778   if (modifiers & NSEventModifierFlagCapsLock) {
779     result |= MODIFIER_CAPSLOCK;
780   }
781   // Mac doesn't have NumLock key.  We can assume that NumLock is always locked
782   // if user is using a keyboard which has numpad.  Otherwise, if user is using
783   // a keyboard which doesn't have numpad, e.g., MacBook's keyboard, we can
784   // assume that NumLock is always unlocked.
785   // Unfortunately, we cannot know whether current keyboard has numpad or not.
786   // We should notify locked state only when keys in numpad are pressed.
787   // By this, web applications may not be confused by unexpected numpad key's
788   // key event with unlocked state.
789   if (modifiers & NSEventModifierFlagNumericPad) {
790     result |= MODIFIER_NUMLOCK;
791   }
793   // Be aware, NSEventModifierFlagFunction is also set on the native event when
794   // arrow keys, the home key or some other keys are pressed. We cannot check
795   // whether the 'fn' key is pressed or not by the flag alone. We need to check
796   // that the event's keyCode falls outside the range of keys that will also set
797   // the function modifier.
798   if (!!(modifiers & NSEventModifierFlagFunction) &&
799       (aNativeEvent.type == NSEventTypeKeyDown ||
800        aNativeEvent.type == NSEventTypeKeyUp ||
801        aNativeEvent.type == NSEventTypeFlagsChanged) &&
802       !(kVK_Return <= aNativeEvent.keyCode &&
803         aNativeEvent.keyCode <= NSModeSwitchFunctionKey)) {
804     result |= MODIFIER_FN;
805   }
807   return result;
810 // static
811 UInt32 nsCocoaUtils::ConvertToCarbonModifier(NSUInteger aCocoaModifier) {
812   UInt32 carbonModifier = 0;
813   if (aCocoaModifier & NSEventModifierFlagCapsLock) {
814     carbonModifier |= alphaLock;
815   }
816   if (aCocoaModifier & NSEventModifierFlagControl) {
817     carbonModifier |= controlKey;
818   }
819   if (aCocoaModifier & NSEventModifierFlagOption) {
820     carbonModifier |= optionKey;
821   }
822   if (aCocoaModifier & NSEventModifierFlagShift) {
823     carbonModifier |= shiftKey;
824   }
825   if (aCocoaModifier & NSEventModifierFlagCommand) {
826     carbonModifier |= cmdKey;
827   }
828   if (aCocoaModifier & NSEventModifierFlagNumericPad) {
829     carbonModifier |= kEventKeyModifierNumLockMask;
830   }
831   if (aCocoaModifier & NSEventModifierFlagFunction) {
832     carbonModifier |= kEventKeyModifierFnMask;
833   }
834   return carbonModifier;
837 // While HiDPI support is not 100% complete and tested, we'll have a pref
838 // to allow it to be turned off in case of problems (or for testing purposes).
840 // gfx.hidpi.enabled is an integer with the meaning:
841 //    <= 0 : HiDPI support is disabled
842 //       1 : HiDPI enabled provided all screens have the same backing resolution
843 //     > 1 : HiDPI enabled even if there are a mixture of screen modes
845 // All the following code is to be removed once HiDPI work is more complete.
847 static bool sHiDPIEnabled = false;
848 static bool sHiDPIPrefInitialized = false;
850 // static
851 bool nsCocoaUtils::HiDPIEnabled() {
852   if (!sHiDPIPrefInitialized) {
853     sHiDPIPrefInitialized = true;
855     int prefSetting = Preferences::GetInt("gfx.hidpi.enabled", 1);
856     if (prefSetting <= 0) {
857       return false;
858     }
860     // prefSetting is at least 1, need to check attached screens...
862     int scaleFactors = 0;  // used as a bitset to track the screen types found
863     NSEnumerator* screenEnum = [[NSScreen screens] objectEnumerator];
864     while (NSScreen* screen = [screenEnum nextObject]) {
865       NSDictionary* desc = [screen deviceDescription];
866       if ([desc objectForKey:NSDeviceIsScreen] == nil) {
867         continue;
868       }
869       // Currently, we only care about differentiating "1.0" and "2.0",
870       // so we set one of the two low bits to record which.
871       if ([screen backingScaleFactor] > 1.0) {
872         scaleFactors |= 2;
873       } else {
874         scaleFactors |= 1;
875       }
876     }
878     // Now scaleFactors will be:
879     //   0 if no screens (supporting backingScaleFactor) found
880     //   1 if only lo-DPI screens
881     //   2 if only hi-DPI screens
882     //   3 if both lo- and hi-DPI screens
883     // We'll enable HiDPI support if there's only a single screen type,
884     // OR if the pref setting is explicitly greater than 1.
885     sHiDPIEnabled = (scaleFactors <= 2) || (prefSetting > 1);
886   }
888   return sHiDPIEnabled;
891 // static
892 void nsCocoaUtils::InvalidateHiDPIState() { sHiDPIPrefInitialized = false; }
894 void nsCocoaUtils::GetCommandsFromKeyEvent(
895     NSEvent* aEvent, nsTArray<KeyBindingsCommand>& aCommands) {
896   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
898   MOZ_ASSERT(aEvent);
900   static NativeKeyBindingsRecorder* sNativeKeyBindingsRecorder;
901   if (!sNativeKeyBindingsRecorder) {
902     sNativeKeyBindingsRecorder = [NativeKeyBindingsRecorder new];
903   }
905   [sNativeKeyBindingsRecorder startRecording:aCommands];
907   // This will trigger 0 - N calls to doCommandBySelector: and insertText:
908   [sNativeKeyBindingsRecorder
909       interpretKeyEvents:[NSArray arrayWithObject:aEvent]];
911   NS_OBJC_END_TRY_IGNORE_BLOCK;
914 @implementation NativeKeyBindingsRecorder
916 - (void)startRecording:(nsTArray<KeyBindingsCommand>&)aCommands {
917   mCommands = &aCommands;
918   mCommands->Clear();
921 - (void)doCommandBySelector:(SEL)aSelector {
922   KeyBindingsCommand command = {aSelector, nil};
924   mCommands->AppendElement(command);
927 - (void)insertText:(id)aString {
928   KeyBindingsCommand command = {@selector(insertText:), aString};
930   mCommands->AppendElement(command);
933 @end  // NativeKeyBindingsRecorder
935 struct KeyConversionData {
936   const char* str;
937   size_t strLength;
938   uint32_t geckoKeyCode;
939   uint32_t charCode;
942 static const KeyConversionData gKeyConversions[] = {
944 #define KEYCODE_ENTRY(aStr, aCode) {#aStr, sizeof(#aStr) - 1, NS_##aStr, aCode}
946 // Some keycodes may have different name in KeyboardEvent from its key name.
947 #define KEYCODE_ENTRY2(aStr, aNSName, aCode) \
948   {#aStr, sizeof(#aStr) - 1, NS_##aNSName, aCode}
950     KEYCODE_ENTRY(VK_CANCEL, 0x001B),
951     KEYCODE_ENTRY(VK_DELETE, NSDeleteFunctionKey),
952     KEYCODE_ENTRY(VK_BACK, NSBackspaceCharacter),
953     KEYCODE_ENTRY2(VK_BACK_SPACE, VK_BACK, NSBackspaceCharacter),
954     KEYCODE_ENTRY(VK_TAB, NSTabCharacter),
955     KEYCODE_ENTRY(VK_CLEAR, NSClearLineFunctionKey),
956     KEYCODE_ENTRY(VK_RETURN, NSEnterCharacter),
957     KEYCODE_ENTRY(VK_SHIFT, 0),
958     KEYCODE_ENTRY(VK_CONTROL, 0),
959     KEYCODE_ENTRY(VK_ALT, 0),
960     KEYCODE_ENTRY(VK_PAUSE, NSPauseFunctionKey),
961     KEYCODE_ENTRY(VK_CAPS_LOCK, 0),
962     KEYCODE_ENTRY(VK_ESCAPE, 0),
963     KEYCODE_ENTRY(VK_SPACE, ' '),
964     KEYCODE_ENTRY(VK_PAGE_UP, NSPageUpFunctionKey),
965     KEYCODE_ENTRY(VK_PAGE_DOWN, NSPageDownFunctionKey),
966     KEYCODE_ENTRY(VK_END, NSEndFunctionKey),
967     KEYCODE_ENTRY(VK_HOME, NSHomeFunctionKey),
968     KEYCODE_ENTRY(VK_LEFT, NSLeftArrowFunctionKey),
969     KEYCODE_ENTRY(VK_UP, NSUpArrowFunctionKey),
970     KEYCODE_ENTRY(VK_RIGHT, NSRightArrowFunctionKey),
971     KEYCODE_ENTRY(VK_DOWN, NSDownArrowFunctionKey),
972     KEYCODE_ENTRY(VK_PRINTSCREEN, NSPrintScreenFunctionKey),
973     KEYCODE_ENTRY(VK_INSERT, NSInsertFunctionKey),
974     KEYCODE_ENTRY(VK_HELP, NSHelpFunctionKey),
975     KEYCODE_ENTRY(VK_0, '0'),
976     KEYCODE_ENTRY(VK_1, '1'),
977     KEYCODE_ENTRY(VK_2, '2'),
978     KEYCODE_ENTRY(VK_3, '3'),
979     KEYCODE_ENTRY(VK_4, '4'),
980     KEYCODE_ENTRY(VK_5, '5'),
981     KEYCODE_ENTRY(VK_6, '6'),
982     KEYCODE_ENTRY(VK_7, '7'),
983     KEYCODE_ENTRY(VK_8, '8'),
984     KEYCODE_ENTRY(VK_9, '9'),
985     KEYCODE_ENTRY(VK_SEMICOLON, ':'),
986     KEYCODE_ENTRY(VK_EQUALS, '='),
987     KEYCODE_ENTRY(VK_A, 'A'),
988     KEYCODE_ENTRY(VK_B, 'B'),
989     KEYCODE_ENTRY(VK_C, 'C'),
990     KEYCODE_ENTRY(VK_D, 'D'),
991     KEYCODE_ENTRY(VK_E, 'E'),
992     KEYCODE_ENTRY(VK_F, 'F'),
993     KEYCODE_ENTRY(VK_G, 'G'),
994     KEYCODE_ENTRY(VK_H, 'H'),
995     KEYCODE_ENTRY(VK_I, 'I'),
996     KEYCODE_ENTRY(VK_J, 'J'),
997     KEYCODE_ENTRY(VK_K, 'K'),
998     KEYCODE_ENTRY(VK_L, 'L'),
999     KEYCODE_ENTRY(VK_M, 'M'),
1000     KEYCODE_ENTRY(VK_N, 'N'),
1001     KEYCODE_ENTRY(VK_O, 'O'),
1002     KEYCODE_ENTRY(VK_P, 'P'),
1003     KEYCODE_ENTRY(VK_Q, 'Q'),
1004     KEYCODE_ENTRY(VK_R, 'R'),
1005     KEYCODE_ENTRY(VK_S, 'S'),
1006     KEYCODE_ENTRY(VK_T, 'T'),
1007     KEYCODE_ENTRY(VK_U, 'U'),
1008     KEYCODE_ENTRY(VK_V, 'V'),
1009     KEYCODE_ENTRY(VK_W, 'W'),
1010     KEYCODE_ENTRY(VK_X, 'X'),
1011     KEYCODE_ENTRY(VK_Y, 'Y'),
1012     KEYCODE_ENTRY(VK_Z, 'Z'),
1013     KEYCODE_ENTRY(VK_CONTEXT_MENU, NSMenuFunctionKey),
1014     KEYCODE_ENTRY(VK_NUMPAD0, '0'),
1015     KEYCODE_ENTRY(VK_NUMPAD1, '1'),
1016     KEYCODE_ENTRY(VK_NUMPAD2, '2'),
1017     KEYCODE_ENTRY(VK_NUMPAD3, '3'),
1018     KEYCODE_ENTRY(VK_NUMPAD4, '4'),
1019     KEYCODE_ENTRY(VK_NUMPAD5, '5'),
1020     KEYCODE_ENTRY(VK_NUMPAD6, '6'),
1021     KEYCODE_ENTRY(VK_NUMPAD7, '7'),
1022     KEYCODE_ENTRY(VK_NUMPAD8, '8'),
1023     KEYCODE_ENTRY(VK_NUMPAD9, '9'),
1024     KEYCODE_ENTRY(VK_MULTIPLY, '*'),
1025     KEYCODE_ENTRY(VK_ADD, '+'),
1026     KEYCODE_ENTRY(VK_SEPARATOR, 0),
1027     KEYCODE_ENTRY(VK_SUBTRACT, '-'),
1028     KEYCODE_ENTRY(VK_DECIMAL, '.'),
1029     KEYCODE_ENTRY(VK_DIVIDE, '/'),
1030     KEYCODE_ENTRY(VK_F1, NSF1FunctionKey),
1031     KEYCODE_ENTRY(VK_F2, NSF2FunctionKey),
1032     KEYCODE_ENTRY(VK_F3, NSF3FunctionKey),
1033     KEYCODE_ENTRY(VK_F4, NSF4FunctionKey),
1034     KEYCODE_ENTRY(VK_F5, NSF5FunctionKey),
1035     KEYCODE_ENTRY(VK_F6, NSF6FunctionKey),
1036     KEYCODE_ENTRY(VK_F7, NSF7FunctionKey),
1037     KEYCODE_ENTRY(VK_F8, NSF8FunctionKey),
1038     KEYCODE_ENTRY(VK_F9, NSF9FunctionKey),
1039     KEYCODE_ENTRY(VK_F10, NSF10FunctionKey),
1040     KEYCODE_ENTRY(VK_F11, NSF11FunctionKey),
1041     KEYCODE_ENTRY(VK_F12, NSF12FunctionKey),
1042     KEYCODE_ENTRY(VK_F13, NSF13FunctionKey),
1043     KEYCODE_ENTRY(VK_F14, NSF14FunctionKey),
1044     KEYCODE_ENTRY(VK_F15, NSF15FunctionKey),
1045     KEYCODE_ENTRY(VK_F16, NSF16FunctionKey),
1046     KEYCODE_ENTRY(VK_F17, NSF17FunctionKey),
1047     KEYCODE_ENTRY(VK_F18, NSF18FunctionKey),
1048     KEYCODE_ENTRY(VK_F19, NSF19FunctionKey),
1049     KEYCODE_ENTRY(VK_F20, NSF20FunctionKey),
1050     KEYCODE_ENTRY(VK_F21, NSF21FunctionKey),
1051     KEYCODE_ENTRY(VK_F22, NSF22FunctionKey),
1052     KEYCODE_ENTRY(VK_F23, NSF23FunctionKey),
1053     KEYCODE_ENTRY(VK_F24, NSF24FunctionKey),
1054     KEYCODE_ENTRY(VK_NUM_LOCK, NSClearLineFunctionKey),
1055     KEYCODE_ENTRY(VK_SCROLL_LOCK, NSScrollLockFunctionKey),
1056     KEYCODE_ENTRY(VK_COMMA, ','),
1057     KEYCODE_ENTRY(VK_PERIOD, '.'),
1058     KEYCODE_ENTRY(VK_SLASH, '/'),
1059     KEYCODE_ENTRY(VK_BACK_QUOTE, '`'),
1060     KEYCODE_ENTRY(VK_OPEN_BRACKET, '['),
1061     KEYCODE_ENTRY(VK_BACK_SLASH, '\\'),
1062     KEYCODE_ENTRY(VK_CLOSE_BRACKET, ']'),
1063     KEYCODE_ENTRY(VK_QUOTE, '\'')
1065 #undef KEYCODE_ENTRY
1069 uint32_t nsCocoaUtils::ConvertGeckoNameToMacCharCode(
1070     const nsAString& aKeyCodeName) {
1071   if (aKeyCodeName.IsEmpty()) {
1072     return 0;
1073   }
1075   nsAutoCString keyCodeName;
1076   LossyCopyUTF16toASCII(aKeyCodeName, keyCodeName);
1077   // We want case-insensitive comparison with data stored as uppercase.
1078   ToUpperCase(keyCodeName);
1080   uint32_t keyCodeNameLength = keyCodeName.Length();
1081   const char* keyCodeNameStr = keyCodeName.get();
1082   for (uint16_t i = 0; i < std::size(gKeyConversions); ++i) {
1083     if (keyCodeNameLength == gKeyConversions[i].strLength &&
1084         nsCRT::strcmp(gKeyConversions[i].str, keyCodeNameStr) == 0) {
1085       return gKeyConversions[i].charCode;
1086     }
1087   }
1089   return 0;
1092 uint32_t nsCocoaUtils::ConvertGeckoKeyCodeToMacCharCode(uint32_t aKeyCode) {
1093   if (!aKeyCode) {
1094     return 0;
1095   }
1097   for (uint16_t i = 0; i < std::size(gKeyConversions); ++i) {
1098     if (gKeyConversions[i].geckoKeyCode == aKeyCode) {
1099       return gKeyConversions[i].charCode;
1100     }
1101   }
1103   return 0;
1106 NSEventModifierFlags nsCocoaUtils::ConvertWidgetModifiersToMacModifierFlags(
1107     nsIWidget::Modifiers aNativeModifiers) {
1108   if (!aNativeModifiers) {
1109     return 0;
1110   }
1111   struct ModifierFlagMapEntry {
1112     nsIWidget::Modifiers mWidgetModifier;
1113     NSEventModifierFlags mModifierFlags;
1114   };
1115   static constexpr ModifierFlagMapEntry sModifierFlagMap[] = {
1116       {nsIWidget::CAPS_LOCK, NSEventModifierFlagCapsLock},
1117       {nsIWidget::SHIFT_L, NSEventModifierFlagShift | 0x0002},
1118       {nsIWidget::SHIFT_R, NSEventModifierFlagShift | 0x0004},
1119       {nsIWidget::CTRL_L, NSEventModifierFlagControl | 0x0001},
1120       {nsIWidget::CTRL_R, NSEventModifierFlagControl | 0x2000},
1121       {nsIWidget::ALT_L, NSEventModifierFlagOption | 0x0020},
1122       {nsIWidget::ALT_R, NSEventModifierFlagOption | 0x0040},
1123       {nsIWidget::COMMAND_L, NSEventModifierFlagCommand | 0x0008},
1124       {nsIWidget::COMMAND_R, NSEventModifierFlagCommand | 0x0010},
1125       {nsIWidget::NUMERIC_KEY_PAD, NSEventModifierFlagNumericPad},
1126       {nsIWidget::HELP, NSEventModifierFlagHelp},
1127       {nsIWidget::FUNCTION, NSEventModifierFlagFunction}};
1129   NSEventModifierFlags modifierFlags = 0;
1130   for (const ModifierFlagMapEntry& entry : sModifierFlagMap) {
1131     if (aNativeModifiers & entry.mWidgetModifier) {
1132       modifierFlags |= entry.mModifierFlags;
1133     }
1134   }
1135   return modifierFlags;
1138 mozilla::MouseButton nsCocoaUtils::ButtonForEvent(NSEvent* aEvent) {
1139   switch (aEvent.type) {
1140     case NSEventTypeLeftMouseDown:
1141     case NSEventTypeLeftMouseDragged:
1142     case NSEventTypeLeftMouseUp:
1143       return MouseButton::ePrimary;
1144     case NSEventTypeRightMouseDown:
1145     case NSEventTypeRightMouseDragged:
1146     case NSEventTypeRightMouseUp:
1147       return MouseButton::eSecondary;
1148     case NSEventTypeOtherMouseDown:
1149     case NSEventTypeOtherMouseDragged:
1150     case NSEventTypeOtherMouseUp:
1151       switch (aEvent.buttonNumber) {
1152         case 3:
1153           return MouseButton::eX1;
1154         case 4:
1155           return MouseButton::eX2;
1156         default:
1157           // The middle button usually has button 2, but if this is a
1158           // synthesized event (for which you cannot specify a buttonNumber),
1159           // then the button will be 0. Treat all remaining OtherMouse events as
1160           // the middle button.
1161           return MouseButton::eMiddle;
1162       }
1163     default:
1164       // Treat non-mouse events as the primary mouse button.
1165       return MouseButton::ePrimary;
1166   }
1169 NSMutableAttributedString* nsCocoaUtils::GetNSMutableAttributedString(
1170     const nsAString& aText, const nsTArray<mozilla::FontRange>& aFontRanges,
1171     const bool aIsVertical, const CGFloat aBackingScaleFactor) {
1172   NS_OBJC_BEGIN_TRY_BLOCK_RETURN
1174   NSString* nsstr = nsCocoaUtils::ToNSString(aText);
1175   NSMutableAttributedString* attrStr =
1176       [[[NSMutableAttributedString alloc] initWithString:nsstr
1177                                               attributes:nil] autorelease];
1179   int32_t lastOffset = aText.Length();
1180   for (auto i = aFontRanges.Length(); i > 0; --i) {
1181     const FontRange& fontRange = aFontRanges[i - 1];
1182     NSString* fontName = nsCocoaUtils::ToNSString(fontRange.mFontName);
1183     CGFloat fontSize = fontRange.mFontSize / aBackingScaleFactor;
1184     NSFont* font = [NSFont fontWithName:fontName size:fontSize];
1185     if (!font) {
1186       font = [NSFont systemFontOfSize:fontSize];
1187     }
1189     NSDictionary* attrs = @{NSFontAttributeName : font};
1190     NSRange range = NSMakeRange(fontRange.mStartOffset,
1191                                 lastOffset - fontRange.mStartOffset);
1192     [attrStr setAttributes:attrs range:range];
1193     lastOffset = fontRange.mStartOffset;
1194   }
1196   if (aIsVertical) {
1197     [attrStr addAttribute:NSVerticalGlyphFormAttributeName
1198                     value:[NSNumber numberWithInt:1]
1199                     range:NSMakeRange(0, [attrStr length])];
1200   }
1202   return attrStr;
1204   NS_OBJC_END_TRY_BLOCK_RETURN(nil)
1207 TimeStamp nsCocoaUtils::GetEventTimeStamp(NSTimeInterval aEventTime) {
1208   if (!aEventTime) {
1209     // If the event is generated by a 3rd party application, its timestamp
1210     // may be 0.  In this case, just return current timestamp.
1211     // XXX Should we cache last event time?
1212     return TimeStamp::Now();
1213   }
1214   // The internal value of the macOS implementation of TimeStamp is based on
1215   // mach_absolute_time(), which measures "ticks" since boot.
1216   // Event timestamps are NSTimeIntervals (seconds) since boot. So the two time
1217   // representations already have the same base; we only need to convert
1218   // seconds into ticks.
1219   int64_t tick =
1220       BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aEventTime * 1000.0);
1221   return TimeStamp::FromSystemTime(tick);
1224 static NSString* ActionOnDoubleClickSystemPref() {
1225   NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
1226   NSString* kAppleActionOnDoubleClickKey = @"AppleActionOnDoubleClick";
1227   id value = [userDefaults objectForKey:kAppleActionOnDoubleClickKey];
1228   if ([value isKindOfClass:[NSString class]]) {
1229     return value;
1230   }
1231   return nil;
1234 @interface NSWindow (NSWindowShouldZoomOnDoubleClick)
1235 + (BOOL)_shouldZoomOnDoubleClick;  // present on 10.7 and above
1236 @end
1238 bool nsCocoaUtils::ShouldZoomOnTitlebarDoubleClick() {
1239   if ([NSWindow respondsToSelector:@selector(_shouldZoomOnDoubleClick)]) {
1240     return [NSWindow _shouldZoomOnDoubleClick];
1241   }
1242   return [ActionOnDoubleClickSystemPref() isEqualToString:@"Maximize"];
1245 bool nsCocoaUtils::ShouldMinimizeOnTitlebarDoubleClick() {
1246   // Check the system preferences.
1247   // We could also check -[NSWindow _shouldMiniaturizeOnDoubleClick]. It's not
1248   // clear to me which approach would be preferable; neither is public API.
1249   return [ActionOnDoubleClickSystemPref() isEqualToString:@"Minimize"];
1252 static const char* AVMediaTypeToString(AVMediaType aType) {
1253   if (aType == AVMediaTypeVideo) {
1254     return "video";
1255   }
1257   if (aType == AVMediaTypeAudio) {
1258     return "audio";
1259   }
1261   return "unexpected type";
1264 static void LogAuthorizationStatus(AVMediaType aType, int aState) {
1265   const char* stateString;
1267   switch (aState) {
1268     case AVAuthorizationStatusAuthorized:
1269       stateString = "AVAuthorizationStatusAuthorized";
1270       break;
1271     case AVAuthorizationStatusDenied:
1272       stateString = "AVAuthorizationStatusDenied";
1273       break;
1274     case AVAuthorizationStatusNotDetermined:
1275       stateString = "AVAuthorizationStatusNotDetermined";
1276       break;
1277     case AVAuthorizationStatusRestricted:
1278       stateString = "AVAuthorizationStatusRestricted";
1279       break;
1280     default:
1281       stateString = "Invalid state";
1282   }
1284   LOG("%s authorization status: %s\n", AVMediaTypeToString(aType), stateString);
1287 static nsresult GetPermissionState(AVMediaType aMediaType, uint16_t& aState) {
1288   MOZ_ASSERT(aMediaType == AVMediaTypeVideo || aMediaType == AVMediaTypeAudio);
1290   AVAuthorizationStatus authStatus = static_cast<AVAuthorizationStatus>(
1291       [AVCaptureDevice authorizationStatusForMediaType:aMediaType]);
1292   LogAuthorizationStatus(aMediaType, authStatus);
1294   // Convert AVAuthorizationStatus to nsIOSPermissionRequest const
1295   switch (authStatus) {
1296     case AVAuthorizationStatusAuthorized:
1297       aState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
1298       return NS_OK;
1299     case AVAuthorizationStatusDenied:
1300       aState = nsIOSPermissionRequest::PERMISSION_STATE_DENIED;
1301       return NS_OK;
1302     case AVAuthorizationStatusNotDetermined:
1303       aState = nsIOSPermissionRequest::PERMISSION_STATE_NOTDETERMINED;
1304       return NS_OK;
1305     case AVAuthorizationStatusRestricted:
1306       aState = nsIOSPermissionRequest::PERMISSION_STATE_RESTRICTED;
1307       return NS_OK;
1308     default:
1309       MOZ_ASSERT(false, "Invalid authorization status");
1310       return NS_ERROR_UNEXPECTED;
1311   }
1314 nsresult nsCocoaUtils::GetVideoCapturePermissionState(
1315     uint16_t& aPermissionState) {
1316   return GetPermissionState(AVMediaTypeVideo, aPermissionState);
1319 nsresult nsCocoaUtils::GetAudioCapturePermissionState(
1320     uint16_t& aPermissionState) {
1321   return GetPermissionState(AVMediaTypeAudio, aPermissionState);
1324 // Set |aPermissionState| to PERMISSION_STATE_AUTHORIZED if this application
1325 // has already been granted permission to record the screen in macOS Security
1326 // and Privacy system settings. If we do not have permission (because the user
1327 // hasn't yet been asked yet or the user previously denied the prompt), use
1328 // PERMISSION_STATE_DENIED. Returns NS_ERROR_NOT_IMPLEMENTED on macOS 10.14
1329 // and earlier.
1330 nsresult nsCocoaUtils::GetScreenCapturePermissionState(
1331     uint16_t& aPermissionState) {
1332   aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_NOTDETERMINED;
1334   if (!StaticPrefs::media_macos_screenrecording_oscheck_enabled()) {
1335     aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
1336     LOG("screen authorization status: authorized (test disabled via pref)");
1337     return NS_OK;
1338   }
1340   // Unlike with camera and microphone capture, there is no support for
1341   // checking the screen recording permission status. Instead, an application
1342   // can use the presence of window names (which are privacy sensitive) in
1343   // the window info list as an indication. The list only includes window
1344   // names if the calling application has been authorized to record the
1345   // screen. We use the window name, window level, and owning PID as
1346   // heuristics to determine if we have screen recording permission.
1347   AutoCFRelease<CFArrayRef> windowArray =
1348       CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
1349   if (!windowArray) {
1350     LOG("GetScreenCapturePermissionState() ERROR: got NULL window info list");
1351     return NS_ERROR_UNEXPECTED;
1352   }
1354   int32_t windowLevelDock = CGWindowLevelForKey(kCGDockWindowLevelKey);
1355   int32_t windowLevelNormal = CGWindowLevelForKey(kCGNormalWindowLevelKey);
1356   LOG("GetScreenCapturePermissionState(): DockWindowLevel: %d, "
1357       "NormalWindowLevel: %d",
1358       windowLevelDock, windowLevelNormal);
1360   int32_t thisPid = [[NSProcessInfo processInfo] processIdentifier];
1362   CFIndex windowCount = CFArrayGetCount(windowArray);
1363   LOG("GetScreenCapturePermissionState() returned %ld windows", windowCount);
1364   if (windowCount == 0) {
1365     return NS_ERROR_UNEXPECTED;
1366   }
1368   for (CFIndex i = 0; i < windowCount; i++) {
1369     CFDictionaryRef windowDict = reinterpret_cast<CFDictionaryRef>(
1370         CFArrayGetValueAtIndex(windowArray, i));
1372     // Get the window owner's PID
1373     int32_t windowOwnerPid = -1;
1374     CFNumberRef windowPidRef = reinterpret_cast<CFNumberRef>(
1375         CFDictionaryGetValue(windowDict, kCGWindowOwnerPID));
1376     if (!windowPidRef ||
1377         !CFNumberGetValue(windowPidRef, kCFNumberIntType, &windowOwnerPid)) {
1378       LOG("GetScreenCapturePermissionState() ERROR: failed to get window "
1379           "owner");
1380       continue;
1381     }
1383     // Our own window names are always readable and
1384     // therefore not relevant to the heuristic.
1385     if (thisPid == windowOwnerPid) {
1386       continue;
1387     }
1389     CFStringRef windowName = reinterpret_cast<CFStringRef>(
1390         CFDictionaryGetValue(windowDict, kCGWindowName));
1391     if (!windowName) {
1392       continue;
1393     }
1395     // macOS versions 12.2 (Monterey) or later have a status indicator when the
1396     // microphone is in use (an orange dot). This is implemented as a window
1397     // owned by the window server process. The permission check logic queries
1398     // window server for all windows and assumes it has the required permission
1399     // if it can read any window name that is at dock or normal level.
1400     // The StatusIndicator window is an exception and needs to be skipped
1401     // because it is owned by window server process and therefore when querying
1402     // the window server, the name is always readable.
1403     if (kCFCompareEqualTo ==
1404         CFStringCompare(windowName, CFSTR("StatusIndicator"), 0)) {
1405       continue;
1406     }
1408     CFNumberRef windowLayerRef = reinterpret_cast<CFNumberRef>(
1409         CFDictionaryGetValue(windowDict, kCGWindowLayer));
1410     int32_t windowLayer;
1411     if (!windowLayerRef ||
1412         !CFNumberGetValue(windowLayerRef, kCFNumberIntType, &windowLayer)) {
1413       LOG("GetScreenCapturePermissionState() ERROR: failed to get layer");
1414       continue;
1415     }
1417     // If we have a window name and the window is in the dock or normal window
1418     // level, and for another process, assume we have screen recording access.
1419     LOG("GetScreenCapturePermissionState(): windowLayer: %d", windowLayer);
1420     if (windowLayer == windowLevelDock || windowLayer == windowLevelNormal) {
1421       aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_AUTHORIZED;
1422       LOG("screen authorization status: authorized");
1423       return NS_OK;
1424     }
1425   }
1427   aPermissionState = nsIOSPermissionRequest::PERMISSION_STATE_DENIED;
1428   LOG("screen authorization status: not authorized");
1429   return NS_OK;
1432 nsresult nsCocoaUtils::RequestVideoCapturePermission(
1433     RefPtr<Promise>& aPromise) {
1434   MOZ_ASSERT(NS_IsMainThread());
1435   return nsCocoaUtils::RequestCapturePermission(AVMediaTypeVideo, aPromise,
1436                                                 sVideoCapturePromises,
1437                                                 VideoCompletionHandler);
1440 nsresult nsCocoaUtils::RequestAudioCapturePermission(
1441     RefPtr<Promise>& aPromise) {
1442   MOZ_ASSERT(NS_IsMainThread());
1443   return nsCocoaUtils::RequestCapturePermission(AVMediaTypeAudio, aPromise,
1444                                                 sAudioCapturePromises,
1445                                                 AudioCompletionHandler);
1449 // Stores |aPromise| on |aPromiseList| and starts an asynchronous media
1450 // capture request for the given media type |aType|. If we are already
1451 // waiting for a capture request for this media type, don't start a new
1452 // request. |aHandler| is invoked on an arbitrary dispatch queue when the
1453 // request completes and must resolve any waiting Promises on the main
1454 // thread.
1456 nsresult nsCocoaUtils::RequestCapturePermission(
1457     AVMediaType aType, RefPtr<Promise>& aPromise, PromiseArray& aPromiseList,
1458     void (^aHandler)(BOOL granted)) {
1459   MOZ_ASSERT(aType == AVMediaTypeVideo || aType == AVMediaTypeAudio);
1460   LOG("RequestCapturePermission(%s)", AVMediaTypeToString(aType));
1462   sMediaCaptureMutex.Lock();
1464   // Initialize our list of promises on first invocation
1465   if (aPromiseList == nullptr) {
1466     aPromiseList = new nsTArray<RefPtr<Promise>>;
1467     ClearOnShutdown(&aPromiseList);
1468   }
1470   aPromiseList->AppendElement(aPromise);
1471   size_t nPromises = aPromiseList->Length();
1473   sMediaCaptureMutex.Unlock();
1475   LOG("RequestCapturePermission(%s): %ld promise(s) unresolved",
1476       AVMediaTypeToString(aType), nPromises);
1478   // If we had one or more more existing promises waiting to be resolved
1479   // by the completion handler, we don't need to start another request.
1480   if (nPromises > 1) {
1481     return NS_OK;
1482   }
1484   // Start the request
1485   [AVCaptureDevice requestAccessForMediaType:aType completionHandler:aHandler];
1486   return NS_OK;
1490 // Audio capture request completion handler. Called from an arbitrary
1491 // dispatch queue.
1493 void (^nsCocoaUtils::AudioCompletionHandler)(BOOL) = ^void(BOOL granted) {
1494   nsCocoaUtils::ResolveAudioCapturePromises(granted);
1498 // Video capture request completion handler. Called from an arbitrary
1499 // dispatch queue.
1501 void (^nsCocoaUtils::VideoCompletionHandler)(BOOL) = ^void(BOOL granted) {
1502   nsCocoaUtils::ResolveVideoCapturePromises(granted);
1505 void nsCocoaUtils::ResolveMediaCapturePromises(bool aGranted,
1506                                                PromiseArray& aPromiseList) {
1507   StaticMutexAutoLock lock(sMediaCaptureMutex);
1509   // Remove each promise from the list and resolve it.
1510   while (aPromiseList->Length() > 0) {
1511     RefPtr<Promise> promise = aPromiseList->PopLastElement();
1513     // Resolve on main thread
1514     nsCOMPtr<nsIRunnable> runnable(
1515         NS_NewRunnableFunction("ResolveMediaAccessPromise",
1516                                [aGranted, aPromise = std::move(promise)]() {
1517                                  aPromise->MaybeResolve(aGranted);
1518                                }));
1519     NS_DispatchToMainThread(runnable.forget());
1520   }
1523 void nsCocoaUtils::ResolveAudioCapturePromises(bool aGranted) {
1524   // Resolve on main thread
1525   nsCOMPtr<nsIRunnable> runnable(
1526       NS_NewRunnableFunction("ResolveAudioCapturePromise", [aGranted]() {
1527         ResolveMediaCapturePromises(aGranted, sAudioCapturePromises);
1528       }));
1529   NS_DispatchToMainThread(runnable.forget());
1533 // Attempt to trigger a dialog requesting permission to record the screen.
1534 // Unlike with the camera and microphone, there is no API to request permission
1535 // to record the screen or to receive a callback when permission is explicitly
1536 // allowed or denied. Here we attempt to trigger the dialog by attempting to
1537 // capture a 1x1 pixel section of the screen. The permission dialog is not
1538 // guaranteed to be displayed because the user may have already been prompted
1539 // in which case macOS does not display the dialog again.
1541 nsresult nsCocoaUtils::MaybeRequestScreenCapturePermission() {
1542   LOG("MaybeRequestScreenCapturePermission()");
1543   AutoCFRelease<CGImageRef> image =
1544       CGDisplayCreateImageForRect(kCGDirectMainDisplay, CGRectMake(0, 0, 1, 1));
1545   return NS_OK;
1548 void nsCocoaUtils::ResolveVideoCapturePromises(bool aGranted) {
1549   // Resolve on main thread
1550   nsCOMPtr<nsIRunnable> runnable(
1551       NS_NewRunnableFunction("ResolveVideoCapturePromise", [aGranted]() {
1552         ResolveMediaCapturePromises(aGranted, sVideoCapturePromises);
1553       }));
1554   NS_DispatchToMainThread(runnable.forget());
1557 static PanGestureInput::PanGestureType PanGestureTypeForEvent(NSEvent* aEvent) {
1558   switch ([aEvent phase]) {
1559     case NSEventPhaseMayBegin:
1560       return PanGestureInput::PANGESTURE_MAYSTART;
1561     case NSEventPhaseCancelled:
1562       return PanGestureInput::PANGESTURE_CANCELLED;
1563     case NSEventPhaseBegan:
1564       return PanGestureInput::PANGESTURE_START;
1565     case NSEventPhaseChanged:
1566       return PanGestureInput::PANGESTURE_PAN;
1567     case NSEventPhaseEnded:
1568       return PanGestureInput::PANGESTURE_END;
1569     case NSEventPhaseNone:
1570       switch ([aEvent momentumPhase]) {
1571         case NSEventPhaseBegan:
1572           return PanGestureInput::PANGESTURE_MOMENTUMSTART;
1573         case NSEventPhaseChanged:
1574           return PanGestureInput::PANGESTURE_MOMENTUMPAN;
1575         case NSEventPhaseEnded:
1576           return PanGestureInput::PANGESTURE_MOMENTUMEND;
1577         default:
1578           NS_ERROR("unexpected event phase");
1579           return PanGestureInput::PANGESTURE_PAN;
1580       }
1581     default:
1582       NS_ERROR("unexpected event phase");
1583       return PanGestureInput::PANGESTURE_PAN;
1584   }
1587 bool static ShouldConsiderStartingSwipeFromEvent(NSEvent* anEvent) {
1588   // Only initiate horizontal tracking for gestures that have just begun --
1589   // otherwise a scroll to one side of the page can have a swipe tacked on
1590   // to it.
1591   // [NSEvent isSwipeTrackingFromScrollEventsEnabled] checks whether the
1592   // AppleEnableSwipeNavigateWithScrolls global preference is set.  If it isn't,
1593   // fluid swipe tracking is disabled, and a horizontal two-finger gesture is
1594   // always a scroll (even in Safari).  This preference can't (currently) be set
1595   // from the Preferences UI -- only using 'defaults write'.
1596   NSEventPhase eventPhase = [anEvent phase];
1597   return [anEvent type] == NSEventTypeScrollWheel &&
1598          eventPhase == NSEventPhaseBegan &&
1599          [anEvent hasPreciseScrollingDeltas] &&
1600          [NSEvent isSwipeTrackingFromScrollEventsEnabled];
1603 PanGestureInput nsCocoaUtils::CreatePanGestureEvent(
1604     NSEvent* aNativeEvent, TimeStamp aTimeStamp,
1605     const ScreenPoint& aPanStartPoint, const ScreenPoint& aPreciseDelta,
1606     const gfx::IntPoint& aLineOrPageDelta, Modifiers aModifiers) {
1607   PanGestureInput::PanGestureType type = PanGestureTypeForEvent(aNativeEvent);
1608   // Always force zero deltas on event types that shouldn't cause any scrolling,
1609   // so that we don't dispatch DOM wheel events for them.
1610   bool shouldIgnoreDeltas = type == PanGestureInput::PANGESTURE_MAYSTART ||
1611                             type == PanGestureInput::PANGESTURE_CANCELLED;
1613   PanGestureInput panEvent(
1614       type, aTimeStamp, aPanStartPoint,
1615       !shouldIgnoreDeltas ? aPreciseDelta : ScreenPoint(), aModifiers,
1616       PanGestureInput::IsEligibleForSwipe(
1617           ShouldConsiderStartingSwipeFromEvent(aNativeEvent)));
1619   if (!shouldIgnoreDeltas) {
1620     panEvent.SetLineOrPageDeltas(aLineOrPageDelta.x, aLineOrPageDelta.y);
1621   }
1623   return panEvent;
1626 bool nsCocoaUtils::IsValidPasteboardType(NSString* aAvailableType,
1627                                          bool aAllowFileURL) {
1628   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1630   // Prevent exposing fileURL for non-fileURL type.
1631   // We need URL provided by dropped webloc file, but don't need file's URL.
1632   // kUTTypeFileURL is returned by [NSPasteboard availableTypeFromArray:] for
1633   // kPublicUrlPboardType, since it conforms to kPublicUrlPboardType.
1634   bool isValid = true;
1635   if (!aAllowFileURL &&
1636       [aAvailableType
1637           isEqualToString:[UTIHelper
1638                               stringFromPboardType:(NSString*)
1639                                                        kUTTypeFileURL]]) {
1640     isValid = false;
1641   }
1643   return isValid;
1645   NS_OBJC_END_TRY_BLOCK_RETURN(false);
1648 NSString* nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1649     NSPasteboardItem* aItem, const NSString* aType, bool aAllowFileURL) {
1650   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1652   NSString* availableType =
1653       [aItem availableTypeFromArray:[NSArray arrayWithObjects:(id)aType, nil]];
1654   if (availableType && IsValidPasteboardType(availableType, aAllowFileURL)) {
1655     return [aItem stringForType:(id)availableType];
1656   }
1658   return nil;
1660   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
1663 NSString* nsCocoaUtils::GetFilePathFromPasteboardItem(NSPasteboardItem* aItem) {
1664   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1666   NSString* urlString = GetStringForTypeFromPasteboardItem(
1667       aItem, [UTIHelper stringFromPboardType:(NSString*)kUTTypeFileURL], true);
1668   if (urlString) {
1669     NSURL* url = [NSURL URLWithString:urlString];
1670     if (url) {
1671       return [url path];
1672     }
1673   }
1675   return nil;
1677   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
1680 NSString* nsCocoaUtils::GetTitleForURLFromPasteboardItem(
1681     NSPasteboardItem* item) {
1682   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
1684   NSString* name = nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1685       item, [UTIHelper stringFromPboardType:kPublicUrlNamePboardType]);
1686   if (name) {
1687     return name;
1688   }
1690   NSString* filePath = nsCocoaUtils::GetFilePathFromPasteboardItem(item);
1691   if (filePath) {
1692     return [filePath lastPathComponent];
1693   }
1695   return nil;
1697   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
1700 void nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem(
1701     nsITransferable* aTransferable, const nsCString& aFlavor,
1702     NSPasteboardItem* aItem) {
1703   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
1705   if (!aTransferable || !aItem) {
1706     return;
1707   }
1709   MOZ_LOG(gCocoaUtilsLog, LogLevel::Info,
1710           ("nsCocoaUtils::SetTransferDataForTypeFromPasteboardItem: looking "
1711            "for pasteboard data of "
1712            "type %s\n",
1713            aFlavor.get()));
1715   if (aFlavor.EqualsLiteral(kFileMime)) {
1716     NSString* filePath = nsCocoaUtils::GetFilePathFromPasteboardItem(aItem);
1717     if (!filePath) {
1718       return;
1719     }
1721     unsigned int stringLength = [filePath length];
1722     unsigned int dataLength =
1723         (stringLength + 1) * sizeof(char16_t);  // in bytes
1724     char16_t* clipboardDataPtr = (char16_t*)malloc(dataLength);
1725     if (!clipboardDataPtr) {
1726       return;
1727     }
1729     [filePath getCharacters:reinterpret_cast<unichar*>(clipboardDataPtr)];
1730     clipboardDataPtr[stringLength] = 0;  // null terminate
1732     nsCOMPtr<nsIFile> file;
1733     nsresult rv = NS_NewLocalFile(nsDependentString(clipboardDataPtr),
1734                                   getter_AddRefs(file));
1735     free(clipboardDataPtr);
1736     if (NS_FAILED(rv)) {
1737       return;
1738     }
1740     aTransferable->SetTransferData(aFlavor.get(), file);
1741     return;
1742   }
1744   if (aFlavor.EqualsLiteral(kCustomTypesMime)) {
1745     NSString* availableType = [aItem
1746         availableTypeFromArray:[NSArray
1747                                    arrayWithObject:kMozCustomTypesPboardType]];
1748     if (!availableType ||
1749         !nsCocoaUtils::IsValidPasteboardType(availableType, false)) {
1750       return;
1751     }
1752     NSData* pasteboardData = [aItem dataForType:availableType];
1753     if (!pasteboardData) {
1754       return;
1755     }
1757     unsigned int dataLength = [pasteboardData length];
1758     void* clipboardDataPtr = malloc(dataLength);
1759     if (!clipboardDataPtr) {
1760       return;
1761     }
1762     [pasteboardData getBytes:clipboardDataPtr length:dataLength];
1764     nsCOMPtr<nsISupports> genericDataWrapper;
1765     nsPrimitiveHelpers::CreatePrimitiveForData(
1766         aFlavor, clipboardDataPtr, dataLength,
1767         getter_AddRefs(genericDataWrapper));
1769     aTransferable->SetTransferData(aFlavor.get(), genericDataWrapper);
1770     free(clipboardDataPtr);
1771     return;
1772   }
1774   NSString* pString = nil;
1775   if (aFlavor.EqualsLiteral(kTextMime)) {
1776     pString = nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1777         aItem, [UTIHelper stringFromPboardType:NSPasteboardTypeString]);
1778   } else if (aFlavor.EqualsLiteral(kHTMLMime)) {
1779     pString = nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1780         aItem, [UTIHelper stringFromPboardType:NSPasteboardTypeHTML]);
1781   } else if (aFlavor.EqualsLiteral(kURLMime)) {
1782     pString = nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1783         aItem, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
1784     if (pString) {
1785       NSString* title = GetTitleForURLFromPasteboardItem(aItem);
1786       if (!title) {
1787         title = pString;
1788       }
1789       pString = [NSString stringWithFormat:@"%@\n%@", pString, title];
1790     }
1791   } else if (aFlavor.EqualsLiteral(kURLDataMime)) {
1792     pString = nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1793         aItem, [UTIHelper stringFromPboardType:kPublicUrlPboardType]);
1794   } else if (aFlavor.EqualsLiteral(kURLDescriptionMime)) {
1795     pString = GetTitleForURLFromPasteboardItem(aItem);
1796   } else if (aFlavor.EqualsLiteral(kRTFMime)) {
1797     pString = nsCocoaUtils::GetStringForTypeFromPasteboardItem(
1798         aItem, [UTIHelper stringFromPboardType:NSPasteboardTypeRTF]);
1799   }
1800   if (pString) {
1801     NSData* stringData;
1802     bool isRTF = aFlavor.EqualsLiteral(kRTFMime);
1803     if (isRTF) {
1804       stringData = [pString dataUsingEncoding:NSASCIIStringEncoding];
1805     } else {
1806       stringData = [pString dataUsingEncoding:NSUnicodeStringEncoding];
1807     }
1808     unsigned int dataLength = [stringData length];
1809     void* clipboardDataPtr = malloc(dataLength);
1810     if (!clipboardDataPtr) {
1811       return;
1812     }
1813     [stringData getBytes:clipboardDataPtr length:dataLength];
1815     // The DOM only wants LF, so convert from MacOS line endings to DOM line
1816     // endings.
1817     int32_t signedDataLength = dataLength;
1818     nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(isRTF, &clipboardDataPtr,
1819                                                        &signedDataLength);
1820     dataLength = signedDataLength;
1822     // skip BOM (Byte Order Mark to distinguish little or big endian)
1823     char16_t* clipboardDataPtrNoBOM = (char16_t*)clipboardDataPtr;
1824     if ((dataLength > 2) && ((clipboardDataPtrNoBOM[0] == 0xFEFF) ||
1825                              (clipboardDataPtrNoBOM[0] == 0xFFFE))) {
1826       dataLength -= sizeof(char16_t);
1827       clipboardDataPtrNoBOM += 1;
1828     }
1830     nsCOMPtr<nsISupports> genericDataWrapper;
1831     nsPrimitiveHelpers::CreatePrimitiveForData(
1832         aFlavor, clipboardDataPtrNoBOM, dataLength,
1833         getter_AddRefs(genericDataWrapper));
1834     aTransferable->SetTransferData(aFlavor.get(), genericDataWrapper);
1835     free(clipboardDataPtr);
1836     return;
1837   }
1839   // We have never supported this on Mac OS X, we should someday. Normally
1840   // dragging images in is accomplished with a file path drag instead of the
1841   // image data itself.
1842   /*
1843   if (aFlavor.EqualsLiteral(kPNGImageMime) ||
1844   aFlavor.EqualsLiteral(kJPEGImageMime) || aFlavor.EqualsLiteral(kJPGImageMime)
1845   || aFlavor.EqualsLiteral(kGIFImageMime)) {
1847   }
1848   */
1850   NS_OBJC_END_TRY_IGNORE_BLOCK;