1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/xre/MacApplicationDelegate.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,397 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +// NSApplication delegate for Mac OS X Cocoa API. 1.10 + 1.11 +// As of 10.4 Tiger, the system can send six kinds of Apple Events to an application; 1.12 +// a well-behaved XUL app should have some kind of handling for all of them. 1.13 +// 1.14 +// See http://developer.apple.com/documentation/Cocoa/Conceptual/ScriptableCocoaApplications/SApps_handle_AEs/chapter_11_section_3.html for details. 1.15 + 1.16 +#import <Cocoa/Cocoa.h> 1.17 +#import <Carbon/Carbon.h> 1.18 + 1.19 +#include "nsCOMPtr.h" 1.20 +#include "nsINativeAppSupport.h" 1.21 +#include "nsAppRunner.h" 1.22 +#include "nsComponentManagerUtils.h" 1.23 +#include "nsIServiceManager.h" 1.24 +#include "nsServiceManagerUtils.h" 1.25 +#include "nsIAppStartup.h" 1.26 +#include "nsIObserverService.h" 1.27 +#include "nsISupportsPrimitives.h" 1.28 +#include "nsObjCExceptions.h" 1.29 +#include "nsIFile.h" 1.30 +#include "nsDirectoryServiceDefs.h" 1.31 +#include "nsICommandLineRunner.h" 1.32 +#include "nsIMacDockSupport.h" 1.33 +#include "nsIStandaloneNativeMenu.h" 1.34 +#include "nsILocalFileMac.h" 1.35 +#include "nsString.h" 1.36 +#include "nsCommandLineServiceMac.h" 1.37 + 1.38 +class AutoAutoreleasePool { 1.39 +public: 1.40 + AutoAutoreleasePool() 1.41 + { 1.42 + mLocalPool = [[NSAutoreleasePool alloc] init]; 1.43 + } 1.44 + ~AutoAutoreleasePool() 1.45 + { 1.46 + [mLocalPool release]; 1.47 + } 1.48 +private: 1.49 + NSAutoreleasePool *mLocalPool; 1.50 +}; 1.51 + 1.52 +@interface MacApplicationDelegate : NSObject 1.53 +{ 1.54 +} 1.55 + 1.56 +@end 1.57 + 1.58 +static bool sProcessedGetURLEvent = false; 1.59 + 1.60 +@class GeckoNSApplication; 1.61 + 1.62 +// Methods that can be called from non-Objective-C code. 1.63 + 1.64 +// This is needed, on relaunch, to force the OS to use the "Cocoa Dock API" 1.65 +// instead of the "Carbon Dock API". For more info see bmo bug 377166. 1.66 +void 1.67 +EnsureUseCocoaDockAPI() 1.68 +{ 1.69 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.70 + 1.71 + [GeckoNSApplication sharedApplication]; 1.72 + 1.73 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.74 +} 1.75 + 1.76 +void 1.77 +SetupMacApplicationDelegate() 1.78 +{ 1.79 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.80 + 1.81 + // this is called during startup, outside an event loop, and therefore 1.82 + // needs an autorelease pool to avoid cocoa object leakage (bug 559075) 1.83 + AutoAutoreleasePool pool; 1.84 + 1.85 + // Ensure that ProcessPendingGetURLAppleEvents() doesn't regress bug 377166. 1.86 + [GeckoNSApplication sharedApplication]; 1.87 + 1.88 + // This call makes it so that application:openFile: doesn't get bogus calls 1.89 + // from Cocoa doing its own parsing of the argument string. And yes, we need 1.90 + // to use a string with a boolean value in it. That's just how it works. 1.91 + [[NSUserDefaults standardUserDefaults] setObject:@"NO" 1.92 + forKey:@"NSTreatUnknownArgumentsAsOpen"]; 1.93 + 1.94 + // Create the delegate. This should be around for the lifetime of the app. 1.95 + MacApplicationDelegate *delegate = [[MacApplicationDelegate alloc] init]; 1.96 + [NSApp setDelegate:delegate]; 1.97 + 1.98 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.99 +} 1.100 + 1.101 +// Indirectly make the OS process any pending GetURL Apple events. This is 1.102 +// done via _DPSNextEvent() (an undocumented AppKit function called from 1.103 +// [NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]). Apple 1.104 +// events are only processed if 'dequeue' is 'YES' -- so we need to call 1.105 +// [NSApplication sendEvent:] on any event that gets returned. 'event' will 1.106 +// never itself be an Apple event, and it may be 'nil' even when Apple events 1.107 +// are processed. 1.108 +void 1.109 +ProcessPendingGetURLAppleEvents() 1.110 +{ 1.111 + AutoAutoreleasePool pool; 1.112 + bool keepSpinning = true; 1.113 + while (keepSpinning) { 1.114 + sProcessedGetURLEvent = false; 1.115 + NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask 1.116 + untilDate:nil 1.117 + inMode:NSDefaultRunLoopMode 1.118 + dequeue:YES]; 1.119 + if (event) 1.120 + [NSApp sendEvent:event]; 1.121 + keepSpinning = sProcessedGetURLEvent; 1.122 + } 1.123 +} 1.124 + 1.125 +@implementation MacApplicationDelegate 1.126 + 1.127 +- (id)init 1.128 +{ 1.129 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.130 + 1.131 + if ((self = [super init])) { 1.132 + NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager]; 1.133 + 1.134 + [aeMgr setEventHandler:self 1.135 + andSelector:@selector(handleAppleEvent:withReplyEvent:) 1.136 + forEventClass:kInternetEventClass 1.137 + andEventID:kAEGetURL]; 1.138 + 1.139 + [aeMgr setEventHandler:self 1.140 + andSelector:@selector(handleAppleEvent:withReplyEvent:) 1.141 + forEventClass:'WWW!' 1.142 + andEventID:'OURL']; 1.143 + 1.144 + [aeMgr setEventHandler:self 1.145 + andSelector:@selector(handleAppleEvent:withReplyEvent:) 1.146 + forEventClass:kCoreEventClass 1.147 + andEventID:kAEOpenDocuments]; 1.148 + 1.149 + if (![NSApp windowsMenu]) { 1.150 + // If the application has a windows menu, it will keep it up to date and 1.151 + // prepend the window list to the Dock menu automatically. 1.152 + NSMenu* windowsMenu = [[NSMenu alloc] initWithTitle:@"Window"]; 1.153 + [NSApp setWindowsMenu:windowsMenu]; 1.154 + [windowsMenu release]; 1.155 + } 1.156 + } 1.157 + return self; 1.158 + 1.159 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nil); 1.160 +} 1.161 + 1.162 +- (void)dealloc 1.163 +{ 1.164 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.165 + 1.166 + NSAppleEventManager *aeMgr = [NSAppleEventManager sharedAppleEventManager]; 1.167 + [aeMgr removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; 1.168 + [aeMgr removeEventHandlerForEventClass:'WWW!' andEventID:'OURL']; 1.169 + [aeMgr removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; 1.170 + [super dealloc]; 1.171 + 1.172 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.173 +} 1.174 + 1.175 +// The method that NSApplication calls upon a request to reopen, such as when 1.176 +// the Dock icon is clicked and no windows are open. A "visible" window may be 1.177 +// miniaturized, so we can't skip nsCocoaNativeReOpen() if 'flag' is 'true'. 1.178 +- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApp hasVisibleWindows:(BOOL)flag 1.179 +{ 1.180 + nsCOMPtr<nsINativeAppSupport> nas = do_CreateInstance(NS_NATIVEAPPSUPPORT_CONTRACTID); 1.181 + NS_ENSURE_TRUE(nas, NO); 1.182 + 1.183 + // Go to the common Carbon/Cocoa reopen method. 1.184 + nsresult rv = nas->ReOpen(); 1.185 + NS_ENSURE_SUCCESS(rv, NO); 1.186 + 1.187 + // NO says we don't want NSApplication to do anything else for us. 1.188 + return NO; 1.189 +} 1.190 + 1.191 +// The method that NSApplication calls when documents are requested to be opened. 1.192 +// It will be called once for each selected document. 1.193 +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename 1.194 +{ 1.195 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.196 + 1.197 + NSURL *url = [NSURL fileURLWithPath:filename]; 1.198 + if (!url) 1.199 + return NO; 1.200 + 1.201 + NSString *urlString = [url absoluteString]; 1.202 + if (!urlString) 1.203 + return NO; 1.204 + 1.205 + // Add the URL to any command line we're currently setting up. 1.206 + if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String])) 1.207 + return YES; 1.208 + 1.209 + nsCOMPtr<nsILocalFileMac> inFile; 1.210 + nsresult rv = NS_NewLocalFileWithCFURL((CFURLRef)url, true, getter_AddRefs(inFile)); 1.211 + if (NS_FAILED(rv)) 1.212 + return NO; 1.213 + 1.214 + nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1")); 1.215 + if (!cmdLine) { 1.216 + NS_ERROR("Couldn't create command line!"); 1.217 + return NO; 1.218 + } 1.219 + 1.220 + nsCString filePath; 1.221 + rv = inFile->GetNativePath(filePath); 1.222 + if (NS_FAILED(rv)) 1.223 + return NO; 1.224 + 1.225 + nsCOMPtr<nsIFile> workingDir; 1.226 + rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); 1.227 + if (NS_FAILED(rv)) 1.228 + return NO; 1.229 + 1.230 + const char *argv[3] = {nullptr, "-file", filePath.get()}; 1.231 + rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT); 1.232 + if (NS_FAILED(rv)) 1.233 + return NO; 1.234 + 1.235 + if (NS_SUCCEEDED(cmdLine->Run())) 1.236 + return YES; 1.237 + 1.238 + return NO; 1.239 + 1.240 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); 1.241 +} 1.242 + 1.243 +// The method that NSApplication calls when documents are requested to be printed 1.244 +// from the Finder (under the "File" menu). 1.245 +// It will be called once for each selected document. 1.246 +- (BOOL)application:(NSApplication*)theApplication printFile:(NSString*)filename 1.247 +{ 1.248 + return NO; 1.249 +} 1.250 + 1.251 +// Create the menu that shows up in the Dock. 1.252 +- (NSMenu*)applicationDockMenu:(NSApplication*)sender 1.253 +{ 1.254 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.255 + 1.256 + // Create the NSMenu that will contain the dock menu items. 1.257 + NSMenu *menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; 1.258 + [menu setAutoenablesItems:NO]; 1.259 + 1.260 + // Add application-specific dock menu items. On error, do not insert the 1.261 + // dock menu items. 1.262 + nsresult rv; 1.263 + nsCOMPtr<nsIMacDockSupport> dockSupport = do_GetService("@mozilla.org/widget/macdocksupport;1", &rv); 1.264 + if (NS_FAILED(rv) || !dockSupport) 1.265 + return menu; 1.266 + 1.267 + nsCOMPtr<nsIStandaloneNativeMenu> dockMenu; 1.268 + rv = dockSupport->GetDockMenu(getter_AddRefs(dockMenu)); 1.269 + if (NS_FAILED(rv) || !dockMenu) 1.270 + return menu; 1.271 + 1.272 + // Determine if the dock menu items should be displayed. This also gives 1.273 + // the menu the opportunity to update itself before display. 1.274 + bool shouldShowItems; 1.275 + rv = dockMenu->MenuWillOpen(&shouldShowItems); 1.276 + if (NS_FAILED(rv) || !shouldShowItems) 1.277 + return menu; 1.278 + 1.279 + // Obtain a copy of the native menu. 1.280 + NSMenu * nativeDockMenu; 1.281 + rv = dockMenu->GetNativeMenu(reinterpret_cast<void **>(&nativeDockMenu)); 1.282 + if (NS_FAILED(rv) || !nativeDockMenu) 1.283 + return menu; 1.284 + 1.285 + // Loop through the application-specific dock menu and insert its 1.286 + // contents into the dock menu that we are building for Cocoa. 1.287 + int numDockMenuItems = [nativeDockMenu numberOfItems]; 1.288 + if (numDockMenuItems > 0) { 1.289 + if ([menu numberOfItems] > 0) 1.290 + [menu addItem:[NSMenuItem separatorItem]]; 1.291 + 1.292 + for (int i = 0; i < numDockMenuItems; i++) { 1.293 + NSMenuItem * itemCopy = [[nativeDockMenu itemAtIndex:i] copy]; 1.294 + [menu addItem:itemCopy]; 1.295 + [itemCopy release]; 1.296 + } 1.297 + } 1.298 + 1.299 + return menu; 1.300 + 1.301 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.302 +} 1.303 + 1.304 +// If we don't handle applicationShouldTerminate:, a call to [NSApp terminate:] 1.305 +// (from the browser or from the OS) can result in an unclean shutdown. 1.306 +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 1.307 +{ 1.308 + nsCOMPtr<nsIObserverService> obsServ = 1.309 + do_GetService("@mozilla.org/observer-service;1"); 1.310 + if (!obsServ) 1.311 + return NSTerminateNow; 1.312 + 1.313 + nsCOMPtr<nsISupportsPRBool> cancelQuit = 1.314 + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); 1.315 + if (!cancelQuit) 1.316 + return NSTerminateNow; 1.317 + 1.318 + cancelQuit->SetData(false); 1.319 + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr); 1.320 + 1.321 + bool abortQuit; 1.322 + cancelQuit->GetData(&abortQuit); 1.323 + if (abortQuit) 1.324 + return NSTerminateCancel; 1.325 + 1.326 + nsCOMPtr<nsIAppStartup> appService = 1.327 + do_GetService("@mozilla.org/toolkit/app-startup;1"); 1.328 + if (appService) 1.329 + appService->Quit(nsIAppStartup::eForceQuit); 1.330 + 1.331 + return NSTerminateNow; 1.332 +} 1.333 + 1.334 +- (void)handleAppleEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent 1.335 +{ 1.336 + if (!event) 1.337 + return; 1.338 + 1.339 + AutoAutoreleasePool pool; 1.340 + 1.341 + bool isGetURLEvent = 1.342 + ([event eventClass] == kInternetEventClass && [event eventID] == kAEGetURL); 1.343 + if (isGetURLEvent) 1.344 + sProcessedGetURLEvent = true; 1.345 + 1.346 + if (isGetURLEvent || 1.347 + ([event eventClass] == 'WWW!' && [event eventID] == 'OURL')) { 1.348 + NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; 1.349 + 1.350 + // don't open chrome URLs 1.351 + NSString* schemeString = [[NSURL URLWithString:urlString] scheme]; 1.352 + if (!schemeString || 1.353 + [schemeString compare:@"chrome" 1.354 + options:NSCaseInsensitiveSearch 1.355 + range:NSMakeRange(0, [schemeString length])] == NSOrderedSame) { 1.356 + return; 1.357 + } 1.358 + 1.359 + // Add the URL to any command line we're currently setting up. 1.360 + if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String])) 1.361 + return; 1.362 + 1.363 + nsCOMPtr<nsICommandLineRunner> cmdLine(do_CreateInstance("@mozilla.org/toolkit/command-line;1")); 1.364 + if (!cmdLine) { 1.365 + NS_ERROR("Couldn't create command line!"); 1.366 + return; 1.367 + } 1.368 + nsCOMPtr<nsIFile> workingDir; 1.369 + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); 1.370 + if (NS_FAILED(rv)) 1.371 + return; 1.372 + const char *argv[3] = {nullptr, "-url", [urlString UTF8String]}; 1.373 + rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT); 1.374 + if (NS_FAILED(rv)) 1.375 + return; 1.376 + rv = cmdLine->Run(); 1.377 + } 1.378 + else if ([event eventClass] == kCoreEventClass && [event eventID] == kAEOpenDocuments) { 1.379 + NSAppleEventDescriptor* fileListDescriptor = [event paramDescriptorForKeyword:keyDirectObject]; 1.380 + if (!fileListDescriptor) 1.381 + return; 1.382 + 1.383 + // Descriptor list indexing is one-based... 1.384 + NSInteger numberOfFiles = [fileListDescriptor numberOfItems]; 1.385 + for (NSInteger i = 1; i <= numberOfFiles; i++) { 1.386 + NSString* urlString = [[fileListDescriptor descriptorAtIndex:i] stringValue]; 1.387 + if (!urlString) 1.388 + continue; 1.389 + 1.390 + // We need a path, not a URL 1.391 + NSURL* url = [NSURL URLWithString:urlString]; 1.392 + if (!url) 1.393 + continue; 1.394 + 1.395 + [self application:NSApp openFile:[url path]]; 1.396 + } 1.397 + } 1.398 +} 1.399 + 1.400 +@end