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