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