app_controller_mac.mm revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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/browser_thread.h"
19#include "chrome/browser/command_updater.h"
20#include "chrome/browser/download/download_manager.h"
21#include "chrome/browser/metrics/user_metrics.h"
22#include "chrome/browser/printing/print_job_manager.h"
23#include "chrome/browser/profiles/profile_manager.h"
24#include "chrome/browser/sessions/tab_restore_service.h"
25#include "chrome/browser/sync/profile_sync_service.h"
26#include "chrome/browser/sync/sync_ui_util.h"
27#include "chrome/browser/sync/sync_ui_util_mac.h"
28#include "chrome/browser/tab_contents/tab_contents.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/browser_init.h"
31#include "chrome/browser/ui/browser_list.h"
32#include "chrome/browser/ui/browser_window.h"
33#import "chrome/browser/ui/cocoa/about_window_controller.h"
34#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
35#import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
36#import "chrome/browser/ui/cocoa/browser_window_controller.h"
37#import "chrome/browser/ui/cocoa/bug_report_window_controller.h"
38#import "chrome/browser/ui/cocoa/clear_browsing_data_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/importer/import_settings_dialog.h"
43#import "chrome/browser/ui/cocoa/options/preferences_window_controller.h"
44#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
45#import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
46#include "chrome/browser/ui/cocoa/task_manager_mac.h"
47#include "chrome/browser/ui/options/options_window.h"
48#include "chrome/common/app_mode_common_mac.h"
49#include "chrome/common/chrome_paths_internal.h"
50#include "chrome/common/chrome_switches.h"
51#include "chrome/common/notification_service.h"
52#include "chrome/common/url_constants.h"
53#include "grit/chromium_strings.h"
54#include "grit/generated_resources.h"
55#include "net/base/net_util.h"
56#include "ui/base/l10n/l10n_util.h"
57#include "ui/base/l10n/l10n_util_mac.h"
58
59// 10.6 adds a public API for the Spotlight-backed search menu item in the Help
60// menu.  Provide the declaration so it can be called below when building with
61// the 10.5 SDK.
62#if !defined(MAC_OS_X_VERSION_10_6) || \
63    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6
64@interface NSApplication (SnowLeopardSDKDeclarations)
65- (void)setHelpMenu:(NSMenu*)helpMenu;
66@end
67#endif
68
69namespace {
70
71// True while AppController is calling Browser::OpenEmptyWindow(). We need a
72// global flag here, analogue to BrowserInit::InProcessStartup() because
73// otherwise the SessionService will try to restore sessions when we make a new
74// window while there are no other active windows.
75bool g_is_opening_new_window = false;
76
77// Activates a browser window having the given profile (the last one active) if
78// possible and returns a pointer to the activate |Browser| or NULL if this was
79// not possible. If the last active browser is minimized (in particular, if
80// there are only minimized windows), it will unminimize it.
81Browser* ActivateBrowser(Profile* profile) {
82  Browser* browser = BrowserList::GetLastActiveWithProfile(profile);
83  if (browser)
84    browser->window()->Activate();
85  return browser;
86}
87
88// Creates an empty browser window with the given profile and returns a pointer
89// to the new |Browser|.
90Browser* CreateBrowser(Profile* profile) {
91  {
92    AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
93    Browser::OpenEmptyWindow(profile);
94  }
95
96  Browser* browser = BrowserList::GetLastActive();
97  CHECK(browser);
98  return browser;
99}
100
101// Activates a browser window having the given profile (the last one active) if
102// possible or creates an empty one if necessary. Returns a pointer to the
103// activated/new |Browser|.
104Browser* ActivateOrCreateBrowser(Profile* profile) {
105  if (Browser* browser = ActivateBrowser(profile))
106    return browser;
107  return CreateBrowser(profile);
108}
109
110// This task synchronizes preferences (under "org.chromium.Chromium" or
111// "com.google.Chrome"), in particular, writes them out to disk.
112class PrefsSyncTask : public Task {
113 public:
114  PrefsSyncTask() {}
115  virtual ~PrefsSyncTask() {}
116  virtual void Run() {
117    if (!CFPreferencesAppSynchronize(app_mode::kAppPrefsID))
118      LOG(WARNING) << "Error recording application bundle path.";
119  }
120};
121
122// Record the location of the application bundle (containing the main framework)
123// from which Chromium was loaded. This is used by app mode shims to find
124// Chromium.
125void RecordLastRunAppBundlePath() {
126  // Going up three levels from |chrome::GetVersionedDirectory()| gives the
127  // real, user-visible app bundle directory. (The alternatives give either the
128  // framework's path or the initial app's path, which may be an app mode shim
129  // or a unit test.)
130  FilePath appBundlePath =
131      chrome::GetVersionedDirectory().DirName().DirName().DirName();
132  CFPreferencesSetAppValue(app_mode::kLastRunAppBundlePathPrefsKey,
133                           base::SysUTF8ToCFStringRef(appBundlePath.value()),
134                           app_mode::kAppPrefsID);
135
136  // Sync after a delay avoid I/O contention on startup; 1500 ms is plenty.
137  BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE,
138                                 new PrefsSyncTask(), 1500);
139}
140
141}  // anonymous namespace
142
143@interface AppController(Private)
144- (void)initMenuState;
145- (void)registerServicesMenuTypesTo:(NSApplication*)app;
146- (void)openUrls:(const std::vector<GURL>&)urls;
147- (void)getUrl:(NSAppleEventDescriptor*)event
148     withReply:(NSAppleEventDescriptor*)reply;
149- (void)windowLayeringDidChange:(NSNotification*)inNotification;
150- (void)checkForAnyKeyWindows;
151- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
152- (BOOL)shouldQuitWithInProgressDownloads;
153- (void)showPreferencesWindow:(id)sender
154                         page:(OptionsPage)page
155                      profile:(Profile*)profile;
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 experiment is enabled.
279  const CommandLine* commandLine(CommandLine::ForCurrentProcess());
280  if (!commandLine->HasSwitch(switches::kEnableConfirmToQuit))
281    return NSTerminateNow;
282
283  // If the application is going to terminate as the result of a Cmd+Q
284  // invocation, use the special sauce to prevent accidental quitting.
285  // http://dev.chromium.org/developers/design-documents/confirm-to-quit-experiment
286
287  // How long the user must hold down Cmd+Q to confirm the quit.
288  const NSTimeInterval kTimeToConfirmQuit = 1.5;
289  // Leeway between the |targetDate| and the current time that will confirm a
290  // quit.
291  const NSTimeInterval kTimeDeltaFuzzFactor = 1.0;
292  // Duration of the window fade out animation.
293  const NSTimeInterval kWindowFadeAnimationDuration = 0.2;
294
295  // This logic is only for keyboard-initiated quits.
296  if ([[app currentEvent] type] != NSKeyDown)
297    return NSTerminateNow;
298
299  // If this is the second of two such attempts to quit within a certain time
300  // interval, then just quit.
301  // Time of last quit attempt, if any.
302  static NSDate* lastQuitAttempt; // Initially nil, as it's static.
303  NSDate* timeNow = [NSDate date];
304  if (lastQuitAttempt &&
305      [timeNow timeIntervalSinceDate:lastQuitAttempt] < kTimeDeltaFuzzFactor) {
306    return NSTerminateNow;
307  } else {
308    [lastQuitAttempt release]; // Harmless if already nil.
309    lastQuitAttempt = [timeNow retain]; // Record this attempt for next time.
310  }
311
312  // Show the info panel that explains what the user must to do confirm quit.
313  [[ConfirmQuitPanelController sharedController] showWindow:self];
314
315  // Spin a nested run loop until the |targetDate| is reached or a KeyUp event
316  // is sent.
317  NSDate* targetDate =
318      [NSDate dateWithTimeIntervalSinceNow:kTimeToConfirmQuit];
319  BOOL willQuit = NO;
320  NSEvent* nextEvent = nil;
321  do {
322    // Dequeue events until a key up is received.
323    nextEvent = [app nextEventMatchingMask:NSKeyUpMask
324                                 untilDate:nil
325                                    inMode:NSEventTrackingRunLoopMode
326                                   dequeue:YES];
327
328    // Wait for the time expiry to happen. Once past the hold threshold,
329    // commit to quitting and hide all the open windows.
330    if (!willQuit) {
331      NSDate* now = [NSDate date];
332      NSTimeInterval difference = [targetDate timeIntervalSinceDate:now];
333      if (difference < kTimeDeltaFuzzFactor) {
334        willQuit = YES;
335
336        // At this point, the quit has been confirmed and windows should all
337        // fade out to convince the user to release the key combo to finalize
338        // the quit.
339        [NSAnimationContext beginGrouping];
340        [[NSAnimationContext currentContext] setDuration:
341            kWindowFadeAnimationDuration];
342        for (NSWindow* aWindow in [app windows]) {
343          // Windows that are set to animate and have a delegate do not
344          // expect to be animated by other things and could result in an
345          // invalid state. If a window is set up like so, just force the
346          // alpha value to 0. Otherwise, animate all pretty and stuff.
347          if (![[aWindow animationForKey:@"alphaValue"] delegate]) {
348            [[aWindow animator] setAlphaValue:0.0];
349          } else {
350            [aWindow setAlphaValue:0.0];
351          }
352        }
353        [NSAnimationContext endGrouping];
354      }
355    }
356  } while (!nextEvent);
357
358  // The user has released the key combo. Discard any events (i.e. the
359  // repeated KeyDown Cmd+Q).
360  [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent];
361
362  if (willQuit) {
363    // The user held down the combination long enough that quitting should
364    // happen.
365    return NSTerminateNow;
366  } else {
367    // Slowly fade the confirm window out in case the user doesn't
368    // understand what they have to do to quit.
369    [[ConfirmQuitPanelController sharedController] dismissPanel];
370    return NSTerminateCancel;
371  }
372
373  // Default case: terminate.
374  return NSTerminateNow;
375}
376
377// Called when the app is shutting down. Clean-up as appropriate.
378- (void)applicationWillTerminate:(NSNotification*)aNotification {
379  NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
380  [em removeEventHandlerForEventClass:kInternetEventClass
381                           andEventID:kAEGetURL];
382  [em removeEventHandlerForEventClass:'WWW!'
383                           andEventID:'OURL'];
384
385  // There better be no browser windows left at this point.
386  CHECK_EQ(BrowserList::size(), 0u);
387
388  // Tell BrowserList not to keep the browser process alive. Once all the
389  // browsers get dealloc'd, it will stop the RunLoop and fall back into main().
390  BrowserList::EndKeepAlive();
391
392  // Close these off if they have open windows.
393  [prefsController_ close];
394  [aboutController_ close];
395
396  [[NSNotificationCenter defaultCenter] removeObserver:self];
397}
398
399- (void)didEndMainMessageLoop {
400  DCHECK(!BrowserList::HasBrowserWithProfile([self defaultProfile]));
401  if (!BrowserList::HasBrowserWithProfile([self defaultProfile])) {
402    // As we're shutting down, we need to nuke the TabRestoreService, which
403    // will start the shutdown of the NavigationControllers and allow for
404    // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
405    // and may end up crashing when some thread tries to use the IO thread (or
406    // another thread) that is no longer valid.
407    [self defaultProfile]->ResetTabRestoreService();
408  }
409}
410
411// Helper routine to get the window controller if the key window is a tabbed
412// window, or nil if not. Examples of non-tabbed windows are "about" or
413// "preferences".
414- (TabWindowController*)keyWindowTabController {
415  NSWindowController* keyWindowController =
416      [[NSApp keyWindow] windowController];
417  if ([keyWindowController isKindOfClass:[TabWindowController class]])
418    return (TabWindowController*)keyWindowController;
419
420  return nil;
421}
422
423// Helper routine to get the window controller if the main window is a tabbed
424// window, or nil if not. Examples of non-tabbed windows are "about" or
425// "preferences".
426- (TabWindowController*)mainWindowTabController {
427  NSWindowController* mainWindowController =
428      [[NSApp mainWindow] windowController];
429  if ([mainWindowController isKindOfClass:[TabWindowController class]])
430    return (TabWindowController*)mainWindowController;
431
432  return nil;
433}
434
435// If the window has tabs, make "close window" be cmd-shift-w, otherwise leave
436// it as the normal cmd-w. Capitalization of the key equivalent affects whether
437// the shift modifer is used.
438- (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)inHaveTabs {
439  [closeWindowMenuItem_ setKeyEquivalent:(inHaveTabs ? @"W" : @"w")];
440  [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
441}
442
443// If the window has tabs, make "close tab" take over cmd-w, otherwise it
444// shouldn't have any key-equivalent because it should be disabled.
445- (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)hasTabs {
446  if (hasTabs) {
447    [closeTabMenuItem_ setKeyEquivalent:@"w"];
448    [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
449  } else {
450    [closeTabMenuItem_ setKeyEquivalent:@""];
451    [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
452  }
453}
454
455// Explicitly remove any command-key equivalents from the close tab/window
456// menus so that nothing can go haywire if we get a user action during pending
457// updates.
458- (void)clearCloseMenuItemKeyEquivalents {
459  [closeTabMenuItem_ setKeyEquivalent:@""];
460  [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
461  [closeWindowMenuItem_ setKeyEquivalent:@""];
462  [closeWindowMenuItem_ setKeyEquivalentModifierMask:0];
463}
464
465// See if we have a window with tabs open, and adjust the key equivalents for
466// Close Tab/Close Window accordingly.
467- (void)fixCloseMenuItemKeyEquivalents {
468  fileMenuUpdatePending_ = NO;
469  TabWindowController* tabController = [self keyWindowTabController];
470  if (!tabController && ![NSApp keyWindow]) {
471    // There might be a small amount of time where there is no key window,
472    // so just use our main browser window if there is one.
473    tabController = [self mainWindowTabController];
474  }
475  BOOL windowWithMultipleTabs =
476      (tabController && [tabController numberOfTabs] > 1);
477  [self adjustCloseWindowMenuItemKeyEquivalent:windowWithMultipleTabs];
478  [self adjustCloseTabMenuItemKeyEquivalent:windowWithMultipleTabs];
479}
480
481// Fix up the "close tab/close window" command-key equivalents. We do this
482// after a delay to ensure that window layer state has been set by the time
483// we do the enabling. This should only be called on the main thread, code that
484// calls this (even as a side-effect) from other threads needs to be fixed.
485- (void)delayedFixCloseMenuItemKeyEquivalents {
486  DCHECK([NSThread isMainThread]);
487  if (!fileMenuUpdatePending_) {
488    // The OS prefers keypresses to timers, so it's possible that a cmd-w
489    // can sneak in before this timer fires. In order to prevent that from
490    // having any bad consequences, just clear the keys combos altogether. They
491    // will be reset when the timer eventually fires.
492    if ([NSThread isMainThread]) {
493      fileMenuUpdatePending_ = YES;
494      [self clearCloseMenuItemKeyEquivalents];
495      [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
496                 withObject:nil
497                 afterDelay:0];
498    } else {
499      // This shouldn't be happening, but if it does, force it to the main
500      // thread to avoid dropping the update. Don't mess with
501      // |fileMenuUpdatePending_| as it's not expected to be threadsafe and
502      // there could be a race between the selector finishing and setting the
503      // flag.
504      [self
505          performSelectorOnMainThread:@selector(fixCloseMenuItemKeyEquivalents)
506                           withObject:nil
507                        waitUntilDone:NO];
508    }
509  }
510}
511
512// Called when we get a notification about the window layering changing to
513// update the UI based on the new main window.
514- (void)windowLayeringDidChange:(NSNotification*)notify {
515  [self delayedFixCloseMenuItemKeyEquivalents];
516
517  if ([notify name] == NSWindowDidResignKeyNotification) {
518    // If a window is closed, this notification is fired but |[NSApp keyWindow]|
519    // returns nil regardless of whether any suitable candidates for the key
520    // window remain. It seems that the new key window for the app is not set
521    // until after this notification is fired, so a check is performed after the
522    // run loop is allowed to spin.
523    [self performSelector:@selector(checkForAnyKeyWindows)
524               withObject:nil
525               afterDelay:0.0];
526  }
527}
528
529- (void)checkForAnyKeyWindows {
530  if ([NSApp keyWindow])
531    return;
532
533  NotificationService::current()->Notify(
534      NotificationType::NO_KEY_WINDOW,
535      NotificationService::AllSources(),
536      NotificationService::NoDetails());
537}
538
539// Called when the number of tabs changes in one of the browser windows. The
540// object is the tab strip controller, but we don't currently care.
541- (void)tabsChanged:(NSNotification*)notify {
542  // We don't need to do this on a delay, as in the method above, because the
543  // window layering isn't changing. As a result, there's no chance that a
544  // different window will sneak in as the key window and cause the problems
545  // we hacked around above by clearing the key equivalents.
546  [self fixCloseMenuItemKeyEquivalents];
547}
548
549// If the auto-update interval is not set, make it 5 hours.
550// This code is specific to Mac Chrome Dev Channel.
551// Placed here for 2 reasons:
552// 1) Same spot as other Pref stuff
553// 2) Try and be friendly by keeping this after app launch
554// TODO(jrg): remove once we go Beta.
555- (void)setUpdateCheckInterval {
556#if defined(GOOGLE_CHROME_BUILD)
557  CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent";
558  CFStringRef checkInterval = (CFStringRef)@"checkInterval";
559  CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
560  if (!plist) {
561    const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
562    NSNumber* value = [NSNumber numberWithFloat:fiveHoursInSeconds];
563    CFPreferencesSetAppValue(checkInterval, value, app);
564    CFPreferencesAppSynchronize(app);
565  }
566#endif
567}
568
569// This is called after profiles have been loaded and preferences registered.
570// It is safe to access the default profile here.
571- (void)applicationDidFinishLaunching:(NSNotification*)notify {
572  // Notify BrowserList to keep the application running so it doesn't go away
573  // when all the browser windows get closed.
574  BrowserList::StartKeepAlive();
575
576  bookmarkMenuBridge_.reset(new BookmarkMenuBridge([self defaultProfile]));
577  historyMenuBridge_.reset(new HistoryMenuBridge([self defaultProfile]));
578
579  [self setUpdateCheckInterval];
580
581  // Build up the encoding menu, the order of the items differs based on the
582  // current locale (see http://crbug.com/7647 for details).
583  // We need a valid g_browser_process to get the profile which is why we can't
584  // call this from awakeFromNib.
585  NSMenu* view_menu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
586  NSMenuItem* encoding_menu_item = [view_menu itemWithTag:IDC_ENCODING_MENU];
587  NSMenu* encoding_menu = [encoding_menu_item submenu];
588  EncodingMenuControllerDelegate::BuildEncodingMenu([self defaultProfile],
589                                                    encoding_menu);
590
591  // Since Chrome is localized to more languages than the OS, tell Cocoa which
592  // menu is the Help so it can add the search item to it.
593  if (helpMenu_ && [NSApp respondsToSelector:@selector(setHelpMenu:)])
594    [NSApp setHelpMenu:helpMenu_];
595
596  // Record the path to the (browser) app bundle; this is used by the app mode
597  // shim.
598  RecordLastRunAppBundlePath();
599
600  // Makes "Services" menu items available.
601  [self registerServicesMenuTypesTo:[notify object]];
602
603  startupComplete_ = YES;
604
605  // TODO(viettrungluu): This is very temporary, since this should be done "in"
606  // |BrowserMain()|, i.e., this list of startup URLs should be appended to the
607  // (probably-empty) list of URLs from the command line.
608  if (startupUrls_.size()) {
609    [self openUrls:startupUrls_];
610    [self clearStartupUrls];
611  }
612}
613
614// This is called after profiles have been loaded and preferences registered.
615// It is safe to access the default profile here.
616- (void)applicationDidBecomeActive:(NSNotification*)notify {
617  NotificationService::current()->Notify(NotificationType::APP_ACTIVATED,
618                                         NotificationService::AllSources(),
619                                         NotificationService::NoDetails());
620}
621
622// Helper function for populating and displaying the in progress downloads at
623// exit alert panel.
624- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
625  NSString* warningText = nil;
626  NSString* explanationText = nil;
627  NSString* waitTitle = nil;
628  NSString* exitTitle = nil;
629
630  string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME);
631
632  // Set the dialog text based on whether or not there are multiple downloads.
633  if (downloadCount == 1) {
634    // Dialog text: warning and explanation.
635    warningText = l10n_util::GetNSStringF(
636        IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_WARNING, product_name);
637    explanationText = l10n_util::GetNSStringF(
638        IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION, product_name);
639
640    // Cancel download and exit button text.
641    exitTitle = l10n_util::GetNSString(
642        IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL);
643
644    // Wait for download button text.
645    waitTitle = l10n_util::GetNSString(
646        IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
647  } else {
648    // Dialog text: warning and explanation.
649    warningText = l10n_util::GetNSStringF(
650        IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_WARNING, product_name,
651        base::IntToString16(downloadCount));
652    explanationText = l10n_util::GetNSStringF(
653        IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION, product_name);
654
655    // Cancel downloads and exit button text.
656    exitTitle = l10n_util::GetNSString(
657        IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_OK_BUTTON_LABEL);
658
659    // Wait for downloads button text.
660    waitTitle = l10n_util::GetNSString(
661        IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
662  }
663
664  // 'waitButton' is the default choice.
665  int choice = NSRunAlertPanel(warningText, explanationText,
666                               waitTitle, exitTitle, nil);
667  return choice == NSAlertDefaultReturn ? YES : NO;
668}
669
670// Check all profiles for in progress downloads, and if we find any, prompt the
671// user to see if we should continue to exit (and thus cancel the downloads), or
672// if we should wait.
673- (BOOL)shouldQuitWithInProgressDownloads {
674  ProfileManager* profile_manager = g_browser_process->profile_manager();
675  if (!profile_manager)
676    return YES;
677
678  ProfileManager::const_iterator it = profile_manager->begin();
679  for (; it != profile_manager->end(); ++it) {
680    Profile* profile = *it;
681    DownloadManager* download_manager = profile->GetDownloadManager();
682    if (download_manager && download_manager->in_progress_count() > 0) {
683      int downloadCount = download_manager->in_progress_count();
684      if ([self userWillWaitForInProgressDownloads:downloadCount]) {
685        // Create a new browser window (if necessary) and navigate to the
686        // downloads page if the user chooses to wait.
687        Browser* browser = BrowserList::FindBrowserWithProfile(profile);
688        if (!browser) {
689          browser = Browser::Create(profile);
690          browser->window()->Show();
691        }
692        DCHECK(browser);
693        browser->ShowDownloadsTab();
694        return NO;
695      }
696
697      // User wants to exit.
698      return YES;
699    }
700  }
701
702  // No profiles or active downloads found, okay to exit.
703  return YES;
704}
705
706// Called to determine if we should enable the "restore tab" menu item.
707// Checks with the TabRestoreService to see if there's anything there to
708// restore and returns YES if so.
709- (BOOL)canRestoreTab {
710  TabRestoreService* service = [self defaultProfile]->GetTabRestoreService();
711  return service && !service->entries().empty();
712}
713
714// Returns true if there is not a modal window (either window- or application-
715// modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
716// sheets) will not count as blocking the browser. But things like open/save
717// dialogs that are window modal will block the browser.
718- (BOOL)keyWindowIsNotModal {
719  Browser* browser = BrowserList::GetLastActive();
720  return [NSApp modalWindow] == nil && (!browser ||
721         ![[browser->window()->GetNativeHandle() attachedSheet]
722             isKindOfClass:[NSWindow class]]);
723}
724
725// Called to validate menu items when there are no key windows. All the
726// items we care about have been set with the |commandDispatch:| action and
727// a target of FirstResponder in IB. If it's not one of those, let it
728// continue up the responder chain to be handled elsewhere. We pull out the
729// tag as the cross-platform constant to differentiate and dispatch the
730// various commands.
731- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
732  SEL action = [item action];
733  BOOL enable = NO;
734  if (action == @selector(commandDispatch:)) {
735    NSInteger tag = [item tag];
736    if (menuState_->SupportsCommand(tag)) {
737      switch (tag) {
738        // The File Menu commands are not automatically disabled by Cocoa when a
739        // dialog sheet obscures the browser window, so we disable several of
740        // them here.  We don't need to include IDC_CLOSE_WINDOW, because
741        // app_controller is only activated when there are no key windows (see
742        // function comment).
743        case IDC_RESTORE_TAB:
744          enable = [self keyWindowIsNotModal] && [self canRestoreTab];
745          break;
746        // Browser-level items that open in new tabs should not open if there's
747        // a window- or app-modal dialog.
748        case IDC_OPEN_FILE:
749        case IDC_NEW_TAB:
750        case IDC_SHOW_HISTORY:
751        case IDC_SHOW_BOOKMARK_MANAGER:
752          enable = [self keyWindowIsNotModal];
753          break;
754        // Browser-level items that open in new windows.
755        case IDC_NEW_WINDOW:
756        case IDC_TASK_MANAGER:
757          // Allow the user to open a new window if there's a window-modal
758          // dialog.
759          enable = [self keyWindowIsNotModal] || ([NSApp modalWindow] == nil);
760          break;
761        case IDC_SYNC_BOOKMARKS: {
762          Profile* defaultProfile = [self defaultProfile];
763          // The profile may be NULL during shutdown -- see
764          // http://code.google.com/p/chromium/issues/detail?id=43048 .
765          //
766          // TODO(akalin,viettrungluu): Figure out whether this method
767          // can be prevented from being called if defaultProfile is
768          // NULL.
769          if (!defaultProfile) {
770            LOG(WARNING)
771                << "NULL defaultProfile detected -- not doing anything";
772            break;
773          }
774          enable = defaultProfile->IsSyncAccessible() &&
775              [self keyWindowIsNotModal];
776          sync_ui_util::UpdateSyncItem(item, enable, defaultProfile);
777          break;
778        }
779        default:
780          enable = menuState_->IsCommandEnabled(tag) ?
781                   [self keyWindowIsNotModal] : NO;
782      }
783    }
784  } else if (action == @selector(terminate:)) {
785    enable = YES;
786  } else if (action == @selector(showPreferences:)) {
787    enable = YES;
788  } else if (action == @selector(orderFrontStandardAboutPanel:)) {
789    enable = YES;
790  } else if (action == @selector(commandFromDock:)) {
791    enable = YES;
792  }
793  return enable;
794}
795
796// Called when the user picks a menu item when there are no key windows, or when
797// there is no foreground browser window. Calls through to the browser object to
798// execute the command. This assumes that the command is supported and doesn't
799// check, otherwise it should have been disabled in the UI in
800// |-validateUserInterfaceItem:|.
801- (void)commandDispatch:(id)sender {
802  Profile* defaultProfile = [self defaultProfile];
803
804  // Handle the case where we're dispatching a command from a sender that's in a
805  // browser window. This means that the command came from a background window
806  // and is getting here because the foreground window is not a browser window.
807  if ([sender respondsToSelector:@selector(window)]) {
808    id delegate = [[sender window] windowController];
809    if ([delegate isKindOfClass:[BrowserWindowController class]]) {
810      [delegate commandDispatch:sender];
811      return;
812    }
813  }
814
815  NSInteger tag = [sender tag];
816  switch (tag) {
817    case IDC_NEW_TAB:
818      // Create a new tab in an existing browser window (which we activate) if
819      // possible.
820      if (Browser* browser = ActivateBrowser(defaultProfile)) {
821        browser->ExecuteCommand(IDC_NEW_TAB);
822        break;
823      }
824      // Else fall through to create new window.
825    case IDC_NEW_WINDOW:
826      CreateBrowser(defaultProfile);
827      break;
828    case IDC_FOCUS_LOCATION:
829      ActivateOrCreateBrowser(defaultProfile)->
830          ExecuteCommand(IDC_FOCUS_LOCATION);
831      break;
832    case IDC_FOCUS_SEARCH:
833      ActivateOrCreateBrowser(defaultProfile)->ExecuteCommand(IDC_FOCUS_SEARCH);
834      break;
835    case IDC_NEW_INCOGNITO_WINDOW:
836      Browser::OpenEmptyWindow(defaultProfile->GetOffTheRecordProfile());
837      break;
838    case IDC_RESTORE_TAB:
839      Browser::OpenWindowWithRestoredTabs(defaultProfile);
840      break;
841    case IDC_OPEN_FILE:
842      CreateBrowser(defaultProfile)->ExecuteCommand(IDC_OPEN_FILE);
843      break;
844    case IDC_CLEAR_BROWSING_DATA: {
845      // There may not be a browser open, so use the default profile.
846      if (CommandLine::ForCurrentProcess()->HasSwitch(
847              switches::kDisableTabbedOptions)) {
848        [ClearBrowsingDataController
849            showClearBrowsingDialogForProfile:defaultProfile];
850      } else {
851        if (Browser* browser = ActivateBrowser(defaultProfile)) {
852          browser->OpenClearBrowsingDataDialog();
853        } else {
854          Browser::OpenClearBrowingDataDialogWindow(defaultProfile);
855        }
856      }
857      break;
858    }
859    case IDC_IMPORT_SETTINGS: {
860      if (CommandLine::ForCurrentProcess()->HasSwitch(
861              switches::kDisableTabbedOptions)) {
862        UserMetrics::RecordAction(UserMetricsAction("Import_ShowDlg"),
863                                  defaultProfile);
864        [ImportSettingsDialogController
865            showImportSettingsDialogForProfile:defaultProfile];
866      } else {
867        if (Browser* browser = ActivateBrowser(defaultProfile)) {
868          browser->OpenImportSettingsDialog();
869        } else {
870          Browser::OpenImportSettingsDialogWindow(defaultProfile);
871        }
872      }
873      break;
874    }
875    case IDC_SHOW_BOOKMARK_MANAGER:
876      UserMetrics::RecordAction(UserMetricsAction("ShowBookmarkManager"),
877                                defaultProfile);
878      if (Browser* browser = ActivateBrowser(defaultProfile)) {
879        // Open a bookmark manager tab.
880        browser->OpenBookmarkManager();
881      } else {
882        // No browser window, so create one for the bookmark manager tab.
883        Browser::OpenBookmarkManagerWindow(defaultProfile);
884      }
885      break;
886    case IDC_SHOW_HISTORY:
887      if (Browser* browser = ActivateBrowser(defaultProfile))
888        browser->ShowHistoryTab();
889      else
890        Browser::OpenHistoryWindow(defaultProfile);
891      break;
892    case IDC_SHOW_DOWNLOADS:
893      if (Browser* browser = ActivateBrowser(defaultProfile))
894        browser->ShowDownloadsTab();
895      else
896        Browser::OpenDownloadsWindow(defaultProfile);
897      break;
898    case IDC_MANAGE_EXTENSIONS:
899      if (Browser* browser = ActivateBrowser(defaultProfile))
900        browser->ShowExtensionsTab();
901      else
902        Browser::OpenExtensionsWindow(defaultProfile);
903      break;
904    case IDC_HELP_PAGE:
905      if (Browser* browser = ActivateBrowser(defaultProfile))
906        browser->OpenHelpTab();
907      else
908        Browser::OpenHelpWindow(defaultProfile);
909      break;
910    case IDC_FEEDBACK: {
911      Browser* browser = BrowserList::GetLastActive();
912      TabContents* currentTab =
913          browser ? browser->GetSelectedTabContents() : NULL;
914      BugReportWindowController* controller =
915          [[BugReportWindowController alloc]
916              initWithTabContents:currentTab
917                          profile:[self defaultProfile]];
918      [controller runModalDialog];
919      break;
920    }
921    case IDC_SYNC_BOOKMARKS:
922      // The profile may be NULL during shutdown -- see
923      // http://code.google.com/p/chromium/issues/detail?id=43048 .
924      //
925      // TODO(akalin,viettrungluu): Figure out whether this method can
926      // be prevented from being called if defaultProfile is NULL.
927      if (!defaultProfile) {
928        LOG(WARNING) << "NULL defaultProfile detected -- not doing anything";
929        break;
930      }
931      // TODO(akalin): Add a constant to denote starting sync from the
932      // main menu and use that instead of START_FROM_WRENCH.
933      sync_ui_util::OpenSyncMyBookmarksDialog(
934          defaultProfile, ActivateBrowser(defaultProfile),
935          ProfileSyncService::START_FROM_WRENCH);
936      break;
937    case IDC_TASK_MANAGER:
938      UserMetrics::RecordAction(UserMetricsAction("TaskManager"),
939                                defaultProfile);
940      TaskManagerMac::Show(false);
941      break;
942    case IDC_OPTIONS:
943      [self showPreferences:sender];
944      break;
945    default:
946      // Background Applications use dynamic values that must be less than the
947      // smallest value among the predefined IDC_* labels.
948      if ([sender tag] < IDC_MinimumLabelValue)
949        [self executeApplication:sender];
950      break;
951  }
952}
953
954// Run a (background) application in a new tab.
955- (void)executeApplication:(id)sender {
956  NSInteger tag = [sender tag];
957  Profile* profile = [self defaultProfile];
958  DCHECK(profile);
959  BackgroundApplicationListModel applications(profile);
960  DCHECK(tag >= 0 &&
961         tag < static_cast<int>(applications.size()));
962  Browser* browser = BrowserList::GetLastActive();
963  if (!browser) {
964    Browser::OpenEmptyWindow(profile);
965    browser = BrowserList::GetLastActive();
966  }
967  const Extension* extension = applications.GetExtension(tag);
968  browser->OpenApplicationTab(profile, extension, NULL);
969}
970
971// Same as |-commandDispatch:|, but executes commands using a disposition
972// determined by the key flags. This will get called in the case where the
973// frontmost window is not a browser window, and the user has command-clicked
974// a button in a background browser window whose action is
975// |-commandDispatchUsingKeyModifiers:|
976- (void)commandDispatchUsingKeyModifiers:(id)sender {
977  DCHECK(sender);
978  if ([sender respondsToSelector:@selector(window)]) {
979    id delegate = [[sender window] windowController];
980    if ([delegate isKindOfClass:[BrowserWindowController class]]) {
981      [delegate commandDispatchUsingKeyModifiers:sender];
982    }
983  }
984}
985
986// NSApplication delegate method called when someone clicks on the
987// dock icon and there are no open windows.  To match standard mac
988// behavior, we should open a new window.
989- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
990                    hasVisibleWindows:(BOOL)flag {
991  // If the browser is currently trying to quit, don't do anything and return NO
992  // to prevent AppKit from doing anything.
993  // TODO(rohitrao): Remove this code when http://crbug.com/40861 is resolved.
994  if (browser_shutdown::IsTryingToQuit())
995    return NO;
996
997  // Don't do anything if there are visible windows.  This will cause
998  // AppKit to unminimize the most recently minimized window.
999  if (flag)
1000    return YES;
1001
1002  // If launched as a hidden login item (due to installation of a persistent app
1003  // or by the user, for example in System Preferenecs->Accounts->Login Items),
1004  // allow session to be restored first time the user clicks on a Dock icon.
1005  // Normally, it'd just open a new empty page.
1006  {
1007      static BOOL doneOnce = NO;
1008      if (!doneOnce) {
1009        doneOnce = YES;
1010        if (base::mac::WasLaunchedAsHiddenLoginItem()) {
1011          SessionService* sessionService =
1012              [self defaultProfile]->GetSessionService();
1013          if (sessionService &&
1014              sessionService->RestoreIfNecessary(std::vector<GURL>()))
1015            return NO;
1016        }
1017      }
1018  }
1019  // Otherwise open a new window.
1020  {
1021    AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
1022    Browser::OpenEmptyWindow([self defaultProfile]);
1023  }
1024
1025  // We've handled the reopen event, so return NO to tell AppKit not
1026  // to do anything.
1027  return NO;
1028}
1029
1030- (void)initMenuState {
1031  menuState_.reset(new CommandUpdater(NULL));
1032  menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
1033  menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
1034  menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
1035  menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
1036  menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
1037  menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
1038  menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
1039  menuState_->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true);
1040  menuState_->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true);
1041  menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
1042  menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
1043  menuState_->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true);
1044  menuState_->UpdateCommandEnabled(IDC_HELP_PAGE, true);
1045  menuState_->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true);
1046  menuState_->UpdateCommandEnabled(IDC_FEEDBACK, true);
1047  menuState_->UpdateCommandEnabled(IDC_SYNC_BOOKMARKS, true);
1048  menuState_->UpdateCommandEnabled(IDC_TASK_MANAGER, true);
1049}
1050
1051- (void)registerServicesMenuTypesTo:(NSApplication*)app {
1052  // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which
1053  // handles requests from services.
1054  NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
1055  [app registerServicesMenuSendTypes:types returnTypes:types];
1056}
1057
1058- (Profile*)defaultProfile {
1059  // TODO(jrg): Find a better way to get the "default" profile.
1060  if (g_browser_process->profile_manager())
1061    return *g_browser_process->profile_manager()->begin();
1062
1063  return NULL;
1064}
1065
1066// Various methods to open URLs that we get in a native fashion. We use
1067// BrowserInit here because on the other platforms, URLs to open come through
1068// the ProcessSingleton, and it calls BrowserInit. It's best to bottleneck the
1069// openings through that for uniform handling.
1070
1071- (void)openUrls:(const std::vector<GURL>&)urls {
1072  // If the browser hasn't started yet, just queue up the URLs.
1073  if (!startupComplete_) {
1074    startupUrls_.insert(startupUrls_.end(), urls.begin(), urls.end());
1075    return;
1076  }
1077
1078  Browser* browser = BrowserList::GetLastActive();
1079  // if no browser window exists then create one with no tabs to be filled in
1080  if (!browser) {
1081    browser = Browser::Create([self defaultProfile]);
1082    browser->window()->Show();
1083  }
1084
1085  CommandLine dummy(CommandLine::NO_PROGRAM);
1086  BrowserInit::LaunchWithProfile launch(FilePath(), dummy);
1087  launch.OpenURLsInBrowser(browser, false, urls);
1088}
1089
1090- (void)getUrl:(NSAppleEventDescriptor*)event
1091     withReply:(NSAppleEventDescriptor*)reply {
1092  NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
1093                      stringValue];
1094
1095  GURL gurl(base::SysNSStringToUTF8(urlStr));
1096  std::vector<GURL> gurlVector;
1097  gurlVector.push_back(gurl);
1098
1099  [self openUrls:gurlVector];
1100}
1101
1102- (void)application:(NSApplication*)sender
1103          openFiles:(NSArray*)filenames {
1104  std::vector<GURL> gurlVector;
1105  for (NSString* file in filenames) {
1106    GURL gurl = net::FilePathToFileURL(FilePath(base::SysNSStringToUTF8(file)));
1107    gurlVector.push_back(gurl);
1108  }
1109  if (!gurlVector.empty())
1110    [self openUrls:gurlVector];
1111  else
1112    NOTREACHED() << "Nothing to open!";
1113
1114  [sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
1115}
1116
1117// Called when the preferences window is closed. We use this to release the
1118// window controller.
1119- (void)prefsWindowClosed:(NSNotification*)notification {
1120  NSWindow* window = [prefsController_ window];
1121  DCHECK([notification object] == window);
1122  NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
1123  [defaultCenter removeObserver:self
1124                           name:NSWindowWillCloseNotification
1125                         object:window];
1126  // PreferencesWindowControllers are autoreleased in
1127  // -[PreferencesWindowController windowWillClose:].
1128  prefsController_ = nil;
1129}
1130
1131// Show the preferences window, or bring it to the front if it's already
1132// visible.
1133- (IBAction)showPreferences:(id)sender {
1134  const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
1135  if (!parsed_command_line.HasSwitch(switches::kDisableTabbedOptions)) {
1136    if (Browser* browser = ActivateBrowser([self defaultProfile])) {
1137      // Show options tab in the active browser window.
1138      browser->ShowOptionsTab(chrome::kDefaultOptionsSubPage);
1139    } else {
1140      // No browser window, so create one for the options tab.
1141      Browser::OpenOptionsWindow([self defaultProfile]);
1142    }
1143  } else {
1144    [self showPreferencesWindow:sender
1145                           page:OPTIONS_PAGE_DEFAULT
1146                        profile:[self defaultProfile]];
1147  }
1148}
1149
1150- (void)showPreferencesWindow:(id)sender
1151                         page:(OptionsPage)page
1152                      profile:(Profile*)profile {
1153  if (prefsController_) {
1154    [prefsController_ switchToPage:page animate:YES];
1155  } else {
1156    prefsController_ =
1157        [[PreferencesWindowController alloc] initWithProfile:profile
1158                                                 initialPage:page];
1159    // Watch for a notification of when it goes away so that we can destroy
1160    // the controller.
1161    [[NSNotificationCenter defaultCenter]
1162        addObserver:self
1163           selector:@selector(prefsWindowClosed:)
1164               name:NSWindowWillCloseNotification
1165             object:[prefsController_ window]];
1166  }
1167  [prefsController_ showPreferences:sender];
1168}
1169
1170// Called when the about window is closed. We use this to release the
1171// window controller.
1172- (void)aboutWindowClosed:(NSNotification*)notification {
1173  NSWindow* window = [aboutController_ window];
1174  DCHECK(window == [notification object]);
1175  [[NSNotificationCenter defaultCenter]
1176      removeObserver:self
1177                name:NSWindowWillCloseNotification
1178              object:window];
1179  // AboutWindowControllers are autoreleased in
1180  // -[AboutWindowController windowWillClose:].
1181  aboutController_ = nil;
1182}
1183
1184- (IBAction)orderFrontStandardAboutPanel:(id)sender {
1185  if (!aboutController_) {
1186    aboutController_ =
1187        [[AboutWindowController alloc] initWithProfile:[self defaultProfile]];
1188
1189    // Watch for a notification of when it goes away so that we can destroy
1190    // the controller.
1191    [[NSNotificationCenter defaultCenter]
1192        addObserver:self
1193           selector:@selector(aboutWindowClosed:)
1194               name:NSWindowWillCloseNotification
1195             object:[aboutController_ window]];
1196  }
1197
1198  [aboutController_ showWindow:self];
1199}
1200
1201// Explicitly bring to the foreground when creating new windows from the dock.
1202- (void)commandFromDock:(id)sender {
1203  [NSApp activateIgnoringOtherApps:YES];
1204  [self commandDispatch:sender];
1205}
1206
1207- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
1208  NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
1209  Profile* profile = [self defaultProfile];
1210
1211  NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
1212  scoped_nsobject<NSMenuItem> item(
1213      [[NSMenuItem alloc] initWithTitle:titleStr
1214                                 action:@selector(commandFromDock:)
1215                          keyEquivalent:@""]);
1216  [item setTarget:self];
1217  [item setTag:IDC_NEW_WINDOW];
1218  [dockMenu addItem:item];
1219
1220  titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
1221  item.reset([[NSMenuItem alloc] initWithTitle:titleStr
1222                                        action:@selector(commandFromDock:)
1223                                 keyEquivalent:@""]);
1224  [item setTarget:self];
1225  [item setTag:IDC_NEW_INCOGNITO_WINDOW];
1226  [dockMenu addItem:item];
1227
1228  // TODO(rickcam): Mock out BackgroundApplicationListModel, then add unit
1229  // tests which use the mock in place of the profile-initialized model.
1230
1231  // Avoid breaking unit tests which have no profile.
1232  if (profile) {
1233    BackgroundApplicationListModel applications(profile);
1234    if (applications.size()) {
1235      int position = 0;
1236      NSString* menuStr =
1237          l10n_util::GetNSStringWithFixup(IDS_BACKGROUND_APPS_MAC);
1238      scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:menuStr]);
1239      for (ExtensionList::const_iterator cursor = applications.begin();
1240           cursor != applications.end();
1241           ++cursor, ++position) {
1242        DCHECK(position == applications.GetPosition(*cursor));
1243        NSString* itemStr =
1244            base::SysUTF16ToNSString(UTF8ToUTF16((*cursor)->name()));
1245        scoped_nsobject<NSMenuItem> appItem([[NSMenuItem alloc]
1246            initWithTitle:itemStr
1247                   action:@selector(commandFromDock:)
1248            keyEquivalent:@""]);
1249        [appItem setTarget:self];
1250        [appItem setTag:position];
1251        [appMenu addItem:appItem];
1252      }
1253      scoped_nsobject<NSMenuItem> appMenuItem([[NSMenuItem alloc]
1254          initWithTitle:menuStr
1255                 action:@selector(commandFromDock:)
1256          keyEquivalent:@""]);
1257      [appMenuItem setTarget:self];
1258      [appMenuItem setTag:position];
1259      [appMenuItem setSubmenu:appMenu];
1260      [dockMenu addItem:appMenuItem];
1261    }
1262  }
1263
1264  return dockMenu;
1265}
1266
1267- (const std::vector<GURL>&)startupUrls {
1268  return startupUrls_;
1269}
1270
1271- (void)clearStartupUrls {
1272  startupUrls_.clear();
1273}
1274
1275@end  // @implementation AppController
1276
1277//---------------------------------------------------------------------------
1278
1279void ShowOptionsWindow(OptionsPage page,
1280                       OptionsGroup highlight_group,
1281                       Profile* profile) {
1282  // TODO(akalin): Use highlight_group.
1283  AppController* appController = [NSApp delegate];
1284  [appController showPreferencesWindow:nil page:page profile:profile];
1285}
1286
1287namespace app_controller_mac {
1288
1289bool IsOpeningNewWindow() {
1290  return g_is_opening_new_window;
1291}
1292
1293}  // namespace app_controller_mac
1294