toolkit/xre/MacApplicationDelegate.mm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 // NSApplication delegate for Mac OS X Cocoa API.
michael@0 7
michael@0 8 // As of 10.4 Tiger, the system can send six kinds of Apple Events to an application;
michael@0 9 // a well-behaved XUL app should have some kind of handling for all of them.
michael@0 10 //
michael@0 11 // See http://developer.apple.com/documentation/Cocoa/Conceptual/ScriptableCocoaApplications/SApps_handle_AEs/chapter_11_section_3.html for details.
michael@0 12
michael@0 13 #import <Cocoa/Cocoa.h>
michael@0 14 #import <Carbon/Carbon.h>
michael@0 15
michael@0 16 #include "nsCOMPtr.h"
michael@0 17 #include "nsINativeAppSupport.h"
michael@0 18 #include "nsAppRunner.h"
michael@0 19 #include "nsComponentManagerUtils.h"
michael@0 20 #include "nsIServiceManager.h"
michael@0 21 #include "nsServiceManagerUtils.h"
michael@0 22 #include "nsIAppStartup.h"
michael@0 23 #include "nsIObserverService.h"
michael@0 24 #include "nsISupportsPrimitives.h"
michael@0 25 #include "nsObjCExceptions.h"
michael@0 26 #include "nsIFile.h"
michael@0 27 #include "nsDirectoryServiceDefs.h"
michael@0 28 #include "nsICommandLineRunner.h"
michael@0 29 #include "nsIMacDockSupport.h"
michael@0 30 #include "nsIStandaloneNativeMenu.h"
michael@0 31 #include "nsILocalFileMac.h"
michael@0 32 #include "nsString.h"
michael@0 33 #include "nsCommandLineServiceMac.h"
michael@0 34
michael@0 35 class AutoAutoreleasePool {
michael@0 36 public:
michael@0 37 AutoAutoreleasePool()
michael@0 38 {
michael@0 39 mLocalPool = [[NSAutoreleasePool alloc] init];
michael@0 40 }
michael@0 41 ~AutoAutoreleasePool()
michael@0 42 {
michael@0 43 [mLocalPool release];
michael@0 44 }
michael@0 45 private:
michael@0 46 NSAutoreleasePool *mLocalPool;
michael@0 47 };
michael@0 48
michael@0 49 @interface MacApplicationDelegate : NSObject
michael@0 50 {
michael@0 51 }
michael@0 52
michael@0 53 @end
michael@0 54
michael@0 55 static bool sProcessedGetURLEvent = false;
michael@0 56
michael@0 57 @class GeckoNSApplication;
michael@0 58
michael@0 59 // Methods that can be called from non-Objective-C code.
michael@0 60
michael@0 61 // This is needed, on relaunch, to force the OS to use the "Cocoa Dock API"
michael@0 62 // instead of the "Carbon Dock API". For more info see bmo bug 377166.
michael@0 63 void
michael@0 64 EnsureUseCocoaDockAPI()
michael@0 65 {
michael@0 66 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 67
michael@0 68 [GeckoNSApplication sharedApplication];
michael@0 69
michael@0 70 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 71 }
michael@0 72
michael@0 73 void
michael@0 74 SetupMacApplicationDelegate()
michael@0 75 {
michael@0 76 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 77
michael@0 78 // this is called during startup, outside an event loop, and therefore
michael@0 79 // needs an autorelease pool to avoid cocoa object leakage (bug 559075)
michael@0 80 AutoAutoreleasePool pool;
michael@0 81
michael@0 82 // Ensure that ProcessPendingGetURLAppleEvents() doesn't regress bug 377166.
michael@0 83 [GeckoNSApplication sharedApplication];
michael@0 84
michael@0 85 // This call makes it so that application:openFile: doesn't get bogus calls
michael@0 86 // from Cocoa doing its own parsing of the argument string. And yes, we need
michael@0 87 // to use a string with a boolean value in it. That's just how it works.
michael@0 88 [[NSUserDefaults standardUserDefaults] setObject:@"NO"
michael@0 89 forKey:@"NSTreatUnknownArgumentsAsOpen"];
michael@0 90
michael@0 91 // Create the delegate. This should be around for the lifetime of the app.
michael@0 92 MacApplicationDelegate *delegate = [[MacApplicationDelegate alloc] init];
michael@0 93 [NSApp setDelegate:delegate];
michael@0 94
michael@0 95 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 96 }
michael@0 97
michael@0 98 // Indirectly make the OS process any pending GetURL Apple events. This is
michael@0 99 // done via _DPSNextEvent() (an undocumented AppKit function called from
michael@0 100 // [NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]). Apple
michael@0 101 // events are only processed if 'dequeue' is 'YES' -- so we need to call
michael@0 102 // [NSApplication sendEvent:] on any event that gets returned. 'event' will
michael@0 103 // never itself be an Apple event, and it may be 'nil' even when Apple events
michael@0 104 // are processed.
michael@0 105 void
michael@0 106 ProcessPendingGetURLAppleEvents()
michael@0 107 {
michael@0 108 AutoAutoreleasePool pool;
michael@0 109 bool keepSpinning = true;
michael@0 110 while (keepSpinning) {
michael@0 111 sProcessedGetURLEvent = false;
michael@0 112 NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
michael@0 113 untilDate:nil
michael@0 114 inMode:NSDefaultRunLoopMode
michael@0 115 dequeue:YES];
michael@0 116 if (event)
michael@0 117 [NSApp sendEvent:event];
michael@0 118 keepSpinning = sProcessedGetURLEvent;
michael@0 119 }
michael@0 120 }
michael@0 121
michael@0 122 @implementation MacApplicationDelegate
michael@0 123
michael@0 124 - (id)init
michael@0 125 {
michael@0 126 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 127
michael@0 128 if ((self = [super init])) {
michael@0 129 NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager];
michael@0 130
michael@0 131 [aeMgr setEventHandler:self
michael@0 132 andSelector:@selector(handleAppleEvent:withReplyEvent:)
michael@0 133 forEventClass:kInternetEventClass
michael@0 134 andEventID:kAEGetURL];
michael@0 135
michael@0 136 [aeMgr setEventHandler:self
michael@0 137 andSelector:@selector(handleAppleEvent:withReplyEvent:)
michael@0 138 forEventClass:'WWW!'
michael@0 139 andEventID:'OURL'];
michael@0 140
michael@0 141 [aeMgr setEventHandler:self
michael@0 142 andSelector:@selector(handleAppleEvent:withReplyEvent:)
michael@0 143 forEventClass:kCoreEventClass
michael@0 144 andEventID:kAEOpenDocuments];
michael@0 145
michael@0 146 if (![NSApp windowsMenu]) {
michael@0 147 // If the application has a windows menu, it will keep it up to date and
michael@0 148 // prepend the window list to the Dock menu automatically.
michael@0 149 NSMenu* windowsMenu = [[NSMenu alloc] initWithTitle:@"Window"];
michael@0 150 [NSApp setWindowsMenu:windowsMenu];
michael@0 151 [windowsMenu release];
michael@0 152 }
michael@0 153 }
michael@0 154 return self;
michael@0 155
michael@0 156 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nil);
michael@0 157 }
michael@0 158
michael@0 159 - (void)dealloc
michael@0 160 {
michael@0 161 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
michael@0 162
michael@0 163 NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager];
michael@0 164 [aeMgr removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL];
michael@0 165 [aeMgr removeEventHandlerForEventClass:'WWW!' andEventID:'OURL'];
michael@0 166 [aeMgr removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
michael@0 167 [super dealloc];
michael@0 168
michael@0 169 NS_OBJC_END_TRY_ABORT_BLOCK;
michael@0 170 }
michael@0 171
michael@0 172 // The method that NSApplication calls upon a request to reopen, such as when
michael@0 173 // the Dock icon is clicked and no windows are open. A "visible" window may be
michael@0 174 // miniaturized, so we can't skip nsCocoaNativeReOpen() if 'flag' is 'true'.
michael@0 175 - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApp hasVisibleWindows:(BOOL)flag
michael@0 176 {
michael@0 177 nsCOMPtr<nsINativeAppSupport> nas = do_CreateInstance(NS_NATIVEAPPSUPPORT_CONTRACTID);
michael@0 178 NS_ENSURE_TRUE(nas, NO);
michael@0 179
michael@0 180 // Go to the common Carbon/Cocoa reopen method.
michael@0 181 nsresult rv = nas->ReOpen();
michael@0 182 NS_ENSURE_SUCCESS(rv, NO);
michael@0 183
michael@0 184 // NO says we don't want NSApplication to do anything else for us.
michael@0 185 return NO;
michael@0 186 }
michael@0 187
michael@0 188 // The method that NSApplication calls when documents are requested to be opened.
michael@0 189 // It will be called once for each selected document.
michael@0 190 - (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename
michael@0 191 {
michael@0 192 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
michael@0 193
michael@0 194 NSURL *url = [NSURL fileURLWithPath:filename];
michael@0 195 if (!url)
michael@0 196 return NO;
michael@0 197
michael@0 198 NSString *urlString = [url absoluteString];
michael@0 199 if (!urlString)
michael@0 200 return NO;
michael@0 201
michael@0 202 // Add the URL to any command line we're currently setting up.
michael@0 203 if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String]))
michael@0 204 return YES;
michael@0 205
michael@0 206 nsCOMPtr<nsILocalFileMac> inFile;
michael@0 207 nsresult rv = NS_NewLocalFileWithCFURL((CFURLRef)url, true, getter_AddRefs(inFile));
michael@0 208 if (NS_FAILED(rv))
michael@0 209 return NO;
michael@0 210
michael@0 211 nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
michael@0 212 if (!cmdLine) {
michael@0 213 NS_ERROR("Couldn't create command line!");
michael@0 214 return NO;
michael@0 215 }
michael@0 216
michael@0 217 nsCString filePath;
michael@0 218 rv = inFile->GetNativePath(filePath);
michael@0 219 if (NS_FAILED(rv))
michael@0 220 return NO;
michael@0 221
michael@0 222 nsCOMPtr<nsIFile> workingDir;
michael@0 223 rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir));
michael@0 224 if (NS_FAILED(rv))
michael@0 225 return NO;
michael@0 226
michael@0 227 const char *argv[3] = {nullptr, "-file", filePath.get()};
michael@0 228 rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT);
michael@0 229 if (NS_FAILED(rv))
michael@0 230 return NO;
michael@0 231
michael@0 232 if (NS_SUCCEEDED(cmdLine->Run()))
michael@0 233 return YES;
michael@0 234
michael@0 235 return NO;
michael@0 236
michael@0 237 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
michael@0 238 }
michael@0 239
michael@0 240 // The method that NSApplication calls when documents are requested to be printed
michael@0 241 // from the Finder (under the "File" menu).
michael@0 242 // It will be called once for each selected document.
michael@0 243 - (BOOL)application:(NSApplication*)theApplication printFile:(NSString*)filename
michael@0 244 {
michael@0 245 return NO;
michael@0 246 }
michael@0 247
michael@0 248 // Create the menu that shows up in the Dock.
michael@0 249 - (NSMenu*)applicationDockMenu:(NSApplication*)sender
michael@0 250 {
michael@0 251 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
michael@0 252
michael@0 253 // Create the NSMenu that will contain the dock menu items.
michael@0 254 NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
michael@0 255 [menu setAutoenablesItems:NO];
michael@0 256
michael@0 257 // Add application-specific dock menu items. On error, do not insert the
michael@0 258 // dock menu items.
michael@0 259 nsresult rv;
michael@0 260 nsCOMPtr<nsIMacDockSupport> dockSupport = do_GetService("@mozilla.org/widget/macdocksupport;1", &rv);
michael@0 261 if (NS_FAILED(rv) || !dockSupport)
michael@0 262 return menu;
michael@0 263
michael@0 264 nsCOMPtr<nsIStandaloneNativeMenu> dockMenu;
michael@0 265 rv = dockSupport->GetDockMenu(getter_AddRefs(dockMenu));
michael@0 266 if (NS_FAILED(rv) || !dockMenu)
michael@0 267 return menu;
michael@0 268
michael@0 269 // Determine if the dock menu items should be displayed. This also gives
michael@0 270 // the menu the opportunity to update itself before display.
michael@0 271 bool shouldShowItems;
michael@0 272 rv = dockMenu->MenuWillOpen(&shouldShowItems);
michael@0 273 if (NS_FAILED(rv) || !shouldShowItems)
michael@0 274 return menu;
michael@0 275
michael@0 276 // Obtain a copy of the native menu.
michael@0 277 NSMenu * nativeDockMenu;
michael@0 278 rv = dockMenu->GetNativeMenu(reinterpret_cast<void **>(&nativeDockMenu));
michael@0 279 if (NS_FAILED(rv) || !nativeDockMenu)
michael@0 280 return menu;
michael@0 281
michael@0 282 // Loop through the application-specific dock menu and insert its
michael@0 283 // contents into the dock menu that we are building for Cocoa.
michael@0 284 int numDockMenuItems = [nativeDockMenu numberOfItems];
michael@0 285 if (numDockMenuItems > 0) {
michael@0 286 if ([menu numberOfItems] > 0)
michael@0 287 [menu addItem:[NSMenuItem separatorItem]];
michael@0 288
michael@0 289 for (int i = 0; i < numDockMenuItems; i++) {
michael@0 290 NSMenuItem * itemCopy = [[nativeDockMenu itemAtIndex:i] copy];
michael@0 291 [menu addItem:itemCopy];
michael@0 292 [itemCopy release];
michael@0 293 }
michael@0 294 }
michael@0 295
michael@0 296 return menu;
michael@0 297
michael@0 298 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
michael@0 299 }
michael@0 300
michael@0 301 // If we don't handle applicationShouldTerminate:, a call to [NSApp terminate:]
michael@0 302 // (from the browser or from the OS) can result in an unclean shutdown.
michael@0 303 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
michael@0 304 {
michael@0 305 nsCOMPtr<nsIObserverService> obsServ =
michael@0 306 do_GetService("@mozilla.org/observer-service;1");
michael@0 307 if (!obsServ)
michael@0 308 return NSTerminateNow;
michael@0 309
michael@0 310 nsCOMPtr<nsISupportsPRBool> cancelQuit =
michael@0 311 do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID);
michael@0 312 if (!cancelQuit)
michael@0 313 return NSTerminateNow;
michael@0 314
michael@0 315 cancelQuit->SetData(false);
michael@0 316 obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr);
michael@0 317
michael@0 318 bool abortQuit;
michael@0 319 cancelQuit->GetData(&abortQuit);
michael@0 320 if (abortQuit)
michael@0 321 return NSTerminateCancel;
michael@0 322
michael@0 323 nsCOMPtr<nsIAppStartup> appService =
michael@0 324 do_GetService("@mozilla.org/toolkit/app-startup;1");
michael@0 325 if (appService)
michael@0 326 appService->Quit(nsIAppStartup::eForceQuit);
michael@0 327
michael@0 328 return NSTerminateNow;
michael@0 329 }
michael@0 330
michael@0 331 - (void)handleAppleEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
michael@0 332 {
michael@0 333 if (!event)
michael@0 334 return;
michael@0 335
michael@0 336 AutoAutoreleasePool pool;
michael@0 337
michael@0 338 bool isGetURLEvent =
michael@0 339 ([event eventClass] == kInternetEventClass && [event eventID] == kAEGetURL);
michael@0 340 if (isGetURLEvent)
michael@0 341 sProcessedGetURLEvent = true;
michael@0 342
michael@0 343 if (isGetURLEvent ||
michael@0 344 ([event eventClass] == 'WWW!' && [event eventID] == 'OURL')) {
michael@0 345 NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
michael@0 346
michael@0 347 // don't open chrome URLs
michael@0 348 NSString* schemeString = [[NSURL URLWithString:urlString] scheme];
michael@0 349 if (!schemeString ||
michael@0 350 [schemeString compare:@"chrome"
michael@0 351 options:NSCaseInsensitiveSearch
michael@0 352 range:NSMakeRange(0, [schemeString length])] == NSOrderedSame) {
michael@0 353 return;
michael@0 354 }
michael@0 355
michael@0 356 // Add the URL to any command line we're currently setting up.
michael@0 357 if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String]))
michael@0 358 return;
michael@0 359
michael@0 360 nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1"));
michael@0 361 if (!cmdLine) {
michael@0 362 NS_ERROR("Couldn't create command line!");
michael@0 363 return;
michael@0 364 }
michael@0 365 nsCOMPtr<nsIFile> workingDir;
michael@0 366 nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir));
michael@0 367 if (NS_FAILED(rv))
michael@0 368 return;
michael@0 369 const char *argv[3] = {nullptr, "-url", [urlString UTF8String]};
michael@0 370 rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT);
michael@0 371 if (NS_FAILED(rv))
michael@0 372 return;
michael@0 373 rv = cmdLine->Run();
michael@0 374 }
michael@0 375 else if ([event eventClass] == kCoreEventClass && [event eventID] == kAEOpenDocuments) {
michael@0 376 NSAppleEventDescriptor* fileListDescriptor = [event paramDescriptorForKeyword:keyDirectObject];
michael@0 377 if (!fileListDescriptor)
michael@0 378 return;
michael@0 379
michael@0 380 // Descriptor list indexing is one-based...
michael@0 381 NSInteger numberOfFiles = [fileListDescriptor numberOfItems];
michael@0 382 for (NSInteger i = 1; i <= numberOfFiles; i++) {
michael@0 383 NSString* urlString = [[fileListDescriptor descriptorAtIndex:i] stringValue];
michael@0 384 if (!urlString)
michael@0 385 continue;
michael@0 386
michael@0 387 // We need a path, not a URL
michael@0 388 NSURL* url = [NSURL URLWithString:urlString];
michael@0 389 if (!url)
michael@0 390 continue;
michael@0 391
michael@0 392 [self application:NSApp openFile:[url path]];
michael@0 393 }
michael@0 394 }
michael@0 395 }
michael@0 396
michael@0 397 @end

mercurial