• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
71namespace {
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.
77bool 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.
83Browser* 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|.
92Browser* 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|.
106Browser* 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.
114class 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.
127void 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
1170namespace browser {
1171
1172void 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
1182namespace app_controller_mac {
1183
1184bool IsOpeningNewWindow() {
1185  return g_is_opening_new_window;
1186}
1187
1188}  // namespace app_controller_mac
1189