• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2012 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 "apps/app_shim/extension_app_shim_handler_mac.h"
8#include "apps/app_window_registry.h"
9#include "base/auto_reset.h"
10#include "base/bind.h"
11#include "base/command_line.h"
12#include "base/files/file_path.h"
13#include "base/mac/foundation_util.h"
14#include "base/mac/mac_util.h"
15#include "base/mac/sdk_forward_declarations.h"
16#include "base/message_loop/message_loop.h"
17#include "base/prefs/pref_service.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/sys_string_conversions.h"
20#include "base/strings/utf_string_conversions.h"
21#include "chrome/app/chrome_command_ids.h"
22#include "chrome/browser/background/background_application_list_model.h"
23#include "chrome/browser/background/background_mode_manager.h"
24#include "chrome/browser/browser_process.h"
25#include "chrome/browser/browser_shutdown.h"
26#include "chrome/browser/chrome_notification_types.h"
27#include "chrome/browser/command_updater.h"
28#include "chrome/browser/download/download_service.h"
29#include "chrome/browser/download/download_service_factory.h"
30#include "chrome/browser/extensions/extension_service.h"
31#include "chrome/browser/first_run/first_run.h"
32#include "chrome/browser/lifetime/application_lifetime.h"
33#include "chrome/browser/profiles/profile_info_cache_observer.h"
34#include "chrome/browser/profiles/profile_manager.h"
35#include "chrome/browser/profiles/profiles_state.h"
36#include "chrome/browser/sessions/session_restore.h"
37#include "chrome/browser/sessions/session_service.h"
38#include "chrome/browser/sessions/session_service_factory.h"
39#include "chrome/browser/sessions/tab_restore_service.h"
40#include "chrome/browser/sessions/tab_restore_service_factory.h"
41#include "chrome/browser/signin/signin_manager_factory.h"
42#include "chrome/browser/signin/signin_promo.h"
43#include "chrome/browser/sync/profile_sync_service.h"
44#include "chrome/browser/sync/sync_ui_util.h"
45#include "chrome/browser/ui/browser.h"
46#include "chrome/browser/ui/browser_command_controller.h"
47#include "chrome/browser/ui/browser_commands.h"
48#include "chrome/browser/ui/browser_dialogs.h"
49#include "chrome/browser/ui/browser_finder.h"
50#include "chrome/browser/ui/browser_iterator.h"
51#include "chrome/browser/ui/browser_mac.h"
52#include "chrome/browser/ui/browser_window.h"
53#include "chrome/browser/ui/chrome_pages.h"
54#import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
55#include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
56#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
57#import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
58#import "chrome/browser/ui/cocoa/browser_window_controller.h"
59#import "chrome/browser/ui/cocoa/confirm_quit.h"
60#import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
61#import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h"
62#import "chrome/browser/ui/cocoa/history_menu_bridge.h"
63#include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
64#import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h"
65#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
66#import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
67#include "chrome/browser/ui/cocoa/task_manager_mac.h"
68#include "chrome/browser/ui/extensions/application_launch.h"
69#include "chrome/browser/ui/host_desktop.h"
70#include "chrome/browser/ui/startup/startup_browser_creator.h"
71#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
72#include "chrome/common/chrome_paths_internal.h"
73#include "chrome/common/chrome_switches.h"
74#include "chrome/common/cloud_print/cloud_print_class_mac.h"
75#include "chrome/common/extensions/extension_constants.h"
76#include "chrome/common/mac/app_mode_common.h"
77#include "chrome/common/pref_names.h"
78#include "chrome/common/url_constants.h"
79#include "components/signin/core/common/profile_management_switches.h"
80#include "components/signin/core/browser/signin_manager.h"
81#include "content/public/browser/browser_thread.h"
82#include "content/public/browser/download_manager.h"
83#include "content/public/browser/notification_service.h"
84#include "content/public/browser/notification_types.h"
85#include "content/public/browser/plugin_service.h"
86#include "content/public/browser/user_metrics.h"
87#include "extensions/browser/extension_system.h"
88#include "grit/chromium_strings.h"
89#include "grit/generated_resources.h"
90#include "net/base/filename_util.h"
91#include "ui/base/cocoa/focus_window_set.h"
92#include "ui/base/l10n/l10n_util.h"
93#include "ui/base/l10n/l10n_util_mac.h"
94
95using base::UserMetricsAction;
96using content::BrowserContext;
97using content::BrowserThread;
98using content::DownloadManager;
99
100namespace {
101
102// Declare notification names from the 10.7 SDK.
103#if !defined(MAC_OS_X_VERSION_10_7) || \
104    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
105NSString* NSPopoverDidShowNotification = @"NSPopoverDidShowNotification";
106NSString* NSPopoverDidCloseNotification = @"NSPopoverDidCloseNotification";
107#endif
108
109// How long we allow a workspace change notification to wait to be
110// associated with a dock activation. The animation lasts 250ms. See
111// applicationShouldHandleReopen:hasVisibleWindows:.
112static const int kWorkspaceChangeTimeoutMs = 500;
113
114// True while AppController is calling chrome::NewEmptyWindow(). We need a
115// global flag here, analogue to StartupBrowserCreator::InProcessStartup()
116// because otherwise the SessionService will try to restore sessions when we
117// make a new window while there are no other active windows.
118bool g_is_opening_new_window = false;
119
120// Activates a browser window having the given profile (the last one active) if
121// possible and returns a pointer to the activate |Browser| or NULL if this was
122// not possible. If the last active browser is minimized (in particular, if
123// there are only minimized windows), it will unminimize it.
124Browser* ActivateBrowser(Profile* profile) {
125  Browser* browser = chrome::FindLastActiveWithProfile(profile,
126      chrome::HOST_DESKTOP_TYPE_NATIVE);
127  if (browser)
128    browser->window()->Activate();
129  return browser;
130}
131
132// Creates an empty browser window with the given profile and returns a pointer
133// to the new |Browser|.
134Browser* CreateBrowser(Profile* profile) {
135  {
136    base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
137    chrome::NewEmptyWindow(profile, chrome::HOST_DESKTOP_TYPE_NATIVE);
138  }
139
140  Browser* browser = chrome::GetLastActiveBrowser();
141  CHECK(browser);
142  return browser;
143}
144
145// Activates a browser window having the given profile (the last one active) if
146// possible or creates an empty one if necessary. Returns a pointer to the
147// activated/new |Browser|.
148Browser* ActivateOrCreateBrowser(Profile* profile) {
149  if (Browser* browser = ActivateBrowser(profile))
150    return browser;
151  return CreateBrowser(profile);
152}
153
154CFStringRef BaseBundleID_CFString() {
155  NSString* base_bundle_id =
156      [NSString stringWithUTF8String:base::mac::BaseBundleID()];
157  return base::mac::NSToCFCast(base_bundle_id);
158}
159
160// This callback synchronizes preferences (under "org.chromium.Chromium" or
161// "com.google.Chrome"), in particular, writes them out to disk.
162void PrefsSyncCallback() {
163  if (!CFPreferencesAppSynchronize(BaseBundleID_CFString()))
164    LOG(WARNING) << "Error recording application bundle path.";
165}
166
167// Record the location of the application bundle (containing the main framework)
168// from which Chromium was loaded. This is used by app mode shims to find
169// Chromium.
170void RecordLastRunAppBundlePath() {
171  // Going up three levels from |chrome::GetVersionedDirectory()| gives the
172  // real, user-visible app bundle directory. (The alternatives give either the
173  // framework's path or the initial app's path, which may be an app mode shim
174  // or a unit test.)
175  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
176
177  base::FilePath app_bundle_path =
178      chrome::GetVersionedDirectory().DirName().DirName().DirName();
179  base::ScopedCFTypeRef<CFStringRef> app_bundle_path_cfstring(
180      base::SysUTF8ToCFStringRef(app_bundle_path.value()));
181  CFPreferencesSetAppValue(
182      base::mac::NSToCFCast(app_mode::kLastRunAppBundlePathPrefsKey),
183      app_bundle_path_cfstring, BaseBundleID_CFString());
184
185  // Sync after a delay avoid I/O contention on startup; 1500 ms is plenty.
186  BrowserThread::PostDelayedTask(
187      BrowserThread::FILE, FROM_HERE,
188      base::Bind(&PrefsSyncCallback),
189      base::TimeDelta::FromMilliseconds(1500));
190}
191
192bool IsProfileSignedOut(Profile* profile) {
193  // The signed out status only makes sense at the moment in the context of the
194  // --new-profile-management flag.
195  if (!switches::IsNewProfileManagement())
196    return false;
197  ProfileInfoCache& cache =
198      g_browser_process->profile_manager()->GetProfileInfoCache();
199  size_t profile_index = cache.GetIndexOfProfileWithPath(profile->GetPath());
200  if (profile_index == std::string::npos)
201    return false;
202  return cache.ProfileIsSigninRequiredAtIndex(profile_index);
203}
204
205}  // anonymous namespace
206
207@interface AppController (Private)
208- (void)initMenuState;
209- (void)initProfileMenu;
210- (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item;
211- (void)updateDisplayMessageCenterPrefMenuItem:(NSMenuItem*)item;
212- (void)registerServicesMenuTypesTo:(NSApplication*)app;
213- (void)openUrls:(const std::vector<GURL>&)urls;
214- (void)getUrl:(NSAppleEventDescriptor*)event
215     withReply:(NSAppleEventDescriptor*)reply;
216- (void)windowLayeringDidChange:(NSNotification*)inNotification;
217- (void)activeSpaceDidChange:(NSNotification*)inNotification;
218- (void)windowChangedToProfile:(Profile*)profile;
219- (void)checkForAnyKeyWindows;
220- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
221- (BOOL)shouldQuitWithInProgressDownloads;
222- (void)executeApplication:(id)sender;
223- (void)profileWasRemoved:(const base::FilePath&)profilePath;
224@end
225
226class AppControllerProfileObserver : public ProfileInfoCacheObserver {
227 public:
228  AppControllerProfileObserver(
229      ProfileManager* profile_manager, AppController* app_controller)
230      : profile_manager_(profile_manager),
231        app_controller_(app_controller) {
232    DCHECK(profile_manager_);
233    DCHECK(app_controller_);
234    profile_manager_->GetProfileInfoCache().AddObserver(this);
235  }
236
237  virtual ~AppControllerProfileObserver() {
238    DCHECK(profile_manager_);
239    profile_manager_->GetProfileInfoCache().RemoveObserver(this);
240  }
241
242 private:
243  // ProfileInfoCacheObserver implementation:
244
245  virtual void OnProfileAdded(const base::FilePath& profile_path) OVERRIDE {
246  }
247
248  virtual void OnProfileWasRemoved(
249      const base::FilePath& profile_path,
250      const base::string16& profile_name) OVERRIDE {
251    // When a profile is deleted we need to notify the AppController,
252    // so it can correctly update its pointer to the last used profile.
253    [app_controller_ profileWasRemoved:profile_path];
254  }
255
256  virtual void OnProfileWillBeRemoved(
257      const base::FilePath& profile_path) OVERRIDE {
258  }
259
260  virtual void OnProfileNameChanged(
261      const base::FilePath& profile_path,
262      const base::string16& old_profile_name) OVERRIDE {
263  }
264
265  virtual void OnProfileAvatarChanged(
266      const base::FilePath& profile_path) OVERRIDE {
267  }
268
269  ProfileManager* profile_manager_;
270
271  AppController* app_controller_;  // Weak; owns us.
272
273  DISALLOW_COPY_AND_ASSIGN(AppControllerProfileObserver);
274};
275
276@implementation AppController
277
278@synthesize startupComplete = startupComplete_;
279
280// This method is called very early in application startup (ie, before
281// the profile is loaded or any preferences have been registered). Defer any
282// user-data initialization until -applicationDidFinishLaunching:.
283- (void)awakeFromNib {
284  // We need to register the handlers early to catch events fired on launch.
285  NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
286  [em setEventHandler:self
287          andSelector:@selector(getUrl:withReply:)
288        forEventClass:kInternetEventClass
289           andEventID:kAEGetURL];
290  [em setEventHandler:self
291          andSelector:@selector(getUrl:withReply:)
292        forEventClass:'WWW!'    // A particularly ancient AppleEvent that dates
293           andEventID:'OURL'];  // back to the Spyglass days.
294
295  // Register for various window layering changes. We use these to update
296  // various UI elements (command-key equivalents, etc) when the frontmost
297  // window changes.
298  NSNotificationCenter* notificationCenter =
299      [NSNotificationCenter defaultCenter];
300  [notificationCenter
301      addObserver:self
302         selector:@selector(windowLayeringDidChange:)
303             name:NSWindowDidBecomeKeyNotification
304           object:nil];
305  [notificationCenter
306      addObserver:self
307         selector:@selector(windowLayeringDidChange:)
308             name:NSWindowDidResignKeyNotification
309           object:nil];
310  [notificationCenter
311      addObserver:self
312         selector:@selector(windowLayeringDidChange:)
313             name:NSWindowDidBecomeMainNotification
314           object:nil];
315  [notificationCenter
316      addObserver:self
317         selector:@selector(windowLayeringDidChange:)
318             name:NSWindowDidResignMainNotification
319           object:nil];
320
321  if (base::mac::IsOSLionOrLater()) {
322    [notificationCenter
323        addObserver:self
324           selector:@selector(popoverDidShow:)
325               name:NSPopoverDidShowNotification
326             object:nil];
327    [notificationCenter
328        addObserver:self
329           selector:@selector(popoverDidClose:)
330               name:NSPopoverDidCloseNotification
331             object:nil];
332  }
333
334  // Register for space change notifications.
335  [[[NSWorkspace sharedWorkspace] notificationCenter]
336    addObserver:self
337       selector:@selector(activeSpaceDidChange:)
338           name:NSWorkspaceActiveSpaceDidChangeNotification
339         object:nil];
340
341  // Set up the command updater for when there are no windows open
342  [self initMenuState];
343
344  // Initialize the Profile menu.
345  [self initProfileMenu];
346}
347
348- (void)unregisterEventHandlers {
349  NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
350  [em removeEventHandlerForEventClass:kInternetEventClass
351                           andEventID:kAEGetURL];
352  [em removeEventHandlerForEventClass:cloud_print::kAECloudPrintClass
353                           andEventID:cloud_print::kAECloudPrintClass];
354  [em removeEventHandlerForEventClass:'WWW!'
355                           andEventID:'OURL'];
356  [[NSNotificationCenter defaultCenter] removeObserver:self];
357  [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
358}
359
360// (NSApplicationDelegate protocol) This is the Apple-approved place to override
361// the default handlers.
362- (void)applicationWillFinishLaunching:(NSNotification*)notification {
363  // Nothing here right now.
364}
365
366- (BOOL)tryToTerminateApplication:(NSApplication*)app {
367  // Check for in-process downloads, and prompt the user if they really want
368  // to quit (and thus cancel downloads). Only check if we're not already
369  // shutting down, else the user might be prompted multiple times if the
370  // download isn't stopped before terminate is called again.
371  if (!browser_shutdown::IsTryingToQuit() &&
372      ![self shouldQuitWithInProgressDownloads])
373    return NO;
374
375  // TODO(viettrungluu): Remove Apple Event handlers here? (It's safe to leave
376  // them in, but I'm not sure about UX; we'd also want to disable other things
377  // though.) http://crbug.com/40861
378
379  // Check if the user really wants to quit by employing the confirm-to-quit
380  // mechanism.
381  if (!browser_shutdown::IsTryingToQuit() &&
382      [self applicationShouldTerminate:app] != NSTerminateNow)
383    return NO;
384
385  // Check for active apps. If quitting is prevented, only close browsers and
386  // sessions.
387  if (!browser_shutdown::IsTryingToQuit() &&
388      quitWithAppsController_ && !quitWithAppsController_->ShouldQuit()) {
389    content::NotificationService::current()->Notify(
390        chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
391        content::NotificationService::AllSources(),
392        content::NotificationService::NoDetails());
393    // This will close all browser sessions.
394    chrome::CloseAllBrowsers();
395    return NO;
396  }
397
398  size_t num_browsers = chrome::GetTotalBrowserCount();
399
400  // Initiate a shutdown (via chrome::CloseAllBrowsersAndQuit()) if we aren't
401  // already shutting down.
402  if (!browser_shutdown::IsTryingToQuit()) {
403    content::NotificationService::current()->Notify(
404        chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
405        content::NotificationService::AllSources(),
406        content::NotificationService::NoDetails());
407    chrome::CloseAllBrowsersAndQuit();
408  }
409
410  return num_browsers == 0 ? YES : NO;
411}
412
413- (void)stopTryingToTerminateApplication:(NSApplication*)app {
414  if (browser_shutdown::IsTryingToQuit()) {
415    // Reset the "trying to quit" state, so that closing all browser windows
416    // will no longer lead to termination.
417    browser_shutdown::SetTryingToQuit(false);
418
419    // TODO(viettrungluu): Were we to remove Apple Event handlers above, we
420    // would have to reinstall them here. http://crbug.com/40861
421  }
422}
423
424- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app {
425  // If there are no windows, quit immediately.
426  if (chrome::BrowserIterator().done() &&
427      !apps::AppWindowRegistry::IsAppWindowRegisteredInAnyProfile(0)) {
428    return NSTerminateNow;
429  }
430
431  // Check if the preference is turned on.
432  const PrefService* prefs = g_browser_process->local_state();
433  if (!prefs->GetBoolean(prefs::kConfirmToQuitEnabled)) {
434    confirm_quit::RecordHistogram(confirm_quit::kNoConfirm);
435    return NSTerminateNow;
436  }
437
438  // If the application is going to terminate as the result of a Cmd+Q
439  // invocation, use the special sauce to prevent accidental quitting.
440  // http://dev.chromium.org/developers/design-documents/confirm-to-quit-experiment
441
442  // This logic is only for keyboard-initiated quits.
443  if (![ConfirmQuitPanelController eventTriggersFeature:[app currentEvent]])
444    return NSTerminateNow;
445
446  return [[ConfirmQuitPanelController sharedController]
447      runModalLoopForApplication:app];
448}
449
450// Called when the app is shutting down. Clean-up as appropriate.
451- (void)applicationWillTerminate:(NSNotification*)aNotification {
452  // There better be no browser windows left at this point.
453  CHECK_EQ(0u, chrome::GetTotalBrowserCount());
454
455  // Tell BrowserList not to keep the browser process alive. Once all the
456  // browsers get dealloc'd, it will stop the RunLoop and fall back into main().
457  chrome::DecrementKeepAliveCount();
458
459  // Reset all pref watching, as this object outlives the prefs system.
460  profilePrefRegistrar_.reset();
461  localPrefRegistrar_.RemoveAll();
462
463  [self unregisterEventHandlers];
464
465  appShimMenuController_.reset();
466}
467
468- (void)didEndMainMessageLoop {
469  DCHECK_EQ(0u, chrome::GetBrowserCount([self lastProfile],
470                                        chrome::HOST_DESKTOP_TYPE_NATIVE));
471  if (!chrome::GetBrowserCount([self lastProfile],
472                               chrome::HOST_DESKTOP_TYPE_NATIVE)) {
473    // As we're shutting down, we need to nuke the TabRestoreService, which
474    // will start the shutdown of the NavigationControllers and allow for
475    // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
476    // and may end up crashing when some thread tries to use the IO thread (or
477    // another thread) that is no longer valid.
478    TabRestoreServiceFactory::ResetForProfile([self lastProfile]);
479  }
480}
481
482// If the window has a tab controller, make "close window" be cmd-shift-w,
483// otherwise leave it as the normal cmd-w. Capitalization of the key equivalent
484// affects whether the shift modifier is used.
485- (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)enableCloseTabShortcut {
486  [closeWindowMenuItem_ setKeyEquivalent:(enableCloseTabShortcut ? @"W" :
487                                                                   @"w")];
488  [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
489}
490
491// If the window has a tab controller, make "close tab" take over cmd-w,
492// otherwise it shouldn't have any key-equivalent because it should be disabled.
493- (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)enableCloseTabShortcut {
494  if (enableCloseTabShortcut) {
495    [closeTabMenuItem_ setKeyEquivalent:@"w"];
496    [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
497  } else {
498    [closeTabMenuItem_ setKeyEquivalent:@""];
499    [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
500  }
501}
502
503// Explicitly remove any command-key equivalents from the close tab/window
504// menus so that nothing can go haywire if we get a user action during pending
505// updates.
506- (void)clearCloseMenuItemKeyEquivalents {
507  [closeTabMenuItem_ setKeyEquivalent:@""];
508  [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
509  [closeWindowMenuItem_ setKeyEquivalent:@""];
510  [closeWindowMenuItem_ setKeyEquivalentModifierMask:0];
511}
512
513// See if the focused window window has tabs, and adjust the key equivalents for
514// Close Tab/Close Window accordingly.
515- (void)fixCloseMenuItemKeyEquivalents {
516  fileMenuUpdatePending_ = NO;
517
518  NSWindow* window = [NSApp keyWindow];
519  NSWindow* mainWindow = [NSApp mainWindow];
520  if (!window || ([window parentWindow] == mainWindow)) {
521    // If the key window is a child of the main window (e.g. a bubble), the main
522    // window should be the one that handles the close menu item action.
523    // Also, there might be a small amount of time where there is no key window;
524    // in that case as well, just use our main browser window if there is one.
525    // You might think that we should just always use the main window, but the
526    // "About Chrome" window serves as a counterexample.
527    window = mainWindow;
528  }
529
530  BOOL hasTabs =
531      [[window windowController] isKindOfClass:[TabWindowController class]];
532  BOOL enableCloseTabShortcut = hasTabs && !hasPopover_;
533  [self adjustCloseWindowMenuItemKeyEquivalent:enableCloseTabShortcut];
534  [self adjustCloseTabMenuItemKeyEquivalent:enableCloseTabShortcut];
535}
536
537// Fix up the "close tab/close window" command-key equivalents. We do this
538// after a delay to ensure that window layer state has been set by the time
539// we do the enabling. This should only be called on the main thread, code that
540// calls this (even as a side-effect) from other threads needs to be fixed.
541- (void)delayedFixCloseMenuItemKeyEquivalents {
542  DCHECK([NSThread isMainThread]);
543  if (!fileMenuUpdatePending_) {
544    // The OS prefers keypresses to timers, so it's possible that a cmd-w
545    // can sneak in before this timer fires. In order to prevent that from
546    // having any bad consequences, just clear the keys combos altogether. They
547    // will be reset when the timer eventually fires.
548    if ([NSThread isMainThread]) {
549      fileMenuUpdatePending_ = YES;
550      [self clearCloseMenuItemKeyEquivalents];
551      [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
552                 withObject:nil
553                 afterDelay:0];
554    } else {
555      // This shouldn't be happening, but if it does, force it to the main
556      // thread to avoid dropping the update. Don't mess with
557      // |fileMenuUpdatePending_| as it's not expected to be threadsafe and
558      // there could be a race between the selector finishing and setting the
559      // flag.
560      [self
561          performSelectorOnMainThread:@selector(fixCloseMenuItemKeyEquivalents)
562                           withObject:nil
563                        waitUntilDone:NO];
564    }
565  }
566}
567
568// Called when we get a notification about the window layering changing to
569// update the UI based on the new main window.
570- (void)windowLayeringDidChange:(NSNotification*)notify {
571  [self delayedFixCloseMenuItemKeyEquivalents];
572
573  if ([notify name] == NSWindowDidResignKeyNotification) {
574    // If a window is closed, this notification is fired but |[NSApp keyWindow]|
575    // returns nil regardless of whether any suitable candidates for the key
576    // window remain. It seems that the new key window for the app is not set
577    // until after this notification is fired, so a check is performed after the
578    // run loop is allowed to spin.
579    [self performSelector:@selector(checkForAnyKeyWindows)
580               withObject:nil
581               afterDelay:0.0];
582  }
583
584  // If the window changed to a new BrowserWindowController, update the profile.
585  id windowController = [[notify object] windowController];
586  if (![windowController isKindOfClass:[BrowserWindowController class]])
587    return;
588
589  if ([notify name] == NSWindowDidBecomeMainNotification) {
590    // If the profile is incognito, use the original profile.
591    Profile* newProfile = [windowController profile]->GetOriginalProfile();
592    [self windowChangedToProfile:newProfile];
593  } else if (chrome::GetTotalBrowserCount() == 0) {
594    [self windowChangedToProfile:
595        g_browser_process->profile_manager()->GetLastUsedProfile()];
596  }
597}
598
599- (void)activeSpaceDidChange:(NSNotification*)notify {
600  if (reopenTime_.is_null() ||
601      ![NSApp isActive] ||
602      (base::TimeTicks::Now() - reopenTime_).InMilliseconds() >
603      kWorkspaceChangeTimeoutMs) {
604    return;
605  }
606
607  // The last applicationShouldHandleReopen:hasVisibleWindows: call
608  // happened during a space change. Now that the change has
609  // completed, raise browser windows.
610  reopenTime_ = base::TimeTicks();
611  std::set<NSWindow*> browserWindows;
612  for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
613    Browser* browser = *iter;
614    browserWindows.insert(browser->window()->GetNativeWindow());
615  }
616  if (!browserWindows.empty()) {
617    ui::FocusWindowSetOnCurrentSpace(browserWindows);
618  }
619}
620
621// Called on Lion and later when a popover (e.g. dictionary) is shown.
622- (void)popoverDidShow:(NSNotification*)notify {
623  hasPopover_ = YES;
624  [self fixCloseMenuItemKeyEquivalents];
625}
626
627// Called on Lion and later when a popover (e.g. dictionary) is closed.
628- (void)popoverDidClose:(NSNotification*)notify {
629  hasPopover_ = NO;
630  [self fixCloseMenuItemKeyEquivalents];
631}
632
633// Called when the user has changed browser windows, meaning the backing profile
634// may have changed. This can cause a rebuild of the user-data menus. This is a
635// no-op if the new profile is the same as the current one. This will always be
636// the original profile and never incognito.
637- (void)windowChangedToProfile:(Profile*)profile {
638  if (lastProfile_ == profile)
639    return;
640
641  // Before tearing down the menu controller bridges, return the Cocoa menus to
642  // their initial state.
643  if (bookmarkMenuBridge_.get())
644    bookmarkMenuBridge_->ResetMenu();
645  if (historyMenuBridge_.get())
646    historyMenuBridge_->ResetMenu();
647
648  // Rebuild the menus with the new profile.
649  lastProfile_ = profile;
650
651  bookmarkMenuBridge_.reset(new BookmarkMenuBridge(lastProfile_,
652      [[[NSApp mainMenu] itemWithTag:IDC_BOOKMARKS_MENU] submenu]));
653  // No need to |BuildMenu| here.  It is done lazily upon menu access.
654
655  historyMenuBridge_.reset(new HistoryMenuBridge(lastProfile_));
656  historyMenuBridge_->BuildMenu();
657
658  chrome::BrowserCommandController::
659      UpdateSharedCommandsForIncognitoAvailability(
660          menuState_.get(), lastProfile_);
661  profilePrefRegistrar_.reset(new PrefChangeRegistrar());
662  profilePrefRegistrar_->Init(lastProfile_->GetPrefs());
663  profilePrefRegistrar_->Add(
664      prefs::kIncognitoModeAvailability,
665      base::Bind(&chrome::BrowserCommandController::
666                     UpdateSharedCommandsForIncognitoAvailability,
667                 menuState_.get(),
668                 lastProfile_));
669}
670
671- (void)checkForAnyKeyWindows {
672  if ([NSApp keyWindow])
673    return;
674
675  content::NotificationService::current()->Notify(
676      chrome::NOTIFICATION_NO_KEY_WINDOW,
677      content::NotificationService::AllSources(),
678      content::NotificationService::NoDetails());
679}
680
681// If the auto-update interval is not set, make it 5 hours.
682// Placed here for 2 reasons:
683// 1) Same spot as other Pref stuff
684// 2) Try and be friendly by keeping this after app launch
685- (void)setUpdateCheckInterval {
686#if defined(GOOGLE_CHROME_BUILD)
687  CFStringRef app = CFSTR("com.google.Keystone.Agent");
688  CFStringRef checkInterval = CFSTR("checkInterval");
689  CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
690  if (!plist) {
691    const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
692    NSNumber* value = [NSNumber numberWithFloat:fiveHoursInSeconds];
693    CFPreferencesSetAppValue(checkInterval, value, app);
694    CFPreferencesAppSynchronize(app);
695  }
696#endif
697}
698
699- (void)openStartupUrls {
700  // On Mac, the URLs are passed in via Cocoa, not command line. The Chrome
701  // NSApplication is created in MainMessageLoop, and then the shortcut urls
702  // are passed in via Apple events. At this point, the first browser is
703  // already loaded in PreMainMessageLoop. If we initialize NSApplication
704  // before PreMainMessageLoop to capture shortcut URL events, it may cause
705  // more problems because it relies on things created in PreMainMessageLoop
706  // and may break existing message loop design.
707  if (startupUrls_.empty())
708    return;
709
710  // If there's only 1 tab and the tab is NTP, close this NTP tab and open all
711  // startup urls in new tabs, because the omnibox will stay focused if we
712  // load url in NTP tab.
713  Browser* browser = chrome::GetLastActiveBrowser();
714  int startupIndex = TabStripModel::kNoTab;
715  content::WebContents* startupContent = NULL;
716
717  if (browser && browser->tab_strip_model()->count() == 1) {
718    startupIndex = browser->tab_strip_model()->active_index();
719    startupContent = browser->tab_strip_model()->GetActiveWebContents();
720  }
721
722  if (startupUrls_.size()) {
723    [self openUrls:startupUrls_];
724    startupUrls_.clear();
725  }
726
727  if (startupIndex != TabStripModel::kNoTab &&
728      startupContent->GetVisibleURL() == GURL(chrome::kChromeUINewTabURL)) {
729    browser->tab_strip_model()->CloseWebContentsAt(startupIndex,
730        TabStripModel::CLOSE_NONE);
731  }
732}
733
734// This is called after profiles have been loaded and preferences registered.
735// It is safe to access the default profile here.
736- (void)applicationDidFinishLaunching:(NSNotification*)notify {
737  // Notify BrowserList to keep the application running so it doesn't go away
738  // when all the browser windows get closed.
739  chrome::IncrementKeepAliveCount();
740
741  [self setUpdateCheckInterval];
742
743  // Start managing the menu for app windows. This needs to be done here because
744  // main menu item titles are not yet initialized in awakeFromNib.
745  [self initAppShimMenuController];
746
747  // If enabled, keep Chrome alive when apps are open instead of quitting all
748  // apps.
749  quitWithAppsController_ = new QuitWithAppsController();
750
751  // Build up the encoding menu, the order of the items differs based on the
752  // current locale (see http://crbug.com/7647 for details).
753  // We need a valid g_browser_process to get the profile which is why we can't
754  // call this from awakeFromNib.
755  NSMenu* viewMenu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
756  NSMenuItem* encodingMenuItem = [viewMenu itemWithTag:IDC_ENCODING_MENU];
757  NSMenu* encodingMenu = [encodingMenuItem submenu];
758  EncodingMenuControllerDelegate::BuildEncodingMenu([self lastProfile],
759                                                    encodingMenu);
760
761  // Instantiate the ProfileInfoCache observer so that we can get
762  // notified when a profile is deleted.
763  profileInfoCacheObserver_.reset(new AppControllerProfileObserver(
764      g_browser_process->profile_manager(), self));
765
766  // Since Chrome is localized to more languages than the OS, tell Cocoa which
767  // menu is the Help so it can add the search item to it.
768  [NSApp setHelpMenu:helpMenu_];
769
770  // Record the path to the (browser) app bundle; this is used by the app mode
771  // shim.  It has to be done in FILE thread because getting the path requires
772  // I/O.
773  BrowserThread::PostTask(
774      BrowserThread::FILE, FROM_HERE,
775      base::Bind(&RecordLastRunAppBundlePath));
776
777  // Makes "Services" menu items available.
778  [self registerServicesMenuTypesTo:[notify object]];
779
780  startupComplete_ = YES;
781
782  [self openStartupUrls];
783
784  PrefService* localState = g_browser_process->local_state();
785  if (localState) {
786    localPrefRegistrar_.Init(localState);
787    localPrefRegistrar_.Add(
788        prefs::kAllowFileSelectionDialogs,
789        base::Bind(&chrome::BrowserCommandController::UpdateOpenFileState,
790                   menuState_.get()));
791  }
792}
793
794// This is called after profiles have been loaded and preferences registered.
795// It is safe to access the default profile here.
796- (void)applicationDidBecomeActive:(NSNotification*)notify {
797  content::PluginService::GetInstance()->AppActivated();
798}
799
800// Helper function for populating and displaying the in progress downloads at
801// exit alert panel.
802- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
803  NSString* titleText = nil;
804  NSString* explanationText = nil;
805  NSString* waitTitle = nil;
806  NSString* exitTitle = nil;
807
808  // Set the dialog text based on whether or not there are multiple downloads.
809  if (downloadCount == 1) {
810    // Dialog text: warning and explanation.
811    titleText = l10n_util::GetNSString(
812        IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_TITLE);
813    explanationText = l10n_util::GetNSString(
814        IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION);
815  } else {
816    // Dialog text: warning and explanation.
817    titleText = l10n_util::GetNSStringF(
818        IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_TITLE,
819        base::IntToString16(downloadCount));
820    explanationText = l10n_util::GetNSString(
821        IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION);
822  }
823  // Cancel download and exit button text.
824  exitTitle = l10n_util::GetNSString(
825      IDS_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL);
826
827  // Wait for download button text.
828  waitTitle = l10n_util::GetNSString(
829      IDS_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
830
831  // 'waitButton' is the default choice.
832  int choice = NSRunAlertPanel(titleText, @"%@",
833                               waitTitle, exitTitle, nil, explanationText);
834  return choice == NSAlertDefaultReturn ? YES : NO;
835}
836
837// Check all profiles for in progress downloads, and if we find any, prompt the
838// user to see if we should continue to exit (and thus cancel the downloads), or
839// if we should wait.
840- (BOOL)shouldQuitWithInProgressDownloads {
841  ProfileManager* profile_manager = g_browser_process->profile_manager();
842  if (!profile_manager)
843    return YES;
844
845  std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
846  for (size_t i = 0; i < profiles.size(); ++i) {
847    DownloadService* download_service =
848      DownloadServiceFactory::GetForBrowserContext(profiles[i]);
849    DownloadManager* download_manager =
850        (download_service->HasCreatedDownloadManager() ?
851         BrowserContext::GetDownloadManager(profiles[i]) : NULL);
852    if (download_manager &&
853        download_manager->NonMaliciousInProgressCount() > 0) {
854      int downloadCount = download_manager->NonMaliciousInProgressCount();
855      if ([self userWillWaitForInProgressDownloads:downloadCount]) {
856        // Create a new browser window (if necessary) and navigate to the
857        // downloads page if the user chooses to wait.
858        Browser* browser = chrome::FindBrowserWithProfile(
859            profiles[i], chrome::HOST_DESKTOP_TYPE_NATIVE);
860        if (!browser) {
861          browser = new Browser(Browser::CreateParams(
862              profiles[i], chrome::HOST_DESKTOP_TYPE_NATIVE));
863          browser->window()->Show();
864        }
865        DCHECK(browser);
866        chrome::ShowDownloads(browser);
867        return NO;
868      }
869
870      // User wants to exit.
871      return YES;
872    }
873  }
874
875  // No profiles or active downloads found, okay to exit.
876  return YES;
877}
878
879// Called to determine if we should enable the "restore tab" menu item.
880// Checks with the TabRestoreService to see if there's anything there to
881// restore and returns YES if so.
882- (BOOL)canRestoreTab {
883  TabRestoreService* service =
884      TabRestoreServiceFactory::GetForProfile([self lastProfile]);
885  return service && !service->entries().empty();
886}
887
888// Called from the AppControllerProfileObserver every time a profile is deleted.
889- (void)profileWasRemoved:(const base::FilePath&)profilePath {
890  Profile* lastProfile = [self lastProfile];
891
892  // If the lastProfile has been deleted, the profile manager has
893  // already loaded a new one, so the pointer needs to be updated;
894  // otherwise we will try to start up a browser window with a pointer
895  // to the old profile.
896  if (profilePath == lastProfile->GetPath())
897    lastProfile_ = g_browser_process->profile_manager()->GetLastUsedProfile();
898}
899
900// Returns true if there is a modal window (either window- or application-
901// modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
902// sheets) will not count as blocking the browser. But things like open/save
903// dialogs that are window modal will block the browser.
904- (BOOL)keyWindowIsModal {
905  if ([NSApp modalWindow])
906    return YES;
907
908  Browser* browser = chrome::GetLastActiveBrowser();
909  return browser &&
910         [[browser->window()->GetNativeWindow() attachedSheet]
911             isKindOfClass:[NSWindow class]];
912}
913
914// Called to validate menu items when there are no key windows. All the
915// items we care about have been set with the |commandDispatch:| action and
916// a target of FirstResponder in IB. If it's not one of those, let it
917// continue up the responder chain to be handled elsewhere. We pull out the
918// tag as the cross-platform constant to differentiate and dispatch the
919// various commands.
920- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
921  SEL action = [item action];
922  BOOL enable = NO;
923  if (action == @selector(commandDispatch:) ||
924      action == @selector(commandFromDock:)) {
925    NSInteger tag = [item tag];
926    if (menuState_ &&  // NULL in tests.
927        menuState_->SupportsCommand(tag)) {
928      switch (tag) {
929        // The File Menu commands are not automatically disabled by Cocoa when a
930        // dialog sheet obscures the browser window, so we disable several of
931        // them here.  We don't need to include IDC_CLOSE_WINDOW, because
932        // app_controller is only activated when there are no key windows (see
933        // function comment).
934        case IDC_RESTORE_TAB:
935          enable = ![self keyWindowIsModal] && [self canRestoreTab];
936          break;
937        // Browser-level items that open in new tabs should not open if there's
938        // a window- or app-modal dialog.
939        case IDC_OPEN_FILE:
940        case IDC_NEW_TAB:
941        case IDC_SHOW_HISTORY:
942        case IDC_SHOW_BOOKMARK_MANAGER:
943          enable = ![self keyWindowIsModal];
944          break;
945        // Browser-level items that open in new windows.
946        case IDC_TASK_MANAGER:
947          // Allow the user to open a new window if there's a window-modal
948          // dialog.
949          enable = ![self keyWindowIsModal];
950          break;
951        case IDC_SHOW_SYNC_SETUP: {
952          Profile* lastProfile = [self lastProfile];
953          // The profile may be NULL during shutdown -- see
954          // http://code.google.com/p/chromium/issues/detail?id=43048 .
955          //
956          // TODO(akalin,viettrungluu): Figure out whether this method
957          // can be prevented from being called if lastProfile is
958          // NULL.
959          if (!lastProfile) {
960            LOG(WARNING)
961                << "NULL lastProfile detected -- not doing anything";
962            break;
963          }
964          SigninManager* signin = SigninManagerFactory::GetForProfile(
965              lastProfile->GetOriginalProfile());
966          enable = signin->IsSigninAllowed() &&
967              ![self keyWindowIsModal];
968          [BrowserWindowController updateSigninItem:item
969                                         shouldShow:enable
970                                     currentProfile:lastProfile];
971          break;
972        }
973#if defined(GOOGLE_CHROME_BUILD)
974        case IDC_FEEDBACK:
975          enable = NO;
976          break;
977#endif
978        default:
979          enable = menuState_->IsCommandEnabled(tag) ?
980                   ![self keyWindowIsModal] : NO;
981      }
982    }
983  } else if (action == @selector(terminate:)) {
984    enable = YES;
985  } else if (action == @selector(showPreferences:)) {
986    enable = YES;
987  } else if (action == @selector(orderFrontStandardAboutPanel:)) {
988    enable = YES;
989  } else if (action == @selector(commandFromDock:)) {
990    enable = YES;
991  } else if (action == @selector(toggleConfirmToQuit:)) {
992    [self updateConfirmToQuitPrefMenuItem:static_cast<NSMenuItem*>(item)];
993    enable = YES;
994  } else if (action == @selector(toggleDisplayMessageCenter:)) {
995    NSMenuItem* menuItem = static_cast<NSMenuItem*>(item);
996    [self updateDisplayMessageCenterPrefMenuItem:menuItem];
997    enable = YES;
998  } else if (action == @selector(executeApplication:)) {
999    enable = YES;
1000  }
1001  return enable;
1002}
1003
1004// Called when the user picks a menu item when there are no key windows, or when
1005// there is no foreground browser window. Calls through to the browser object to
1006// execute the command. This assumes that the command is supported and doesn't
1007// check, otherwise it should have been disabled in the UI in
1008// |-validateUserInterfaceItem:|.
1009- (void)commandDispatch:(id)sender {
1010  Profile* lastProfile = [self safeLastProfileForNewWindows];
1011
1012  // Handle the case where we're dispatching a command from a sender that's in a
1013  // browser window. This means that the command came from a background window
1014  // and is getting here because the foreground window is not a browser window.
1015  if ([sender respondsToSelector:@selector(window)]) {
1016    id delegate = [[sender window] windowController];
1017    if ([delegate isKindOfClass:[BrowserWindowController class]]) {
1018      [delegate commandDispatch:sender];
1019      return;
1020    }
1021  }
1022
1023  // Ignore commands during session restore's browser creation.  It uses a
1024  // nested message loop and commands dispatched during this operation cause
1025  // havoc.
1026  if (SessionRestore::IsRestoring(lastProfile) &&
1027      base::MessageLoop::current()->IsNested())
1028    return;
1029
1030  NSInteger tag = [sender tag];
1031
1032  // If there are no browser windows, and we are trying to open a browser
1033  // for a locked profile, we have to show the User Manager instead as the
1034  // locked profile needs authentication.
1035  if (IsProfileSignedOut(lastProfile)) {
1036    chrome::ShowUserManager(lastProfile->GetPath());
1037    return;
1038  }
1039
1040  switch (tag) {
1041    case IDC_NEW_TAB:
1042      // Create a new tab in an existing browser window (which we activate) if
1043      // possible.
1044      if (Browser* browser = ActivateBrowser(lastProfile)) {
1045        chrome::ExecuteCommand(browser, IDC_NEW_TAB);
1046        break;
1047      }
1048      // Else fall through to create new window.
1049    case IDC_NEW_WINDOW:
1050      CreateBrowser(lastProfile);
1051      break;
1052    case IDC_FOCUS_LOCATION:
1053      chrome::ExecuteCommand(ActivateOrCreateBrowser(lastProfile),
1054                             IDC_FOCUS_LOCATION);
1055      break;
1056    case IDC_FOCUS_SEARCH:
1057      chrome::ExecuteCommand(ActivateOrCreateBrowser(lastProfile),
1058                             IDC_FOCUS_SEARCH);
1059      break;
1060    case IDC_NEW_INCOGNITO_WINDOW:
1061      CreateBrowser(lastProfile->GetOffTheRecordProfile());
1062      break;
1063    case IDC_RESTORE_TAB:
1064      // There is only the native desktop on Mac.
1065      chrome::OpenWindowWithRestoredTabs(lastProfile,
1066                                         chrome::HOST_DESKTOP_TYPE_NATIVE);
1067      break;
1068    case IDC_OPEN_FILE:
1069      chrome::ExecuteCommand(CreateBrowser(lastProfile), IDC_OPEN_FILE);
1070      break;
1071    case IDC_CLEAR_BROWSING_DATA: {
1072      // There may not be a browser open, so use the default profile.
1073      if (Browser* browser = ActivateBrowser(lastProfile)) {
1074        chrome::ShowClearBrowsingDataDialog(browser);
1075      } else {
1076        chrome::OpenClearBrowsingDataDialogWindow(lastProfile);
1077      }
1078      break;
1079    }
1080    case IDC_IMPORT_SETTINGS: {
1081      if (Browser* browser = ActivateBrowser(lastProfile)) {
1082        chrome::ShowImportDialog(browser);
1083      } else {
1084        chrome::OpenImportSettingsDialogWindow(lastProfile);
1085      }
1086      break;
1087    }
1088    case IDC_SHOW_BOOKMARK_MANAGER:
1089      content::RecordAction(UserMetricsAction("ShowBookmarkManager"));
1090      if (Browser* browser = ActivateBrowser(lastProfile)) {
1091        chrome::ShowBookmarkManager(browser);
1092      } else {
1093        // No browser window, so create one for the bookmark manager tab.
1094        chrome::OpenBookmarkManagerWindow(lastProfile);
1095      }
1096      break;
1097    case IDC_SHOW_HISTORY:
1098      if (Browser* browser = ActivateBrowser(lastProfile))
1099        chrome::ShowHistory(browser);
1100      else
1101        chrome::OpenHistoryWindow(lastProfile);
1102      break;
1103    case IDC_SHOW_DOWNLOADS:
1104      if (Browser* browser = ActivateBrowser(lastProfile))
1105        chrome::ShowDownloads(browser);
1106      else
1107        chrome::OpenDownloadsWindow(lastProfile);
1108      break;
1109    case IDC_MANAGE_EXTENSIONS:
1110      if (Browser* browser = ActivateBrowser(lastProfile))
1111        chrome::ShowExtensions(browser, std::string());
1112      else
1113        chrome::OpenExtensionsWindow(lastProfile);
1114      break;
1115    case IDC_HELP_PAGE_VIA_MENU:
1116      if (Browser* browser = ActivateBrowser(lastProfile))
1117        chrome::ShowHelp(browser, chrome::HELP_SOURCE_MENU);
1118      else
1119        chrome::OpenHelpWindow(lastProfile, chrome::HELP_SOURCE_MENU);
1120      break;
1121    case IDC_SHOW_SYNC_SETUP:
1122      if (Browser* browser = ActivateBrowser(lastProfile)) {
1123        chrome::ShowBrowserSignin(browser, signin::SOURCE_MENU);
1124      } else {
1125        chrome::OpenSyncSetupWindow(lastProfile, signin::SOURCE_MENU);
1126      }
1127      break;
1128    case IDC_TASK_MANAGER:
1129      content::RecordAction(UserMetricsAction("TaskManager"));
1130      TaskManagerMac::Show();
1131      break;
1132    case IDC_OPTIONS:
1133      [self showPreferences:sender];
1134      break;
1135  }
1136}
1137
1138// Run a (background) application in a new tab.
1139- (void)executeApplication:(id)sender {
1140  NSInteger tag = [sender tag];
1141  Profile* profile = [self lastProfile];
1142  DCHECK(profile);
1143  BackgroundApplicationListModel applications(profile);
1144  DCHECK(tag >= 0 &&
1145         tag < static_cast<int>(applications.size()));
1146  const extensions::Extension* extension = applications.GetExtension(tag);
1147  BackgroundModeManager::LaunchBackgroundApplication(profile, extension);
1148}
1149
1150// Same as |-commandDispatch:|, but executes commands using a disposition
1151// determined by the key flags. This will get called in the case where the
1152// frontmost window is not a browser window, and the user has command-clicked
1153// a button in a background browser window whose action is
1154// |-commandDispatchUsingKeyModifiers:|
1155- (void)commandDispatchUsingKeyModifiers:(id)sender {
1156  DCHECK(sender);
1157  if ([sender respondsToSelector:@selector(window)]) {
1158    id delegate = [[sender window] windowController];
1159    if ([delegate isKindOfClass:[BrowserWindowController class]]) {
1160      [delegate commandDispatchUsingKeyModifiers:sender];
1161    }
1162  }
1163}
1164
1165// NSApplication delegate method called when someone clicks on the dock icon.
1166// To match standard mac behavior, we should open a new window if there are no
1167// browser windows.
1168- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
1169                    hasVisibleWindows:(BOOL)hasVisibleWindows {
1170  // If the browser is currently trying to quit, don't do anything and return NO
1171  // to prevent AppKit from doing anything.
1172  // TODO(rohitrao): Remove this code when http://crbug.com/40861 is resolved.
1173  if (browser_shutdown::IsTryingToQuit())
1174    return NO;
1175
1176  // Bring all browser windows to the front. Specifically, this brings them in
1177  // front of any app windows. FocusWindowSet will also unminimize the most
1178  // recently minimized window if no windows in the set are visible.
1179  // If there are any, return here. Otherwise, the windows are panels or
1180  // notifications so we still need to open a new window.
1181  if (hasVisibleWindows) {
1182    std::set<NSWindow*> browserWindows;
1183    for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
1184      Browser* browser = *iter;
1185      browserWindows.insert(browser->window()->GetNativeWindow());
1186    }
1187    if (!browserWindows.empty()) {
1188      NSWindow* keyWindow = [NSApp keyWindow];
1189      if (keyWindow && ![keyWindow isOnActiveSpace]) {
1190        // The key window is not on the active space. We must be mid-animation
1191        // for a space transition triggered by the dock. Delay the call to
1192        // |ui::FocusWindowSet| until the transition completes. Otherwise, the
1193        // wrong space's windows get raised, resulting in an off-screen key
1194        // window. It does not work to |ui::FocusWindowSet| twice, once here
1195        // and once in |activeSpaceDidChange:|, as that appears to break when
1196        // the omnibox is focused.
1197        //
1198        // This check relies on OS X setting the key window to a window on the
1199        // target space before calling this method.
1200        //
1201        // See http://crbug.com/309656.
1202        reopenTime_ = base::TimeTicks::Now();
1203      } else {
1204        ui::FocusWindowSetOnCurrentSpace(browserWindows);
1205      }
1206      // Return NO; we've done (or soon will do) the deminiaturize, so
1207      // AppKit shouldn't do anything.
1208      return NO;
1209    }
1210  }
1211
1212  // If launched as a hidden login item (due to installation of a persistent app
1213  // or by the user, for example in System Preferences->Accounts->Login Items),
1214  // allow session to be restored first time the user clicks on a Dock icon.
1215  // Normally, it'd just open a new empty page.
1216  {
1217    static BOOL doneOnce = NO;
1218    BOOL attemptRestore = apps::AppShimHandler::ShouldRestoreSession() ||
1219        (!doneOnce && base::mac::WasLaunchedAsHiddenLoginItem());
1220    doneOnce = YES;
1221    if (attemptRestore) {
1222      SessionService* sessionService =
1223          SessionServiceFactory::GetForProfileForSessionRestore(
1224              [self lastProfile]);
1225      if (sessionService &&
1226          sessionService->RestoreIfNecessary(std::vector<GURL>()))
1227        return NO;
1228    }
1229  }
1230
1231  // Otherwise open a new window.
1232  // If the last profile was locked, we have to open the User Manager, as the
1233  // profile requires authentication. Similarly, because guest mode is
1234  // implemented as forced incognito, we can't open a new guest browser either,
1235  // so we have to show the User Manager as well.
1236  Profile* lastProfile = [self lastProfile];
1237  if (lastProfile->IsGuestSession() || IsProfileSignedOut(lastProfile))
1238    chrome::ShowUserManager(lastProfile->GetPath());
1239  else
1240    CreateBrowser(lastProfile);
1241
1242  // We've handled the reopen event, so return NO to tell AppKit not
1243  // to do anything.
1244  return NO;
1245}
1246
1247- (void)initMenuState {
1248  menuState_.reset(new CommandUpdater(NULL));
1249  menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
1250  menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
1251  menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
1252  menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
1253  menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
1254  menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
1255  menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
1256  menuState_->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true);
1257  menuState_->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true);
1258  menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
1259  menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
1260  menuState_->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true);
1261  menuState_->UpdateCommandEnabled(IDC_HELP_PAGE_VIA_MENU, true);
1262  menuState_->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true);
1263#if defined(GOOGLE_CHROME_BUILD)
1264  menuState_->UpdateCommandEnabled(IDC_FEEDBACK, true);
1265#endif
1266  menuState_->UpdateCommandEnabled(IDC_SHOW_SYNC_SETUP, true);
1267  menuState_->UpdateCommandEnabled(IDC_TASK_MANAGER, true);
1268}
1269
1270// Conditionally adds the Profile menu to the main menu bar.
1271- (void)initProfileMenu {
1272  NSMenu* mainMenu = [NSApp mainMenu];
1273  NSMenuItem* profileMenu = [mainMenu itemWithTag:IDC_PROFILE_MAIN_MENU];
1274
1275  if (!profiles::IsMultipleProfilesEnabled()) {
1276    [mainMenu removeItem:profileMenu];
1277    return;
1278  }
1279
1280  // The controller will unhide the menu if necessary.
1281  [profileMenu setHidden:YES];
1282
1283  profileMenuController_.reset(
1284      [[ProfileMenuController alloc] initWithMainMenuItem:profileMenu]);
1285}
1286
1287// The Confirm to Quit preference is atypical in that the preference lives in
1288// the app menu right above the Quit menu item. This method will refresh the
1289// display of that item depending on the preference state.
1290- (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item {
1291  // Format the string so that the correct key equivalent is displayed.
1292  NSString* acceleratorString = [ConfirmQuitPanelController keyCommandString];
1293  NSString* title = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_OPTION,
1294      base::SysNSStringToUTF16(acceleratorString));
1295  [item setTitle:title];
1296
1297  const PrefService* prefService = g_browser_process->local_state();
1298  bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1299  [item setState:enabled ? NSOnState : NSOffState];
1300}
1301
1302- (void)updateDisplayMessageCenterPrefMenuItem:(NSMenuItem*)item {
1303  const PrefService* prefService = g_browser_process->local_state();
1304  bool enabled = prefService->GetBoolean(prefs::kMessageCenterShowIcon);
1305  // The item should be checked if "show icon" is false, since the text reads
1306  // "Hide notification center icon."
1307  [item setState:enabled ? NSOffState : NSOnState];
1308}
1309
1310- (void)registerServicesMenuTypesTo:(NSApplication*)app {
1311  // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which
1312  // handles requests from services.
1313  NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
1314  [app registerServicesMenuSendTypes:types returnTypes:types];
1315}
1316
1317- (Profile*)lastProfile {
1318  // Return the profile of the last-used BrowserWindowController, if available.
1319  if (lastProfile_)
1320    return lastProfile_;
1321
1322  // On first launch, use the logic that ChromeBrowserMain uses to determine
1323  // the initial profile.
1324  ProfileManager* profile_manager = g_browser_process->profile_manager();
1325  if (!profile_manager)
1326    return NULL;
1327
1328  return profile_manager->GetProfile(GetStartupProfilePath(
1329      profile_manager->user_data_dir(),
1330      *CommandLine::ForCurrentProcess()));
1331}
1332
1333- (Profile*)safeLastProfileForNewWindows {
1334  Profile* profile = [self lastProfile];
1335
1336  // Guest sessions must always be OffTheRecord. Use that when opening windows.
1337  if (profile->IsGuestSession())
1338    return profile->GetOffTheRecordProfile();
1339
1340  return profile;
1341}
1342
1343// Various methods to open URLs that we get in a native fashion. We use
1344// StartupBrowserCreator here because on the other platforms, URLs to open come
1345// through the ProcessSingleton, and it calls StartupBrowserCreator. It's best
1346// to bottleneck the openings through that for uniform handling.
1347
1348- (void)openUrls:(const std::vector<GURL>&)urls {
1349  // If the browser hasn't started yet, just queue up the URLs.
1350  if (!startupComplete_) {
1351    startupUrls_.insert(startupUrls_.end(), urls.begin(), urls.end());
1352    return;
1353  }
1354
1355  Browser* browser = chrome::GetLastActiveBrowser();
1356  // if no browser window exists then create one with no tabs to be filled in
1357  if (!browser) {
1358    browser = new Browser(Browser::CreateParams(
1359        [self lastProfile], chrome::HOST_DESKTOP_TYPE_NATIVE));
1360    browser->window()->Show();
1361  }
1362
1363  CommandLine dummy(CommandLine::NO_PROGRAM);
1364  chrome::startup::IsFirstRun first_run = first_run::IsChromeFirstRun() ?
1365      chrome::startup::IS_FIRST_RUN : chrome::startup::IS_NOT_FIRST_RUN;
1366  StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
1367  launch.OpenURLsInBrowser(browser, false, urls, browser->host_desktop_type());
1368}
1369
1370- (void)getUrl:(NSAppleEventDescriptor*)event
1371     withReply:(NSAppleEventDescriptor*)reply {
1372  NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
1373                      stringValue];
1374
1375  GURL gurl(base::SysNSStringToUTF8(urlStr));
1376  std::vector<GURL> gurlVector;
1377  gurlVector.push_back(gurl);
1378
1379  [self openUrls:gurlVector];
1380}
1381
1382- (void)application:(NSApplication*)sender
1383          openFiles:(NSArray*)filenames {
1384  std::vector<GURL> gurlVector;
1385  for (NSString* file in filenames) {
1386    GURL gurl =
1387        net::FilePathToFileURL(base::FilePath([file fileSystemRepresentation]));
1388    gurlVector.push_back(gurl);
1389  }
1390  if (!gurlVector.empty())
1391    [self openUrls:gurlVector];
1392  else
1393    NOTREACHED() << "Nothing to open!";
1394
1395  [sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
1396}
1397
1398// Show the preferences window, or bring it to the front if it's already
1399// visible.
1400- (IBAction)showPreferences:(id)sender {
1401  if (Browser* browser = ActivateBrowser([self lastProfile])) {
1402    // Show options tab in the active browser window.
1403    chrome::ShowSettings(browser);
1404  } else {
1405    // No browser window, so create one for the options tab.
1406    chrome::OpenOptionsWindow([self safeLastProfileForNewWindows]);
1407  }
1408}
1409
1410- (IBAction)orderFrontStandardAboutPanel:(id)sender {
1411  if (Browser* browser = ActivateBrowser([self lastProfile])) {
1412    chrome::ShowAboutChrome(browser);
1413  } else {
1414    // No browser window, so create one for the about tab.
1415    chrome::OpenAboutWindow([self safeLastProfileForNewWindows]);
1416  }
1417}
1418
1419- (IBAction)toggleConfirmToQuit:(id)sender {
1420  PrefService* prefService = g_browser_process->local_state();
1421  bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1422  prefService->SetBoolean(prefs::kConfirmToQuitEnabled, !enabled);
1423}
1424
1425- (IBAction)toggleDisplayMessageCenter:(id)sender {
1426  PrefService* prefService = g_browser_process->local_state();
1427  bool enabled = prefService->GetBoolean(prefs::kMessageCenterShowIcon);
1428  prefService->SetBoolean(prefs::kMessageCenterShowIcon, !enabled);
1429}
1430
1431// Explicitly bring to the foreground when creating new windows from the dock.
1432- (void)commandFromDock:(id)sender {
1433  [NSApp activateIgnoringOtherApps:YES];
1434  [self commandDispatch:sender];
1435}
1436
1437- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
1438  NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
1439  Profile* profile = [self lastProfile];
1440
1441  BOOL profilesAdded = [profileMenuController_ insertItemsIntoMenu:dockMenu
1442                                                          atOffset:0
1443                                                          fromDock:YES];
1444  if (profilesAdded)
1445    [dockMenu addItem:[NSMenuItem separatorItem]];
1446
1447  NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
1448  base::scoped_nsobject<NSMenuItem> item(
1449      [[NSMenuItem alloc] initWithTitle:titleStr
1450                                 action:@selector(commandFromDock:)
1451                          keyEquivalent:@""]);
1452  [item setTarget:self];
1453  [item setTag:IDC_NEW_WINDOW];
1454  [item setEnabled:[self validateUserInterfaceItem:item]];
1455  [dockMenu addItem:item];
1456
1457  // |profile| can be NULL during unit tests.
1458  if (!profile || !profile->IsSupervised()) {
1459    titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
1460    item.reset(
1461        [[NSMenuItem alloc] initWithTitle:titleStr
1462                                   action:@selector(commandFromDock:)
1463                            keyEquivalent:@""]);
1464    [item setTarget:self];
1465    [item setTag:IDC_NEW_INCOGNITO_WINDOW];
1466    [item setEnabled:[self validateUserInterfaceItem:item]];
1467    [dockMenu addItem:item];
1468  }
1469
1470  // TODO(rickcam): Mock out BackgroundApplicationListModel, then add unit
1471  // tests which use the mock in place of the profile-initialized model.
1472
1473  // Avoid breaking unit tests which have no profile.
1474  if (profile) {
1475    BackgroundApplicationListModel applications(profile);
1476    if (applications.size()) {
1477      int position = 0;
1478      NSString* menuStr =
1479          l10n_util::GetNSStringWithFixup(IDS_BACKGROUND_APPS_MAC);
1480      base::scoped_nsobject<NSMenu> appMenu(
1481          [[NSMenu alloc] initWithTitle:menuStr]);
1482      for (extensions::ExtensionList::const_iterator cursor =
1483               applications.begin();
1484           cursor != applications.end();
1485           ++cursor, ++position) {
1486        DCHECK_EQ(applications.GetPosition(cursor->get()), position);
1487        NSString* itemStr =
1488            base::SysUTF16ToNSString(base::UTF8ToUTF16((*cursor)->name()));
1489        base::scoped_nsobject<NSMenuItem> appItem(
1490            [[NSMenuItem alloc] initWithTitle:itemStr
1491                                       action:@selector(executeApplication:)
1492                                keyEquivalent:@""]);
1493        [appItem setTarget:self];
1494        [appItem setTag:position];
1495        [appMenu addItem:appItem];
1496      }
1497    }
1498  }
1499
1500  return dockMenu;
1501}
1502
1503- (const std::vector<GURL>&)startupUrls {
1504  return startupUrls_;
1505}
1506
1507- (BookmarkMenuBridge*)bookmarkMenuBridge {
1508  return bookmarkMenuBridge_.get();
1509}
1510
1511- (void)addObserverForWorkAreaChange:(ui::WorkAreaWatcherObserver*)observer {
1512  workAreaChangeObservers_.AddObserver(observer);
1513}
1514
1515- (void)removeObserverForWorkAreaChange:(ui::WorkAreaWatcherObserver*)observer {
1516  workAreaChangeObservers_.RemoveObserver(observer);
1517}
1518
1519- (void)initAppShimMenuController {
1520  if (!appShimMenuController_)
1521    appShimMenuController_.reset([[AppShimMenuController alloc] init]);
1522}
1523
1524- (void)applicationDidChangeScreenParameters:(NSNotification*)notification {
1525  // During this callback the working area is not always already updated. Defer.
1526  [self performSelector:@selector(delayedScreenParametersUpdate)
1527             withObject:nil
1528             afterDelay:0];
1529}
1530
1531- (void)delayedScreenParametersUpdate {
1532  FOR_EACH_OBSERVER(ui::WorkAreaWatcherObserver, workAreaChangeObservers_,
1533      WorkAreaChanged());
1534}
1535
1536@end  // @implementation AppController
1537
1538//---------------------------------------------------------------------------
1539
1540namespace app_controller_mac {
1541
1542bool IsOpeningNewWindow() {
1543  return g_is_opening_new_window;
1544}
1545
1546}  // namespace app_controller_mac
1547