PageRenderTime 29ms CodeModel.GetById 18ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 1ms

/toolkit/xre/MacApplicationDelegate.mm

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