Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #import "chrome/browser/app_controller_mac.h"
      6 
      7 #include "base/auto_reset.h"
      8 #include "base/command_line.h"
      9 #include "base/file_path.h"
     10 #include "base/mac/mac_util.h"
     11 #include "base/message_loop.h"
     12 #include "base/string_number_conversions.h"
     13 #include "base/sys_string_conversions.h"
     14 #include "chrome/app/chrome_command_ids.h"
     15 #include "chrome/browser/background_application_list_model.h"
     16 #include "chrome/browser/browser_process.h"
     17 #include "chrome/browser/browser_shutdown.h"
     18 #include "chrome/browser/command_updater.h"
     19 #include "chrome/browser/download/download_manager.h"
     20 #include "chrome/browser/instant/instant_confirm_dialog.h"
     21 #include "chrome/browser/metrics/user_metrics.h"
     22 #include "chrome/browser/prefs/pref_service.h"
     23 #include "chrome/browser/printing/print_job_manager.h"
     24 #include "chrome/browser/profiles/profile_manager.h"
     25 #include "chrome/browser/sessions/session_service.h"
     26 #include "chrome/browser/sessions/tab_restore_service.h"
     27 #include "chrome/browser/sync/profile_sync_service.h"
     28 #include "chrome/browser/sync/sync_ui_util.h"
     29 #include "chrome/browser/sync/sync_ui_util_mac.h"
     30 #include "chrome/browser/ui/browser.h"
     31 #include "chrome/browser/ui/browser_init.h"
     32 #include "chrome/browser/ui/browser_list.h"
     33 #include "chrome/browser/ui/browser_window.h"
     34 #import "chrome/browser/ui/cocoa/about_window_controller.h"
     35 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
     36 #import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
     37 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
     38 #import "chrome/browser/ui/cocoa/bug_report_window_controller.h"
     39 #import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
     40 #import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h"
     41 #import "chrome/browser/ui/cocoa/history_menu_bridge.h"
     42 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
     43 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
     44 #include "chrome/browser/ui/cocoa/task_manager_mac.h"
     45 #include "chrome/browser/ui/options/options_window.h"
     46 #include "chrome/common/app_mode_common_mac.h"
     47 #include "chrome/common/chrome_paths_internal.h"
     48 #include "chrome/common/chrome_switches.h"
     49 #include "chrome/common/pref_names.h"
     50 #include "chrome/common/url_constants.h"
     51 #include "content/browser/browser_thread.h"
     52 #include "content/browser/tab_contents/tab_contents.h"
     53 #include "content/common/notification_service.h"
     54 #include "grit/chromium_strings.h"
     55 #include "grit/generated_resources.h"
     56 #include "net/base/net_util.h"
     57 #include "ui/base/l10n/l10n_util.h"
     58 #include "ui/base/l10n/l10n_util_mac.h"
     59 #include "ui/base/models/accelerator_cocoa.h"
     60 
     61 // 10.6 adds a public API for the Spotlight-backed search menu item in the Help
     62 // menu.  Provide the declaration so it can be called below when building with
     63 // the 10.5 SDK.
     64 #if !defined(MAC_OS_X_VERSION_10_6) || \
     65     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
     66 @interface NSApplication (SnowLeopardSDKDeclarations)
     67 - (void)setHelpMenu:(NSMenu*)helpMenu;
     68 @end
     69 #endif
     70 
     71 namespace {
     72 
     73 // True while AppController is calling Browser::OpenEmptyWindow(). We need a
     74 // global flag here, analogue to BrowserInit::InProcessStartup() because
     75 // otherwise the SessionService will try to restore sessions when we make a new
     76 // window while there are no other active windows.
     77 bool g_is_opening_new_window = false;
     78 
     79 // Activates a browser window having the given profile (the last one active) if
     80 // possible and returns a pointer to the activate |Browser| or NULL if this was
     81 // not possible. If the last active browser is minimized (in particular, if
     82 // there are only minimized windows), it will unminimize it.
     83 Browser* ActivateBrowser(Profile* profile) {
     84   Browser* browser = BrowserList::GetLastActiveWithProfile(profile);
     85   if (browser)
     86     browser->window()->Activate();
     87   return browser;
     88 }
     89 
     90 // Creates an empty browser window with the given profile and returns a pointer
     91 // to the new |Browser|.
     92 Browser* CreateBrowser(Profile* profile) {
     93   {
     94     AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
     95     Browser::OpenEmptyWindow(profile);
     96   }
     97 
     98   Browser* browser = BrowserList::GetLastActive();
     99   CHECK(browser);
    100   return browser;
    101 }
    102 
    103 // Activates a browser window having the given profile (the last one active) if
    104 // possible or creates an empty one if necessary. Returns a pointer to the
    105 // activated/new |Browser|.
    106 Browser* ActivateOrCreateBrowser(Profile* profile) {
    107   if (Browser* browser = ActivateBrowser(profile))
    108     return browser;
    109   return CreateBrowser(profile);
    110 }
    111 
    112 // This task synchronizes preferences (under "org.chromium.Chromium" or
    113 // "com.google.Chrome"), in particular, writes them out to disk.
    114 class PrefsSyncTask : public Task {
    115  public:
    116   PrefsSyncTask() {}
    117   virtual ~PrefsSyncTask() {}
    118   virtual void Run() {
    119     if (!CFPreferencesAppSynchronize(app_mode::kAppPrefsID))
    120       LOG(WARNING) << "Error recording application bundle path.";
    121   }
    122 };
    123 
    124 // Record the location of the application bundle (containing the main framework)
    125 // from which Chromium was loaded. This is used by app mode shims to find
    126 // Chromium.
    127 void RecordLastRunAppBundlePath() {
    128   // Going up three levels from |chrome::GetVersionedDirectory()| gives the
    129   // real, user-visible app bundle directory. (The alternatives give either the
    130   // framework's path or the initial app's path, which may be an app mode shim
    131   // or a unit test.)
    132   FilePath appBundlePath =
    133       chrome::GetVersionedDirectory().DirName().DirName().DirName();
    134   CFPreferencesSetAppValue(app_mode::kLastRunAppBundlePathPrefsKey,
    135                            base::SysUTF8ToCFStringRef(appBundlePath.value()),
    136                            app_mode::kAppPrefsID);
    137 
    138   // Sync after a delay avoid I/O contention on startup; 1500 ms is plenty.
    139   BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE,
    140                                  new PrefsSyncTask(), 1500);
    141 }
    142 
    143 }  // anonymous namespace
    144 
    145 @interface AppController (Private)
    146 - (void)initMenuState;
    147 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item;
    148 - (void)registerServicesMenuTypesTo:(NSApplication*)app;
    149 - (void)openUrls:(const std::vector<GURL>&)urls;
    150 - (void)getUrl:(NSAppleEventDescriptor*)event
    151      withReply:(NSAppleEventDescriptor*)reply;
    152 - (void)windowLayeringDidChange:(NSNotification*)inNotification;
    153 - (void)checkForAnyKeyWindows;
    154 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
    155 - (BOOL)shouldQuitWithInProgressDownloads;
    156 - (void)executeApplication:(id)sender;
    157 @end
    158 
    159 @implementation AppController
    160 
    161 @synthesize startupComplete = startupComplete_;
    162 
    163 // This method is called very early in application startup (ie, before
    164 // the profile is loaded or any preferences have been registered). Defer any
    165 // user-data initialization until -applicationDidFinishLaunching:.
    166 - (void)awakeFromNib {
    167   // We need to register the handlers early to catch events fired on launch.
    168   NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
    169   [em setEventHandler:self
    170           andSelector:@selector(getUrl:withReply:)
    171         forEventClass:kInternetEventClass
    172            andEventID:kAEGetURL];
    173   [em setEventHandler:self
    174           andSelector:@selector(getUrl:withReply:)
    175         forEventClass:'WWW!'    // A particularly ancient AppleEvent that dates
    176            andEventID:'OURL'];  // back to the Spyglass days.
    177 
    178   // Register for various window layering changes. We use these to update
    179   // various UI elements (command-key equivalents, etc) when the frontmost
    180   // window changes.
    181   NSNotificationCenter* notificationCenter =
    182       [NSNotificationCenter defaultCenter];
    183   [notificationCenter
    184       addObserver:self
    185          selector:@selector(windowLayeringDidChange:)
    186              name:NSWindowDidBecomeKeyNotification
    187            object:nil];
    188   [notificationCenter
    189       addObserver:self
    190          selector:@selector(windowLayeringDidChange:)
    191              name:NSWindowDidResignKeyNotification
    192            object:nil];
    193   [notificationCenter
    194       addObserver:self
    195          selector:@selector(windowLayeringDidChange:)
    196              name:NSWindowDidBecomeMainNotification
    197            object:nil];
    198   [notificationCenter
    199       addObserver:self
    200          selector:@selector(windowLayeringDidChange:)
    201              name:NSWindowDidResignMainNotification
    202            object:nil];
    203 
    204   // Register for a notification that the number of tabs changes in windows
    205   // so we can adjust the close tab/window command keys.
    206   [notificationCenter
    207       addObserver:self
    208          selector:@selector(tabsChanged:)
    209              name:kTabStripNumberOfTabsChanged
    210            object:nil];
    211 
    212   // Set up the command updater for when there are no windows open
    213   [self initMenuState];
    214 
    215   // Activate (bring to foreground) if asked to do so.  On
    216   // Windows this logic isn't necessary since
    217   // BrowserWindow::Activate() calls ::SetForegroundWindow() which is
    218   // adequate.  On Mac, BrowserWindow::Activate() calls -[NSWindow
    219   // makeKeyAndOrderFront:] which does not activate the application
    220   // itself.
    221   const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    222   if (parsed_command_line.HasSwitch(switches::kActivateOnLaunch)) {
    223     [NSApp activateIgnoringOtherApps:YES];
    224   }
    225 }
    226 
    227 // (NSApplicationDelegate protocol) This is the Apple-approved place to override
    228 // the default handlers.
    229 - (void)applicationWillFinishLaunching:(NSNotification*)notification {
    230   // Nothing here right now.
    231 }
    232 
    233 - (BOOL)tryToTerminateApplication:(NSApplication*)app {
    234   // Check for in-process downloads, and prompt the user if they really want
    235   // to quit (and thus cancel downloads). Only check if we're not already
    236   // shutting down, else the user might be prompted multiple times if the
    237   // download isn't stopped before terminate is called again.
    238   if (!browser_shutdown::IsTryingToQuit() &&
    239       ![self shouldQuitWithInProgressDownloads])
    240     return NO;
    241 
    242   // TODO(viettrungluu): Remove Apple Event handlers here? (It's safe to leave
    243   // them in, but I'm not sure about UX; we'd also want to disable other things
    244   // though.) http://crbug.com/40861
    245 
    246   // Check if the user really wants to quit by employing the confirm-to-quit
    247   // mechanism.
    248   if (!browser_shutdown::IsTryingToQuit() &&
    249       [self applicationShouldTerminate:app] != NSTerminateNow)
    250     return NO;
    251 
    252   size_t num_browsers = BrowserList::size();
    253 
    254   // Give any print jobs in progress time to finish.
    255   if (!browser_shutdown::IsTryingToQuit())
    256     g_browser_process->print_job_manager()->StopJobs(true);
    257 
    258   // Initiate a shutdown (via BrowserList::CloseAllBrowsers()) if we aren't
    259   // already shutting down.
    260   if (!browser_shutdown::IsTryingToQuit())
    261     BrowserList::CloseAllBrowsers();
    262 
    263   return num_browsers == 0 ? YES : NO;
    264 }
    265 
    266 - (void)stopTryingToTerminateApplication:(NSApplication*)app {
    267   if (browser_shutdown::IsTryingToQuit()) {
    268     // Reset the "trying to quit" state, so that closing all browser windows
    269     // will no longer lead to termination.
    270     browser_shutdown::SetTryingToQuit(false);
    271 
    272     // TODO(viettrungluu): Were we to remove Apple Event handlers above, we
    273     // would have to reinstall them here. http://crbug.com/40861
    274   }
    275 }
    276 
    277 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app {
    278   // Check if the preference is turned on.
    279   const PrefService* prefs = [self defaultProfile]->GetPrefs();
    280   if (!prefs->GetBoolean(prefs::kConfirmToQuitEnabled)) {
    281     confirm_quit::RecordHistogram(confirm_quit::kNoConfirm);
    282     return NSTerminateNow;
    283   }
    284 
    285   // If the application is going to terminate as the result of a Cmd+Q
    286   // invocation, use the special sauce to prevent accidental quitting.
    287   // http://dev.chromium.org/developers/design-documents/confirm-to-quit-experiment
    288 
    289   // This logic is only for keyboard-initiated quits.
    290   if (![ConfirmQuitPanelController eventTriggersFeature:[app currentEvent]])
    291     return NSTerminateNow;
    292 
    293   return [[ConfirmQuitPanelController sharedController]
    294       runModalLoopForApplication:app];
    295 }
    296 
    297 // Called when the app is shutting down. Clean-up as appropriate.
    298 - (void)applicationWillTerminate:(NSNotification*)aNotification {
    299   NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
    300   [em removeEventHandlerForEventClass:kInternetEventClass
    301                            andEventID:kAEGetURL];
    302   [em removeEventHandlerForEventClass:'WWW!'
    303                            andEventID:'OURL'];
    304 
    305   // There better be no browser windows left at this point.
    306   CHECK_EQ(BrowserList::size(), 0u);
    307 
    308   // Tell BrowserList not to keep the browser process alive. Once all the
    309   // browsers get dealloc'd, it will stop the RunLoop and fall back into main().
    310   BrowserList::EndKeepAlive();
    311 
    312   // Close these off if they have open windows.
    313   [aboutController_ close];
    314 
    315   [[NSNotificationCenter defaultCenter] removeObserver:self];
    316 }
    317 
    318 - (void)didEndMainMessageLoop {
    319   DCHECK(!BrowserList::HasBrowserWithProfile([self defaultProfile]));
    320   if (!BrowserList::HasBrowserWithProfile([self defaultProfile])) {
    321     // As we're shutting down, we need to nuke the TabRestoreService, which
    322     // will start the shutdown of the NavigationControllers and allow for
    323     // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
    324     // and may end up crashing when some thread tries to use the IO thread (or
    325     // another thread) that is no longer valid.
    326     [self defaultProfile]->ResetTabRestoreService();
    327   }
    328 }
    329 
    330 // Helper routine to get the window controller if the key window is a tabbed
    331 // window, or nil if not. Examples of non-tabbed windows are "about" or
    332 // "preferences".
    333 - (TabWindowController*)keyWindowTabController {
    334   NSWindowController* keyWindowController =
    335       [[NSApp keyWindow] windowController];
    336   if ([keyWindowController isKindOfClass:[TabWindowController class]])
    337     return (TabWindowController*)keyWindowController;
    338 
    339   return nil;
    340 }
    341 
    342 // Helper routine to get the window controller if the main window is a tabbed
    343 // window, or nil if not. Examples of non-tabbed windows are "about" or
    344 // "preferences".
    345 - (TabWindowController*)mainWindowTabController {
    346   NSWindowController* mainWindowController =
    347       [[NSApp mainWindow] windowController];
    348   if ([mainWindowController isKindOfClass:[TabWindowController class]])
    349     return (TabWindowController*)mainWindowController;
    350 
    351   return nil;
    352 }
    353 
    354 // If the window has tabs, make "close window" be cmd-shift-w, otherwise leave
    355 // it as the normal cmd-w. Capitalization of the key equivalent affects whether
    356 // the shift modifer is used.
    357 - (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)inHaveTabs {
    358   [closeWindowMenuItem_ setKeyEquivalent:(inHaveTabs ? @"W" : @"w")];
    359   [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
    360 }
    361 
    362 // If the window has tabs, make "close tab" take over cmd-w, otherwise it
    363 // shouldn't have any key-equivalent because it should be disabled.
    364 - (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)hasTabs {
    365   if (hasTabs) {
    366     [closeTabMenuItem_ setKeyEquivalent:@"w"];
    367     [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
    368   } else {
    369     [closeTabMenuItem_ setKeyEquivalent:@""];
    370     [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
    371   }
    372 }
    373 
    374 // Explicitly remove any command-key equivalents from the close tab/window
    375 // menus so that nothing can go haywire if we get a user action during pending
    376 // updates.
    377 - (void)clearCloseMenuItemKeyEquivalents {
    378   [closeTabMenuItem_ setKeyEquivalent:@""];
    379   [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
    380   [closeWindowMenuItem_ setKeyEquivalent:@""];
    381   [closeWindowMenuItem_ setKeyEquivalentModifierMask:0];
    382 }
    383 
    384 // See if we have a window with tabs open, and adjust the key equivalents for
    385 // Close Tab/Close Window accordingly.
    386 - (void)fixCloseMenuItemKeyEquivalents {
    387   fileMenuUpdatePending_ = NO;
    388   TabWindowController* tabController = [self keyWindowTabController];
    389   if (!tabController && ![NSApp keyWindow]) {
    390     // There might be a small amount of time where there is no key window,
    391     // so just use our main browser window if there is one.
    392     tabController = [self mainWindowTabController];
    393   }
    394   BOOL windowWithMultipleTabs =
    395       (tabController && [tabController numberOfTabs] > 1);
    396   [self adjustCloseWindowMenuItemKeyEquivalent:windowWithMultipleTabs];
    397   [self adjustCloseTabMenuItemKeyEquivalent:windowWithMultipleTabs];
    398 }
    399 
    400 // Fix up the "close tab/close window" command-key equivalents. We do this
    401 // after a delay to ensure that window layer state has been set by the time
    402 // we do the enabling. This should only be called on the main thread, code that
    403 // calls this (even as a side-effect) from other threads needs to be fixed.
    404 - (void)delayedFixCloseMenuItemKeyEquivalents {
    405   DCHECK([NSThread isMainThread]);
    406   if (!fileMenuUpdatePending_) {
    407     // The OS prefers keypresses to timers, so it's possible that a cmd-w
    408     // can sneak in before this timer fires. In order to prevent that from
    409     // having any bad consequences, just clear the keys combos altogether. They
    410     // will be reset when the timer eventually fires.
    411     if ([NSThread isMainThread]) {
    412       fileMenuUpdatePending_ = YES;
    413       [self clearCloseMenuItemKeyEquivalents];
    414       [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
    415                  withObject:nil
    416                  afterDelay:0];
    417     } else {
    418       // This shouldn't be happening, but if it does, force it to the main
    419       // thread to avoid dropping the update. Don't mess with
    420       // |fileMenuUpdatePending_| as it's not expected to be threadsafe and
    421       // there could be a race between the selector finishing and setting the
    422       // flag.
    423       [self
    424           performSelectorOnMainThread:@selector(fixCloseMenuItemKeyEquivalents)
    425                            withObject:nil
    426                         waitUntilDone:NO];
    427     }
    428   }
    429 }
    430 
    431 // Called when we get a notification about the window layering changing to
    432 // update the UI based on the new main window.
    433 - (void)windowLayeringDidChange:(NSNotification*)notify {
    434   [self delayedFixCloseMenuItemKeyEquivalents];
    435 
    436   if ([notify name] == NSWindowDidResignKeyNotification) {
    437     // If a window is closed, this notification is fired but |[NSApp keyWindow]|
    438     // returns nil regardless of whether any suitable candidates for the key
    439     // window remain. It seems that the new key window for the app is not set
    440     // until after this notification is fired, so a check is performed after the
    441     // run loop is allowed to spin.
    442     [self performSelector:@selector(checkForAnyKeyWindows)
    443                withObject:nil
    444                afterDelay:0.0];
    445   }
    446 }
    447 
    448 - (void)checkForAnyKeyWindows {
    449   if ([NSApp keyWindow])
    450     return;
    451 
    452   NotificationService::current()->Notify(
    453       NotificationType::NO_KEY_WINDOW,
    454       NotificationService::AllSources(),
    455       NotificationService::NoDetails());
    456 }
    457 
    458 // Called when the number of tabs changes in one of the browser windows. The
    459 // object is the tab strip controller, but we don't currently care.
    460 - (void)tabsChanged:(NSNotification*)notify {
    461   // We don't need to do this on a delay, as in the method above, because the
    462   // window layering isn't changing. As a result, there's no chance that a
    463   // different window will sneak in as the key window and cause the problems
    464   // we hacked around above by clearing the key equivalents.
    465   [self fixCloseMenuItemKeyEquivalents];
    466 }
    467 
    468 // If the auto-update interval is not set, make it 5 hours.
    469 // This code is specific to Mac Chrome Dev Channel.
    470 // Placed here for 2 reasons:
    471 // 1) Same spot as other Pref stuff
    472 // 2) Try and be friendly by keeping this after app launch
    473 // TODO(jrg): remove once we go Beta.
    474 - (void)setUpdateCheckInterval {
    475 #if defined(GOOGLE_CHROME_BUILD)
    476   CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent";
    477   CFStringRef checkInterval = (CFStringRef)@"checkInterval";
    478   CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
    479   if (!plist) {
    480     const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
    481     NSNumber* value = [NSNumber numberWithFloat:fiveHoursInSeconds];
    482     CFPreferencesSetAppValue(checkInterval, value, app);
    483     CFPreferencesAppSynchronize(app);
    484   }
    485 #endif
    486 }
    487 
    488 // This is called after profiles have been loaded and preferences registered.
    489 // It is safe to access the default profile here.
    490 - (void)applicationDidFinishLaunching:(NSNotification*)notify {
    491   // Notify BrowserList to keep the application running so it doesn't go away
    492   // when all the browser windows get closed.
    493   BrowserList::StartKeepAlive();
    494 
    495   bookmarkMenuBridge_.reset(new BookmarkMenuBridge([self defaultProfile]));
    496   historyMenuBridge_.reset(new HistoryMenuBridge([self defaultProfile]));
    497 
    498   [self setUpdateCheckInterval];
    499 
    500   // Build up the encoding menu, the order of the items differs based on the
    501   // current locale (see http://crbug.com/7647 for details).
    502   // We need a valid g_browser_process to get the profile which is why we can't
    503   // call this from awakeFromNib.
    504   NSMenu* viewMenu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
    505   NSMenuItem* encodingMenuItem = [viewMenu itemWithTag:IDC_ENCODING_MENU];
    506   NSMenu* encodingMenu = [encodingMenuItem submenu];
    507   EncodingMenuControllerDelegate::BuildEncodingMenu([self defaultProfile],
    508                                                     encodingMenu);
    509 
    510   // Since Chrome is localized to more languages than the OS, tell Cocoa which
    511   // menu is the Help so it can add the search item to it.
    512   if (helpMenu_ && [NSApp respondsToSelector:@selector(setHelpMenu:)])
    513     [NSApp setHelpMenu:helpMenu_];
    514 
    515   // Record the path to the (browser) app bundle; this is used by the app mode
    516   // shim.
    517   RecordLastRunAppBundlePath();
    518 
    519   // Makes "Services" menu items available.
    520   [self registerServicesMenuTypesTo:[notify object]];
    521 
    522   startupComplete_ = YES;
    523 
    524   // TODO(viettrungluu): This is very temporary, since this should be done "in"
    525   // |BrowserMain()|, i.e., this list of startup URLs should be appended to the
    526   // (probably-empty) list of URLs from the command line.
    527   if (startupUrls_.size()) {
    528     [self openUrls:startupUrls_];
    529     [self clearStartupUrls];
    530   }
    531 
    532   const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
    533   if (!parsed_command_line.HasSwitch(switches::kEnableExposeForTabs)) {
    534     [tabposeMenuItem_ setHidden:YES];
    535   }
    536 }
    537 
    538 // This is called after profiles have been loaded and preferences registered.
    539 // It is safe to access the default profile here.
    540 - (void)applicationDidBecomeActive:(NSNotification*)notify {
    541   NotificationService::current()->Notify(NotificationType::APP_ACTIVATED,
    542                                          NotificationService::AllSources(),
    543                                          NotificationService::NoDetails());
    544 }
    545 
    546 // Helper function for populating and displaying the in progress downloads at
    547 // exit alert panel.
    548 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
    549   NSString* warningText = nil;
    550   NSString* explanationText = nil;
    551   NSString* waitTitle = nil;
    552   NSString* exitTitle = nil;
    553 
    554   string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
    555 
    556   // Set the dialog text based on whether or not there are multiple downloads.
    557   if (downloadCount == 1) {
    558     // Dialog text: warning and explanation.
    559     warningText = l10n_util::GetNSStringF(
    560         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_WARNING, product_name);
    561     explanationText = l10n_util::GetNSStringF(
    562         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION, product_name);
    563 
    564     // Cancel download and exit button text.
    565     exitTitle = l10n_util::GetNSString(
    566         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL);
    567 
    568     // Wait for download button text.
    569     waitTitle = l10n_util::GetNSString(
    570         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
    571   } else {
    572     // Dialog text: warning and explanation.
    573     warningText = l10n_util::GetNSStringF(
    574         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_WARNING, product_name,
    575         base::IntToString16(downloadCount));
    576     explanationText = l10n_util::GetNSStringF(
    577         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION, product_name);
    578 
    579     // Cancel downloads and exit button text.
    580     exitTitle = l10n_util::GetNSString(
    581         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_OK_BUTTON_LABEL);
    582 
    583     // Wait for downloads button text.
    584     waitTitle = l10n_util::GetNSString(
    585         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
    586   }
    587 
    588   // 'waitButton' is the default choice.
    589   int choice = NSRunAlertPanel(warningText, explanationText,
    590                                waitTitle, exitTitle, nil);
    591   return choice == NSAlertDefaultReturn ? YES : NO;
    592 }
    593 
    594 // Check all profiles for in progress downloads, and if we find any, prompt the
    595 // user to see if we should continue to exit (and thus cancel the downloads), or
    596 // if we should wait.
    597 - (BOOL)shouldQuitWithInProgressDownloads {
    598   ProfileManager* profile_manager = g_browser_process->profile_manager();
    599   if (!profile_manager)
    600     return YES;
    601 
    602   std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
    603   for (size_t i = 0; i < profiles.size(); ++i) {
    604     DownloadManager* download_manager = profiles[i]->GetDownloadManager();
    605     if (download_manager && download_manager->in_progress_count() > 0) {
    606       int downloadCount = download_manager->in_progress_count();
    607       if ([self userWillWaitForInProgressDownloads:downloadCount]) {
    608         // Create a new browser window (if necessary) and navigate to the
    609         // downloads page if the user chooses to wait.
    610         Browser* browser = BrowserList::FindBrowserWithProfile(profiles[i]);
    611         if (!browser) {
    612           browser = Browser::Create(profiles[i]);
    613           browser->window()->Show();
    614         }
    615         DCHECK(browser);
    616         browser->ShowDownloadsTab();
    617         return NO;
    618       }
    619 
    620       // User wants to exit.
    621       return YES;
    622     }
    623   }
    624 
    625   // No profiles or active downloads found, okay to exit.
    626   return YES;
    627 }
    628 
    629 // Called to determine if we should enable the "restore tab" menu item.
    630 // Checks with the TabRestoreService to see if there's anything there to
    631 // restore and returns YES if so.
    632 - (BOOL)canRestoreTab {
    633   TabRestoreService* service = [self defaultProfile]->GetTabRestoreService();
    634   return service && !service->entries().empty();
    635 }
    636 
    637 // Returns true if there is not a modal window (either window- or application-
    638 // modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
    639 // sheets) will not count as blocking the browser. But things like open/save
    640 // dialogs that are window modal will block the browser.
    641 - (BOOL)keyWindowIsNotModal {
    642   Browser* browser = BrowserList::GetLastActive();
    643   return [NSApp modalWindow] == nil && (!browser ||
    644          ![[browser->window()->GetNativeHandle() attachedSheet]
    645              isKindOfClass:[NSWindow class]]);
    646 }
    647 
    648 // Called to validate menu items when there are no key windows. All the
    649 // items we care about have been set with the |commandDispatch:| action and
    650 // a target of FirstResponder in IB. If it's not one of those, let it
    651 // continue up the responder chain to be handled elsewhere. We pull out the
    652 // tag as the cross-platform constant to differentiate and dispatch the
    653 // various commands.
    654 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
    655   SEL action = [item action];
    656   BOOL enable = NO;
    657   if (action == @selector(commandDispatch:)) {
    658     NSInteger tag = [item tag];
    659     if (menuState_->SupportsCommand(tag)) {
    660       switch (tag) {
    661         // The File Menu commands are not automatically disabled by Cocoa when a
    662         // dialog sheet obscures the browser window, so we disable several of
    663         // them here.  We don't need to include IDC_CLOSE_WINDOW, because
    664         // app_controller is only activated when there are no key windows (see
    665         // function comment).
    666         case IDC_RESTORE_TAB:
    667           enable = [self keyWindowIsNotModal] && [self canRestoreTab];
    668           break;
    669         // Browser-level items that open in new tabs should not open if there's
    670         // a window- or app-modal dialog.
    671         case IDC_OPEN_FILE:
    672         case IDC_NEW_TAB:
    673         case IDC_SHOW_HISTORY:
    674         case IDC_SHOW_BOOKMARK_MANAGER:
    675           enable = [self keyWindowIsNotModal];
    676           break;
    677         // Browser-level items that open in new windows.
    678         case IDC_NEW_WINDOW:
    679         case IDC_TASK_MANAGER:
    680           // Allow the user to open a new window if there's a window-modal
    681           // dialog.
    682           enable = [self keyWindowIsNotModal] || ([NSApp modalWindow] == nil);
    683           break;
    684         case IDC_SYNC_BOOKMARKS: {
    685           Profile* defaultProfile = [self defaultProfile];
    686           // The profile may be NULL during shutdown -- see
    687           // http://code.google.com/p/chromium/issues/detail?id=43048 .
    688           //
    689           // TODO(akalin,viettrungluu): Figure out whether this method
    690           // can be prevented from being called if defaultProfile is
    691           // NULL.
    692           if (!defaultProfile) {
    693             LOG(WARNING)
    694                 << "NULL defaultProfile detected -- not doing anything";
    695             break;
    696           }
    697           enable = defaultProfile->IsSyncAccessible() &&
    698               [self keyWindowIsNotModal];
    699           sync_ui_util::UpdateSyncItem(item, enable, defaultProfile);
    700           break;
    701         }
    702         default:
    703           enable = menuState_->IsCommandEnabled(tag) ?
    704                    [self keyWindowIsNotModal] : NO;
    705       }
    706     }
    707   } else if (action == @selector(terminate:)) {
    708     enable = YES;
    709   } else if (action == @selector(showPreferences:)) {
    710     enable = YES;
    711   } else if (action == @selector(orderFrontStandardAboutPanel:)) {
    712     enable = YES;
    713   } else if (action == @selector(commandFromDock:)) {
    714     enable = YES;
    715   } else if (action == @selector(toggleConfirmToQuit:)) {
    716     [self updateConfirmToQuitPrefMenuItem:static_cast<NSMenuItem*>(item)];
    717     enable = YES;
    718   }
    719   return enable;
    720 }
    721 
    722 // Called when the user picks a menu item when there are no key windows, or when
    723 // there is no foreground browser window. Calls through to the browser object to
    724 // execute the command. This assumes that the command is supported and doesn't
    725 // check, otherwise it should have been disabled in the UI in
    726 // |-validateUserInterfaceItem:|.
    727 - (void)commandDispatch:(id)sender {
    728   Profile* defaultProfile = [self defaultProfile];
    729 
    730   // Handle the case where we're dispatching a command from a sender that's in a
    731   // browser window. This means that the command came from a background window
    732   // and is getting here because the foreground window is not a browser window.
    733   if ([sender respondsToSelector:@selector(window)]) {
    734     id delegate = [[sender window] windowController];
    735     if ([delegate isKindOfClass:[BrowserWindowController class]]) {
    736       [delegate commandDispatch:sender];
    737       return;
    738     }
    739   }
    740 
    741   NSInteger tag = [sender tag];
    742   switch (tag) {
    743     case IDC_NEW_TAB:
    744       // Create a new tab in an existing browser window (which we activate) if
    745       // possible.
    746       if (Browser* browser = ActivateBrowser(defaultProfile)) {
    747         browser->ExecuteCommand(IDC_NEW_TAB);
    748         break;
    749       }
    750       // Else fall through to create new window.
    751     case IDC_NEW_WINDOW:
    752       CreateBrowser(defaultProfile);
    753       break;
    754     case IDC_FOCUS_LOCATION:
    755       ActivateOrCreateBrowser(defaultProfile)->
    756           ExecuteCommand(IDC_FOCUS_LOCATION);
    757       break;
    758     case IDC_FOCUS_SEARCH:
    759       ActivateOrCreateBrowser(defaultProfile)->ExecuteCommand(IDC_FOCUS_SEARCH);
    760       break;
    761     case IDC_NEW_INCOGNITO_WINDOW:
    762       Browser::OpenEmptyWindow(defaultProfile->GetOffTheRecordProfile());
    763       break;
    764     case IDC_RESTORE_TAB:
    765       Browser::OpenWindowWithRestoredTabs(defaultProfile);
    766       break;
    767     case IDC_OPEN_FILE:
    768       CreateBrowser(defaultProfile)->ExecuteCommand(IDC_OPEN_FILE);
    769       break;
    770     case IDC_CLEAR_BROWSING_DATA: {
    771       // There may not be a browser open, so use the default profile.
    772       if (Browser* browser = ActivateBrowser(defaultProfile)) {
    773         browser->OpenClearBrowsingDataDialog();
    774       } else {
    775         Browser::OpenClearBrowingDataDialogWindow(defaultProfile);
    776       }
    777       break;
    778     }
    779     case IDC_IMPORT_SETTINGS: {
    780       if (Browser* browser = ActivateBrowser(defaultProfile)) {
    781         browser->OpenImportSettingsDialog();
    782       } else {
    783         Browser::OpenImportSettingsDialogWindow(defaultProfile);
    784       }
    785       break;
    786     }
    787     case IDC_SHOW_BOOKMARK_MANAGER:
    788       UserMetrics::RecordAction(UserMetricsAction("ShowBookmarkManager"),
    789                                 defaultProfile);
    790       if (Browser* browser = ActivateBrowser(defaultProfile)) {
    791         // Open a bookmark manager tab.
    792         browser->OpenBookmarkManager();
    793       } else {
    794         // No browser window, so create one for the bookmark manager tab.
    795         Browser::OpenBookmarkManagerWindow(defaultProfile);
    796       }
    797       break;
    798     case IDC_SHOW_HISTORY:
    799       if (Browser* browser = ActivateBrowser(defaultProfile))
    800         browser->ShowHistoryTab();
    801       else
    802         Browser::OpenHistoryWindow(defaultProfile);
    803       break;
    804     case IDC_SHOW_DOWNLOADS:
    805       if (Browser* browser = ActivateBrowser(defaultProfile))
    806         browser->ShowDownloadsTab();
    807       else
    808         Browser::OpenDownloadsWindow(defaultProfile);
    809       break;
    810     case IDC_MANAGE_EXTENSIONS:
    811       if (Browser* browser = ActivateBrowser(defaultProfile))
    812         browser->ShowExtensionsTab();
    813       else
    814         Browser::OpenExtensionsWindow(defaultProfile);
    815       break;
    816     case IDC_HELP_PAGE:
    817       if (Browser* browser = ActivateBrowser(defaultProfile))
    818         browser->OpenHelpTab();
    819       else
    820         Browser::OpenHelpWindow(defaultProfile);
    821       break;
    822     case IDC_FEEDBACK: {
    823       Browser* browser = BrowserList::GetLastActive();
    824       TabContents* currentTab =
    825           browser ? browser->GetSelectedTabContents() : NULL;
    826       BugReportWindowController* controller =
    827           [[BugReportWindowController alloc]
    828               initWithTabContents:currentTab
    829                           profile:[self defaultProfile]];
    830       [controller runModalDialog];
    831       break;
    832     }
    833     case IDC_SYNC_BOOKMARKS:
    834       // The profile may be NULL during shutdown -- see
    835       // http://code.google.com/p/chromium/issues/detail?id=43048 .
    836       //
    837       // TODO(akalin,viettrungluu): Figure out whether this method can
    838       // be prevented from being called if defaultProfile is NULL.
    839       if (!defaultProfile) {
    840         LOG(WARNING) << "NULL defaultProfile detected -- not doing anything";
    841         break;
    842       }
    843       // TODO(akalin): Add a constant to denote starting sync from the
    844       // main menu and use that instead of START_FROM_WRENCH.
    845       sync_ui_util::OpenSyncMyBookmarksDialog(
    846           defaultProfile, ActivateBrowser(defaultProfile),
    847           ProfileSyncService::START_FROM_WRENCH);
    848       break;
    849     case IDC_TASK_MANAGER:
    850       UserMetrics::RecordAction(UserMetricsAction("TaskManager"),
    851                                 defaultProfile);
    852       TaskManagerMac::Show(false);
    853       break;
    854     case IDC_OPTIONS:
    855       [self showPreferences:sender];
    856       break;
    857     default:
    858       // Background Applications use dynamic values that must be less than the
    859       // smallest value among the predefined IDC_* labels.
    860       if ([sender tag] < IDC_MinimumLabelValue)
    861         [self executeApplication:sender];
    862       break;
    863   }
    864 }
    865 
    866 // Run a (background) application in a new tab.
    867 - (void)executeApplication:(id)sender {
    868   NSInteger tag = [sender tag];
    869   Profile* profile = [self defaultProfile];
    870   DCHECK(profile);
    871   BackgroundApplicationListModel applications(profile);
    872   DCHECK(tag >= 0 &&
    873          tag < static_cast<int>(applications.size()));
    874   Browser* browser = BrowserList::GetLastActive();
    875   if (!browser) {
    876     Browser::OpenEmptyWindow(profile);
    877     browser = BrowserList::GetLastActive();
    878   }
    879   const Extension* extension = applications.GetExtension(tag);
    880   browser->OpenApplicationTab(profile, extension, NULL);
    881 }
    882 
    883 // Same as |-commandDispatch:|, but executes commands using a disposition
    884 // determined by the key flags. This will get called in the case where the
    885 // frontmost window is not a browser window, and the user has command-clicked
    886 // a button in a background browser window whose action is
    887 // |-commandDispatchUsingKeyModifiers:|
    888 - (void)commandDispatchUsingKeyModifiers:(id)sender {
    889   DCHECK(sender);
    890   if ([sender respondsToSelector:@selector(window)]) {
    891     id delegate = [[sender window] windowController];
    892     if ([delegate isKindOfClass:[BrowserWindowController class]]) {
    893       [delegate commandDispatchUsingKeyModifiers:sender];
    894     }
    895   }
    896 }
    897 
    898 // NSApplication delegate method called when someone clicks on the
    899 // dock icon and there are no open windows.  To match standard mac
    900 // behavior, we should open a new window.
    901 - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
    902                     hasVisibleWindows:(BOOL)flag {
    903   // If the browser is currently trying to quit, don't do anything and return NO
    904   // to prevent AppKit from doing anything.
    905   // TODO(rohitrao): Remove this code when http://crbug.com/40861 is resolved.
    906   if (browser_shutdown::IsTryingToQuit())
    907     return NO;
    908 
    909   // Don't do anything if there are visible windows.  This will cause
    910   // AppKit to unminimize the most recently minimized window.
    911   if (flag)
    912     return YES;
    913 
    914   // If launched as a hidden login item (due to installation of a persistent app
    915   // or by the user, for example in System Preferenecs->Accounts->Login Items),
    916   // allow session to be restored first time the user clicks on a Dock icon.
    917   // Normally, it'd just open a new empty page.
    918   {
    919       static BOOL doneOnce = NO;
    920       if (!doneOnce) {
    921         doneOnce = YES;
    922         if (base::mac::WasLaunchedAsHiddenLoginItem()) {
    923           SessionService* sessionService =
    924               [self defaultProfile]->GetSessionService();
    925           if (sessionService &&
    926               sessionService->RestoreIfNecessary(std::vector<GURL>()))
    927             return NO;
    928         }
    929       }
    930   }
    931   // Otherwise open a new window.
    932   {
    933     AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
    934     Browser::OpenEmptyWindow([self defaultProfile]);
    935   }
    936 
    937   // We've handled the reopen event, so return NO to tell AppKit not
    938   // to do anything.
    939   return NO;
    940 }
    941 
    942 - (void)initMenuState {
    943   menuState_.reset(new CommandUpdater(NULL));
    944   menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
    945   menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
    946   menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
    947   menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
    948   menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
    949   menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
    950   menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
    951   menuState_->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true);
    952   menuState_->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true);
    953   menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
    954   menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
    955   menuState_->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true);
    956   menuState_->UpdateCommandEnabled(IDC_HELP_PAGE, true);
    957   menuState_->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true);
    958   menuState_->UpdateCommandEnabled(IDC_FEEDBACK, true);
    959   menuState_->UpdateCommandEnabled(IDC_SYNC_BOOKMARKS, true);
    960   menuState_->UpdateCommandEnabled(IDC_TASK_MANAGER, true);
    961 }
    962 
    963 // The Confirm to Quit preference is atypical in that the preference lives in
    964 // the app menu right above the Quit menu item. This method will refresh the
    965 // display of that item depending on the preference state.
    966 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item {
    967   // Format the string so that the correct key equivalent is displayed.
    968   NSString* acceleratorString = [ConfirmQuitPanelController keyCommandString];
    969   NSString* title = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_OPTION,
    970       base::SysNSStringToUTF16(acceleratorString));
    971   [item setTitle:title];
    972 
    973   const PrefService* prefService = [self defaultProfile]->GetPrefs();
    974   bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
    975   [item setState:enabled ? NSOnState : NSOffState];
    976 }
    977 
    978 - (void)registerServicesMenuTypesTo:(NSApplication*)app {
    979   // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which
    980   // handles requests from services.
    981   NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
    982   [app registerServicesMenuSendTypes:types returnTypes:types];
    983 }
    984 
    985 - (Profile*)defaultProfile {
    986   if (g_browser_process->profile_manager())
    987     return g_browser_process->profile_manager()->GetDefaultProfile();
    988 
    989   return NULL;
    990 }
    991 
    992 // Various methods to open URLs that we get in a native fashion. We use
    993 // BrowserInit here because on the other platforms, URLs to open come through
    994 // the ProcessSingleton, and it calls BrowserInit. It's best to bottleneck the
    995 // openings through that for uniform handling.
    996 
    997 - (void)openUrls:(const std::vector<GURL>&)urls {
    998   // If the browser hasn't started yet, just queue up the URLs.
    999   if (!startupComplete_) {
   1000     startupUrls_.insert(startupUrls_.end(), urls.begin(), urls.end());
   1001     return;
   1002   }
   1003 
   1004   Browser* browser = BrowserList::GetLastActive();
   1005   // if no browser window exists then create one with no tabs to be filled in
   1006   if (!browser) {
   1007     browser = Browser::Create([self defaultProfile]);
   1008     browser->window()->Show();
   1009   }
   1010 
   1011   CommandLine dummy(CommandLine::NO_PROGRAM);
   1012   BrowserInit::LaunchWithProfile launch(FilePath(), dummy);
   1013   launch.OpenURLsInBrowser(browser, false, urls);
   1014 }
   1015 
   1016 - (void)getUrl:(NSAppleEventDescriptor*)event
   1017      withReply:(NSAppleEventDescriptor*)reply {
   1018   NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
   1019                       stringValue];
   1020 
   1021   GURL gurl(base::SysNSStringToUTF8(urlStr));
   1022   std::vector<GURL> gurlVector;
   1023   gurlVector.push_back(gurl);
   1024 
   1025   [self openUrls:gurlVector];
   1026 }
   1027 
   1028 - (void)application:(NSApplication*)sender
   1029           openFiles:(NSArray*)filenames {
   1030   std::vector<GURL> gurlVector;
   1031   for (NSString* file in filenames) {
   1032     GURL gurl = net::FilePathToFileURL(FilePath(base::SysNSStringToUTF8(file)));
   1033     gurlVector.push_back(gurl);
   1034   }
   1035   if (!gurlVector.empty())
   1036     [self openUrls:gurlVector];
   1037   else
   1038     NOTREACHED() << "Nothing to open!";
   1039 
   1040   [sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
   1041 }
   1042 
   1043 // Show the preferences window, or bring it to the front if it's already
   1044 // visible.
   1045 - (IBAction)showPreferences:(id)sender {
   1046   if (Browser* browser = ActivateBrowser([self defaultProfile])) {
   1047     // Show options tab in the active browser window.
   1048     browser->OpenOptionsDialog();
   1049   } else {
   1050     // No browser window, so create one for the options tab.
   1051     Browser::OpenOptionsWindow([self defaultProfile]);
   1052   }
   1053 }
   1054 
   1055 // Called when the about window is closed. We use this to release the
   1056 // window controller.
   1057 - (void)aboutWindowClosed:(NSNotification*)notification {
   1058   NSWindow* window = [aboutController_ window];
   1059   DCHECK_EQ([notification object], window);
   1060   [[NSNotificationCenter defaultCenter]
   1061       removeObserver:self
   1062                 name:NSWindowWillCloseNotification
   1063               object:window];
   1064   // AboutWindowControllers are autoreleased in
   1065   // -[AboutWindowController windowWillClose:].
   1066   aboutController_ = nil;
   1067 }
   1068 
   1069 - (IBAction)orderFrontStandardAboutPanel:(id)sender {
   1070   if (!aboutController_) {
   1071     aboutController_ =
   1072         [[AboutWindowController alloc] initWithProfile:[self defaultProfile]];
   1073 
   1074     // Watch for a notification of when it goes away so that we can destroy
   1075     // the controller.
   1076     [[NSNotificationCenter defaultCenter]
   1077         addObserver:self
   1078            selector:@selector(aboutWindowClosed:)
   1079                name:NSWindowWillCloseNotification
   1080              object:[aboutController_ window]];
   1081   }
   1082 
   1083   [aboutController_ showWindow:self];
   1084 }
   1085 
   1086 - (IBAction)toggleConfirmToQuit:(id)sender {
   1087   PrefService* prefService = [self defaultProfile]->GetPrefs();
   1088   bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
   1089   prefService->SetBoolean(prefs::kConfirmToQuitEnabled, !enabled);
   1090 }
   1091 
   1092 // Explicitly bring to the foreground when creating new windows from the dock.
   1093 - (void)commandFromDock:(id)sender {
   1094   [NSApp activateIgnoringOtherApps:YES];
   1095   [self commandDispatch:sender];
   1096 }
   1097 
   1098 - (NSMenu*)applicationDockMenu:(NSApplication*)sender {
   1099   NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
   1100   Profile* profile = [self defaultProfile];
   1101 
   1102   NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
   1103   scoped_nsobject<NSMenuItem> item(
   1104       [[NSMenuItem alloc] initWithTitle:titleStr
   1105                                  action:@selector(commandFromDock:)
   1106                           keyEquivalent:@""]);
   1107   [item setTarget:self];
   1108   [item setTag:IDC_NEW_WINDOW];
   1109   [dockMenu addItem:item];
   1110 
   1111   titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
   1112   item.reset([[NSMenuItem alloc] initWithTitle:titleStr
   1113                                         action:@selector(commandFromDock:)
   1114                                  keyEquivalent:@""]);
   1115   [item setTarget:self];
   1116   [item setTag:IDC_NEW_INCOGNITO_WINDOW];
   1117   [dockMenu addItem:item];
   1118 
   1119   // TODO(rickcam): Mock out BackgroundApplicationListModel, then add unit
   1120   // tests which use the mock in place of the profile-initialized model.
   1121 
   1122   // Avoid breaking unit tests which have no profile.
   1123   if (profile) {
   1124     BackgroundApplicationListModel applications(profile);
   1125     if (applications.size()) {
   1126       int position = 0;
   1127       NSString* menuStr =
   1128           l10n_util::GetNSStringWithFixup(IDS_BACKGROUND_APPS_MAC);
   1129       scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:menuStr]);
   1130       for (ExtensionList::const_iterator cursor = applications.begin();
   1131            cursor != applications.end();
   1132            ++cursor, ++position) {
   1133         DCHECK_EQ(applications.GetPosition(*cursor), position);
   1134         NSString* itemStr =
   1135             base::SysUTF16ToNSString(UTF8ToUTF16((*cursor)->name()));
   1136         scoped_nsobject<NSMenuItem> appItem([[NSMenuItem alloc]
   1137             initWithTitle:itemStr
   1138                    action:@selector(commandFromDock:)
   1139             keyEquivalent:@""]);
   1140         [appItem setTarget:self];
   1141         [appItem setTag:position];
   1142         [appMenu addItem:appItem];
   1143       }
   1144       scoped_nsobject<NSMenuItem> appMenuItem([[NSMenuItem alloc]
   1145           initWithTitle:menuStr
   1146                  action:@selector(commandFromDock:)
   1147           keyEquivalent:@""]);
   1148       [appMenuItem setTarget:self];
   1149       [appMenuItem setTag:position];
   1150       [appMenuItem setSubmenu:appMenu];
   1151       [dockMenu addItem:appMenuItem];
   1152     }
   1153   }
   1154 
   1155   return dockMenu;
   1156 }
   1157 
   1158 - (const std::vector<GURL>&)startupUrls {
   1159   return startupUrls_;
   1160 }
   1161 
   1162 - (void)clearStartupUrls {
   1163   startupUrls_.clear();
   1164 }
   1165 
   1166 @end  // @implementation AppController
   1167 
   1168 //---------------------------------------------------------------------------
   1169 
   1170 namespace browser {
   1171 
   1172 void ShowInstantConfirmDialog(gfx::NativeWindow parent, Profile* profile) {
   1173   if (Browser* browser = ActivateBrowser(profile)) {
   1174     browser->OpenInstantConfirmDialog();
   1175   } else {
   1176     Browser::OpenInstantConfirmDialogWindow(profile);
   1177   }
   1178 }
   1179 
   1180 }  // namespace browser
   1181 
   1182 namespace app_controller_mac {
   1183 
   1184 bool IsOpeningNewWindow() {
   1185   return g_is_opening_new_window;
   1186 }
   1187 
   1188 }  // namespace app_controller_mac
   1189