Fix build break
[chromium-blink-merge.git] / chrome / browser / app_controller_mac.mm
blob9c4d57930e48dcd37f955a21dcbb8cb495aed9c5
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.
5 #import "chrome/browser/app_controller_mac.h"
7 #include "base/auto_reset.h"
8 #include "base/bind.h"
9 #include "base/command_line.h"
10 #include "base/files/file_path.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/mac_util.h"
13 #include "base/message_loop.h"
14 #include "base/prefs/pref_service.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/utf_string_conversions.h"
18 #include "chrome/app/chrome_command_ids.h"
19 #include "chrome/browser/background/background_application_list_model.h"
20 #include "chrome/browser/background/background_mode_manager.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/browser_shutdown.h"
23 #include "chrome/browser/command_updater.h"
24 #include "chrome/browser/download/download_service.h"
25 #include "chrome/browser/download/download_service_factory.h"
26 #include "chrome/browser/extensions/extension_service.h"
27 #include "chrome/browser/extensions/extension_system.h"
28 #include "chrome/browser/first_run/first_run.h"
29 #include "chrome/browser/lifetime/application_lifetime.h"
30 #include "chrome/browser/printing/print_dialog_cloud.h"
31 #include "chrome/browser/profiles/profile_manager.h"
32 #include "chrome/browser/service/service_process_control.h"
33 #include "chrome/browser/sessions/session_restore.h"
34 #include "chrome/browser/sessions/session_service.h"
35 #include "chrome/browser/sessions/session_service_factory.h"
36 #include "chrome/browser/sessions/tab_restore_service.h"
37 #include "chrome/browser/sessions/tab_restore_service_factory.h"
38 #include "chrome/browser/signin/signin_manager.h"
39 #include "chrome/browser/signin/signin_manager_factory.h"
40 #include "chrome/browser/sync/profile_sync_service.h"
41 #include "chrome/browser/sync/sync_ui_util.h"
42 #include "chrome/browser/ui/browser.h"
43 #include "chrome/browser/ui/browser_command_controller.h"
44 #include "chrome/browser/ui/browser_commands.h"
45 #include "chrome/browser/ui/browser_finder.h"
46 #include "chrome/browser/ui/browser_iterator.h"
47 #include "chrome/browser/ui/browser_mac.h"
48 #include "chrome/browser/ui/browser_window.h"
49 #include "chrome/browser/ui/chrome_pages.h"
50 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
51 #import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
52 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
53 #import "chrome/browser/ui/cocoa/confirm_quit.h"
54 #import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
55 #import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h"
56 #import "chrome/browser/ui/cocoa/history_menu_bridge.h"
57 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
58 #import "chrome/browser/ui/cocoa/profile_menu_controller.h"
59 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
60 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
61 #include "chrome/browser/ui/cocoa/task_manager_mac.h"
62 #include "chrome/browser/ui/extensions/application_launch.h"
63 #include "chrome/browser/ui/host_desktop.h"
64 #include "chrome/browser/ui/startup/startup_browser_creator.h"
65 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
66 #include "chrome/common/chrome_notification_types.h"
67 #include "chrome/common/chrome_paths_internal.h"
68 #include "chrome/common/chrome_switches.h"
69 #include "chrome/common/cloud_print/cloud_print_class_mac.h"
70 #include "chrome/common/extensions/extension_constants.h"
71 #include "chrome/common/mac/app_mode_common.h"
72 #include "chrome/common/pref_names.h"
73 #include "chrome/common/service_messages.h"
74 #include "chrome/common/url_constants.h"
75 #include "content/public/browser/browser_thread.h"
76 #include "content/public/browser/download_manager.h"
77 #include "content/public/browser/notification_service.h"
78 #include "content/public/browser/notification_types.h"
79 #include "content/public/browser/plugin_service.h"
80 #include "content/public/browser/user_metrics.h"
81 #include "grit/chromium_strings.h"
82 #include "grit/generated_resources.h"
83 #include "net/base/net_util.h"
84 #include "ui/base/l10n/l10n_util.h"
85 #include "ui/base/l10n/l10n_util_mac.h"
87 using content::BrowserContext;
88 using content::BrowserThread;
89 using content::DownloadManager;
90 using content::UserMetricsAction;
92 namespace {
94 // Declare notification names from the 10.7 SDK.
95 #if !defined(MAC_OS_X_VERSION_10_7) || \
96     MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
97 NSString* NSPopoverDidShowNotification = @"NSPopoverDidShowNotification";
98 NSString* NSPopoverDidCloseNotification = @"NSPopoverDidCloseNotification";
99 #endif
101 // True while AppController is calling chrome::NewEmptyWindow(). We need a
102 // global flag here, analogue to StartupBrowserCreator::InProcessStartup()
103 // because otherwise the SessionService will try to restore sessions when we
104 // make a new window while there are no other active windows.
105 bool g_is_opening_new_window = false;
107 // Activates a browser window having the given profile (the last one active) if
108 // possible and returns a pointer to the activate |Browser| or NULL if this was
109 // not possible. If the last active browser is minimized (in particular, if
110 // there are only minimized windows), it will unminimize it.
111 Browser* ActivateBrowser(Profile* profile) {
112   Browser* browser = chrome::FindLastActiveWithProfile(profile,
113       chrome::HOST_DESKTOP_TYPE_NATIVE);
114   if (browser)
115     browser->window()->Activate();
116   return browser;
119 // Creates an empty browser window with the given profile and returns a pointer
120 // to the new |Browser|.
121 Browser* CreateBrowser(Profile* profile) {
122   {
123     base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
124     chrome::NewEmptyWindow(profile, chrome::HOST_DESKTOP_TYPE_NATIVE);
125   }
127   Browser* browser = chrome::GetLastActiveBrowser();
128   CHECK(browser);
129   return browser;
132 // Activates a browser window having the given profile (the last one active) if
133 // possible or creates an empty one if necessary. Returns a pointer to the
134 // activated/new |Browser|.
135 Browser* ActivateOrCreateBrowser(Profile* profile) {
136   if (Browser* browser = ActivateBrowser(profile))
137     return browser;
138   return CreateBrowser(profile);
141 CFStringRef BaseBundleID_CFString() {
142   NSString* base_bundle_id =
143       [NSString stringWithUTF8String:base::mac::BaseBundleID()];
144   return base::mac::NSToCFCast(base_bundle_id);
147 // This callback synchronizes preferences (under "org.chromium.Chromium" or
148 // "com.google.Chrome"), in particular, writes them out to disk.
149 void PrefsSyncCallback() {
150   if (!CFPreferencesAppSynchronize(BaseBundleID_CFString()))
151     LOG(WARNING) << "Error recording application bundle path.";
154 // Record the location of the application bundle (containing the main framework)
155 // from which Chromium was loaded. This is used by app mode shims to find
156 // Chromium.
157 void RecordLastRunAppBundlePath() {
158   // Going up three levels from |chrome::GetVersionedDirectory()| gives the
159   // real, user-visible app bundle directory. (The alternatives give either the
160   // framework's path or the initial app's path, which may be an app mode shim
161   // or a unit test.)
162   base::FilePath appBundlePath =
163       chrome::GetVersionedDirectory().DirName().DirName().DirName();
164   CFPreferencesSetAppValue(
165       base::mac::NSToCFCast(app_mode::kLastRunAppBundlePathPrefsKey),
166       base::SysUTF8ToCFStringRef(appBundlePath.value()),
167       BaseBundleID_CFString());
169   // Sync after a delay avoid I/O contention on startup; 1500 ms is plenty.
170   BrowserThread::PostDelayedTask(
171       BrowserThread::FILE, FROM_HERE,
172       base::Bind(&PrefsSyncCallback),
173       base::TimeDelta::FromMilliseconds(1500));
176 }  // anonymous namespace
178 @interface AppController (Private)
179 - (void)initMenuState;
180 - (void)initProfileMenu;
181 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item;
182 - (void)registerServicesMenuTypesTo:(NSApplication*)app;
183 - (void)openUrls:(const std::vector<GURL>&)urls;
184 - (void)getUrl:(NSAppleEventDescriptor*)event
185      withReply:(NSAppleEventDescriptor*)reply;
186 - (void)submitCloudPrintJob:(NSAppleEventDescriptor*)event;
187 - (void)windowLayeringDidChange:(NSNotification*)inNotification;
188 - (void)windowChangedToProfile:(Profile*)profile;
189 - (void)checkForAnyKeyWindows;
190 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
191 - (BOOL)shouldQuitWithInProgressDownloads;
192 - (void)executeApplication:(id)sender;
193 @end
195 @implementation AppController
197 @synthesize startupComplete = startupComplete_;
199 // This method is called very early in application startup (ie, before
200 // the profile is loaded or any preferences have been registered). Defer any
201 // user-data initialization until -applicationDidFinishLaunching:.
202 - (void)awakeFromNib {
203   // We need to register the handlers early to catch events fired on launch.
204   NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
205   [em setEventHandler:self
206           andSelector:@selector(getUrl:withReply:)
207         forEventClass:kInternetEventClass
208            andEventID:kAEGetURL];
209   [em setEventHandler:self
210           andSelector:@selector(submitCloudPrintJob:)
211         forEventClass:cloud_print::kAECloudPrintClass
212            andEventID:cloud_print::kAECloudPrintClass];
213   [em setEventHandler:self
214           andSelector:@selector(getUrl:withReply:)
215         forEventClass:'WWW!'    // A particularly ancient AppleEvent that dates
216            andEventID:'OURL'];  // back to the Spyglass days.
218   // Register for various window layering changes. We use these to update
219   // various UI elements (command-key equivalents, etc) when the frontmost
220   // window changes.
221   NSNotificationCenter* notificationCenter =
222       [NSNotificationCenter defaultCenter];
223   [notificationCenter
224       addObserver:self
225          selector:@selector(windowLayeringDidChange:)
226              name:NSWindowDidBecomeKeyNotification
227            object:nil];
228   [notificationCenter
229       addObserver:self
230          selector:@selector(windowLayeringDidChange:)
231              name:NSWindowDidResignKeyNotification
232            object:nil];
233   [notificationCenter
234       addObserver:self
235          selector:@selector(windowLayeringDidChange:)
236              name:NSWindowDidBecomeMainNotification
237            object:nil];
238   [notificationCenter
239       addObserver:self
240          selector:@selector(windowLayeringDidChange:)
241              name:NSWindowDidResignMainNotification
242            object:nil];
244   if (base::mac::IsOSLionOrLater()) {
245     [notificationCenter
246         addObserver:self
247            selector:@selector(popoverDidShow:)
248                name:NSPopoverDidShowNotification
249              object:nil];
250     [notificationCenter
251         addObserver:self
252            selector:@selector(popoverDidClose:)
253                name:NSPopoverDidCloseNotification
254              object:nil];
255   }
257   // Set up the command updater for when there are no windows open
258   [self initMenuState];
260   // Initialize the Profile menu.
261   [self initProfileMenu];
264 - (void)unregisterEventHandlers {
265   NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager];
266   [em removeEventHandlerForEventClass:kInternetEventClass
267                            andEventID:kAEGetURL];
268   [em removeEventHandlerForEventClass:cloud_print::kAECloudPrintClass
269                            andEventID:cloud_print::kAECloudPrintClass];
270   [em removeEventHandlerForEventClass:'WWW!'
271                            andEventID:'OURL'];
272   [[NSNotificationCenter defaultCenter] removeObserver:self];
275 // (NSApplicationDelegate protocol) This is the Apple-approved place to override
276 // the default handlers.
277 - (void)applicationWillFinishLaunching:(NSNotification*)notification {
278   // Nothing here right now.
281 - (BOOL)tryToTerminateApplication:(NSApplication*)app {
282   // Check for in-process downloads, and prompt the user if they really want
283   // to quit (and thus cancel downloads). Only check if we're not already
284   // shutting down, else the user might be prompted multiple times if the
285   // download isn't stopped before terminate is called again.
286   if (!browser_shutdown::IsTryingToQuit() &&
287       ![self shouldQuitWithInProgressDownloads])
288     return NO;
290   // TODO(viettrungluu): Remove Apple Event handlers here? (It's safe to leave
291   // them in, but I'm not sure about UX; we'd also want to disable other things
292   // though.) http://crbug.com/40861
294   // Check if the user really wants to quit by employing the confirm-to-quit
295   // mechanism.
296   if (!browser_shutdown::IsTryingToQuit() &&
297       [self applicationShouldTerminate:app] != NSTerminateNow)
298     return NO;
300   size_t num_browsers = chrome::GetTotalBrowserCount();
302   // Initiate a shutdown (via chrome::CloseAllBrowsers()) if we aren't
303   // already shutting down.
304   if (!browser_shutdown::IsTryingToQuit()) {
305     content::NotificationService::current()->Notify(
306         chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
307         content::NotificationService::AllSources(),
308         content::NotificationService::NoDetails());
309     chrome::CloseAllBrowsers();
310   }
312   return num_browsers == 0 ? YES : NO;
315 - (void)stopTryingToTerminateApplication:(NSApplication*)app {
316   if (browser_shutdown::IsTryingToQuit()) {
317     // Reset the "trying to quit" state, so that closing all browser windows
318     // will no longer lead to termination.
319     browser_shutdown::SetTryingToQuit(false);
321     // TODO(viettrungluu): Were we to remove Apple Event handlers above, we
322     // would have to reinstall them here. http://crbug.com/40861
323   }
326 - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app {
327   // Check if the preference is turned on.
328   const PrefService* prefs = g_browser_process->local_state();
329   if (!prefs->GetBoolean(prefs::kConfirmToQuitEnabled)) {
330     confirm_quit::RecordHistogram(confirm_quit::kNoConfirm);
331     return NSTerminateNow;
332   }
334   // If the application is going to terminate as the result of a Cmd+Q
335   // invocation, use the special sauce to prevent accidental quitting.
336   // http://dev.chromium.org/developers/design-documents/confirm-to-quit-experiment
338   // This logic is only for keyboard-initiated quits.
339   if (![ConfirmQuitPanelController eventTriggersFeature:[app currentEvent]])
340     return NSTerminateNow;
342   return [[ConfirmQuitPanelController sharedController]
343       runModalLoopForApplication:app];
346 // Called when the app is shutting down. Clean-up as appropriate.
347 - (void)applicationWillTerminate:(NSNotification*)aNotification {
348   // There better be no browser windows left at this point.
349   CHECK_EQ(0u, chrome::GetTotalBrowserCount());
351   // Tell BrowserList not to keep the browser process alive. Once all the
352   // browsers get dealloc'd, it will stop the RunLoop and fall back into main().
353   chrome::EndKeepAlive();
355   // Reset all pref watching, as this object outlives the prefs system.
356   profilePrefRegistrar_.reset();
357   localPrefRegistrar_.RemoveAll();
359   [self unregisterEventHandlers];
362 - (void)didEndMainMessageLoop {
363   DCHECK_EQ(0u, chrome::GetBrowserCount([self lastProfile],
364                                         chrome::HOST_DESKTOP_TYPE_NATIVE));
365   if (!chrome::GetBrowserCount([self lastProfile],
366                                chrome::HOST_DESKTOP_TYPE_NATIVE)) {
367     // As we're shutting down, we need to nuke the TabRestoreService, which
368     // will start the shutdown of the NavigationControllers and allow for
369     // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
370     // and may end up crashing when some thread tries to use the IO thread (or
371     // another thread) that is no longer valid.
372     TabRestoreServiceFactory::ResetForProfile([self lastProfile]);
373   }
376 // If the window has a tab controller, make "close window" be cmd-shift-w,
377 // otherwise leave it as the normal cmd-w. Capitalization of the key equivalent
378 // affects whether the shift modifier is used.
379 - (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)enableCloseTabShortcut {
380   [closeWindowMenuItem_ setKeyEquivalent:(enableCloseTabShortcut ? @"W" :
381                                                                    @"w")];
382   [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
385 // If the window has a tab controller, make "close tab" take over cmd-w,
386 // otherwise it shouldn't have any key-equivalent because it should be disabled.
387 - (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)enableCloseTabShortcut {
388   if (enableCloseTabShortcut) {
389     [closeTabMenuItem_ setKeyEquivalent:@"w"];
390     [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask];
391   } else {
392     [closeTabMenuItem_ setKeyEquivalent:@""];
393     [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
394   }
397 // Explicitly remove any command-key equivalents from the close tab/window
398 // menus so that nothing can go haywire if we get a user action during pending
399 // updates.
400 - (void)clearCloseMenuItemKeyEquivalents {
401   [closeTabMenuItem_ setKeyEquivalent:@""];
402   [closeTabMenuItem_ setKeyEquivalentModifierMask:0];
403   [closeWindowMenuItem_ setKeyEquivalent:@""];
404   [closeWindowMenuItem_ setKeyEquivalentModifierMask:0];
407 // See if the focused window window has tabs, and adjust the key equivalents for
408 // Close Tab/Close Window accordingly.
409 - (void)fixCloseMenuItemKeyEquivalents {
410   fileMenuUpdatePending_ = NO;
412   NSWindow* window = [NSApp keyWindow];
413   NSWindow* mainWindow = [NSApp mainWindow];
414   if (!window || ([window parentWindow] == mainWindow)) {
415     // If the key window is a child of the main window (e.g. a bubble), the main
416     // window should be the one that handles the close menu item action.
417     // Also, there might be a small amount of time where there is no key window;
418     // in that case as well, just use our main browser window if there is one.
419     // You might think that we should just always use the main window, but the
420     // "About Chrome" window serves as a counterexample.
421     window = mainWindow;
422   }
424   BOOL hasTabs =
425       [[window windowController] isKindOfClass:[TabWindowController class]];
426   BOOL enableCloseTabShortcut = hasTabs && !hasPopover_;
427   [self adjustCloseWindowMenuItemKeyEquivalent:enableCloseTabShortcut];
428   [self adjustCloseTabMenuItemKeyEquivalent:enableCloseTabShortcut];
431 // Fix up the "close tab/close window" command-key equivalents. We do this
432 // after a delay to ensure that window layer state has been set by the time
433 // we do the enabling. This should only be called on the main thread, code that
434 // calls this (even as a side-effect) from other threads needs to be fixed.
435 - (void)delayedFixCloseMenuItemKeyEquivalents {
436   DCHECK([NSThread isMainThread]);
437   if (!fileMenuUpdatePending_) {
438     // The OS prefers keypresses to timers, so it's possible that a cmd-w
439     // can sneak in before this timer fires. In order to prevent that from
440     // having any bad consequences, just clear the keys combos altogether. They
441     // will be reset when the timer eventually fires.
442     if ([NSThread isMainThread]) {
443       fileMenuUpdatePending_ = YES;
444       [self clearCloseMenuItemKeyEquivalents];
445       [self performSelector:@selector(fixCloseMenuItemKeyEquivalents)
446                  withObject:nil
447                  afterDelay:0];
448     } else {
449       // This shouldn't be happening, but if it does, force it to the main
450       // thread to avoid dropping the update. Don't mess with
451       // |fileMenuUpdatePending_| as it's not expected to be threadsafe and
452       // there could be a race between the selector finishing and setting the
453       // flag.
454       [self
455           performSelectorOnMainThread:@selector(fixCloseMenuItemKeyEquivalents)
456                            withObject:nil
457                         waitUntilDone:NO];
458     }
459   }
462 // Called when we get a notification about the window layering changing to
463 // update the UI based on the new main window.
464 - (void)windowLayeringDidChange:(NSNotification*)notify {
465   [self delayedFixCloseMenuItemKeyEquivalents];
467   if ([notify name] == NSWindowDidResignKeyNotification) {
468     // If a window is closed, this notification is fired but |[NSApp keyWindow]|
469     // returns nil regardless of whether any suitable candidates for the key
470     // window remain. It seems that the new key window for the app is not set
471     // until after this notification is fired, so a check is performed after the
472     // run loop is allowed to spin.
473     [self performSelector:@selector(checkForAnyKeyWindows)
474                withObject:nil
475                afterDelay:0.0];
476   }
478   // If the window changed to a new BrowserWindowController, update the profile.
479   id windowController = [[notify object] windowController];
480   if ([notify name] == NSWindowDidBecomeMainNotification &&
481       [windowController isKindOfClass:[BrowserWindowController class]]) {
482     // If the profile is incognito, use the original profile.
483     Profile* newProfile = [windowController profile]->GetOriginalProfile();
484     [self windowChangedToProfile:newProfile];
485   } else if (chrome::GetTotalBrowserCount() == 0) {
486     [self windowChangedToProfile:
487         g_browser_process->profile_manager()->GetLastUsedProfile()];
488   }
491 // Called on Lion and later when a popover (e.g. dictionary) is shown.
492 - (void)popoverDidShow:(NSNotification*)notify {
493   hasPopover_ = YES;
494   [self fixCloseMenuItemKeyEquivalents];
497 // Called on Lion and later when a popover (e.g. dictionary) is closed.
498 - (void)popoverDidClose:(NSNotification*)notify {
499   hasPopover_ = NO;
500   [self fixCloseMenuItemKeyEquivalents];
503 // Called when the user has changed browser windows, meaning the backing profile
504 // may have changed. This can cause a rebuild of the user-data menus. This is a
505 // no-op if the new profile is the same as the current one. This will always be
506 // the original profile and never incognito.
507 - (void)windowChangedToProfile:(Profile*)profile {
508   if (lastProfile_ == profile)
509     return;
511   // Before tearing down the menu controller bridges, return the Cocoa menus to
512   // their initial state.
513   if (bookmarkMenuBridge_.get())
514     bookmarkMenuBridge_->ResetMenu();
515   if (historyMenuBridge_.get())
516     historyMenuBridge_->ResetMenu();
518   // Rebuild the menus with the new profile.
519   lastProfile_ = profile;
521   bookmarkMenuBridge_.reset(new BookmarkMenuBridge(lastProfile_,
522       [[[NSApp mainMenu] itemWithTag:IDC_BOOKMARKS_MENU] submenu]));
523   // No need to |BuildMenu| here.  It is done lazily upon menu access.
525   historyMenuBridge_.reset(new HistoryMenuBridge(lastProfile_));
526   historyMenuBridge_->BuildMenu();
528   chrome::BrowserCommandController::
529       UpdateSharedCommandsForIncognitoAvailability(
530           menuState_.get(), lastProfile_);
531   profilePrefRegistrar_.reset(new PrefChangeRegistrar());
532   profilePrefRegistrar_->Init(lastProfile_->GetPrefs());
533   profilePrefRegistrar_->Add(
534       prefs::kIncognitoModeAvailability,
535       base::Bind(&chrome::BrowserCommandController::
536                      UpdateSharedCommandsForIncognitoAvailability,
537                  menuState_.get(),
538                  lastProfile_));
541 - (void)checkForAnyKeyWindows {
542   if ([NSApp keyWindow])
543     return;
545   content::NotificationService::current()->Notify(
546       chrome::NOTIFICATION_NO_KEY_WINDOW,
547       content::NotificationService::AllSources(),
548       content::NotificationService::NoDetails());
551 // If the auto-update interval is not set, make it 5 hours.
552 // This code is specific to Mac Chrome Dev Channel.
553 // Placed here for 2 reasons:
554 // 1) Same spot as other Pref stuff
555 // 2) Try and be friendly by keeping this after app launch
556 // TODO(jrg): remove once we go Beta.
557 - (void)setUpdateCheckInterval {
558 #if defined(GOOGLE_CHROME_BUILD)
559   CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent";
560   CFStringRef checkInterval = (CFStringRef)@"checkInterval";
561   CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
562   if (!plist) {
563     const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
564     NSNumber* value = [NSNumber numberWithFloat:fiveHoursInSeconds];
565     CFPreferencesSetAppValue(checkInterval, value, app);
566     CFPreferencesAppSynchronize(app);
567   }
568 #endif
571 // This is called after profiles have been loaded and preferences registered.
572 // It is safe to access the default profile here.
573 - (void)applicationDidFinishLaunching:(NSNotification*)notify {
574   // Notify BrowserList to keep the application running so it doesn't go away
575   // when all the browser windows get closed.
576   chrome::StartKeepAlive();
578   [self setUpdateCheckInterval];
580   // Build up the encoding menu, the order of the items differs based on the
581   // current locale (see http://crbug.com/7647 for details).
582   // We need a valid g_browser_process to get the profile which is why we can't
583   // call this from awakeFromNib.
584   NSMenu* viewMenu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
585   NSMenuItem* encodingMenuItem = [viewMenu itemWithTag:IDC_ENCODING_MENU];
586   NSMenu* encodingMenu = [encodingMenuItem submenu];
587   EncodingMenuControllerDelegate::BuildEncodingMenu([self lastProfile],
588                                                     encodingMenu);
590   // Since Chrome is localized to more languages than the OS, tell Cocoa which
591   // menu is the Help so it can add the search item to it.
592   [NSApp setHelpMenu:helpMenu_];
594   // Record the path to the (browser) app bundle; this is used by the app mode
595   // shim.
596   RecordLastRunAppBundlePath();
598   // Makes "Services" menu items available.
599   [self registerServicesMenuTypesTo:[notify object]];
601   startupComplete_ = YES;
603   // TODO(viettrungluu): This is very temporary, since this should be done "in"
604   // |BrowserMain()|, i.e., this list of startup URLs should be appended to the
605   // (probably-empty) list of URLs from the command line.
606   if (startupUrls_.size()) {
607     [self openUrls:startupUrls_];
608     [self clearStartupUrls];
609   }
611   const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
612   if (!parsed_command_line.HasSwitch(switches::kEnableExposeForTabs)) {
613     [tabposeMenuItem_ setHidden:YES];
614   }
616   PrefService* localState = g_browser_process->local_state();
617   if (localState) {
618     localPrefRegistrar_.Init(localState);
619     localPrefRegistrar_.Add(
620         prefs::kAllowFileSelectionDialogs,
621         base::Bind(&chrome::BrowserCommandController::UpdateOpenFileState,
622                    menuState_.get()));
623   }
626 // This is called after profiles have been loaded and preferences registered.
627 // It is safe to access the default profile here.
628 - (void)applicationDidBecomeActive:(NSNotification*)notify {
629   content::PluginService::GetInstance()->AppActivated();
632 // Helper function for populating and displaying the in progress downloads at
633 // exit alert panel.
634 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
635   NSString* titleText = nil;
636   NSString* explanationText = nil;
637   NSString* waitTitle = nil;
638   NSString* exitTitle = nil;
640   // Set the dialog text based on whether or not there are multiple downloads.
641   if (downloadCount == 1) {
642     // Dialog text: warning and explanation.
643     titleText = l10n_util::GetNSString(
644         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_TITLE);
645     explanationText = l10n_util::GetNSString(
646         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION);
647   } else {
648     // Dialog text: warning and explanation.
649     titleText = l10n_util::GetNSStringF(
650         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_TITLE,
651         base::IntToString16(downloadCount));
652     explanationText = l10n_util::GetNSString(
653         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION);
654   }
655   // Cancel download and exit button text.
656   exitTitle = l10n_util::GetNSString(
657       IDS_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL);
659   // Wait for download button text.
660   waitTitle = l10n_util::GetNSString(
661       IDS_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
663   // 'waitButton' is the default choice.
664   int choice = NSRunAlertPanel(titleText, explanationText,
665                                waitTitle, exitTitle, nil);
666   return choice == NSAlertDefaultReturn ? YES : NO;
669 // Check all profiles for in progress downloads, and if we find any, prompt the
670 // user to see if we should continue to exit (and thus cancel the downloads), or
671 // if we should wait.
672 - (BOOL)shouldQuitWithInProgressDownloads {
673   ProfileManager* profile_manager = g_browser_process->profile_manager();
674   if (!profile_manager)
675     return YES;
677   std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
678   for (size_t i = 0; i < profiles.size(); ++i) {
679     DownloadService* download_service =
680       DownloadServiceFactory::GetForProfile(profiles[i]);
681     DownloadManager* download_manager =
682         (download_service->HasCreatedDownloadManager() ?
683          BrowserContext::GetDownloadManager(profiles[i]) : NULL);
684     if (download_manager && download_manager->InProgressCount() > 0) {
685       int downloadCount = download_manager->InProgressCount();
686       if ([self userWillWaitForInProgressDownloads:downloadCount]) {
687         // Create a new browser window (if necessary) and navigate to the
688         // downloads page if the user chooses to wait.
689         Browser* browser = chrome::FindBrowserWithProfile(
690             profiles[i], chrome::HOST_DESKTOP_TYPE_NATIVE);
691         if (!browser) {
692           browser = new Browser(Browser::CreateParams(
693               profiles[i], chrome::HOST_DESKTOP_TYPE_NATIVE));
694           browser->window()->Show();
695         }
696         DCHECK(browser);
697         chrome::ShowDownloads(browser);
698         return NO;
699       }
701       // User wants to exit.
702       return YES;
703     }
704   }
706   // No profiles or active downloads found, okay to exit.
707   return YES;
710 // Called to determine if we should enable the "restore tab" menu item.
711 // Checks with the TabRestoreService to see if there's anything there to
712 // restore and returns YES if so.
713 - (BOOL)canRestoreTab {
714   TabRestoreService* service =
715       TabRestoreServiceFactory::GetForProfile([self lastProfile]);
716   return service && !service->entries().empty();
719 // Returns true if there is a modal window (either window- or application-
720 // modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
721 // sheets) will not count as blocking the browser. But things like open/save
722 // dialogs that are window modal will block the browser.
723 - (BOOL)keyWindowIsModal {
724   if ([NSApp modalWindow])
725     return YES;
727   Browser* browser = chrome::GetLastActiveBrowser();
728   return browser &&
729          [[browser->window()->GetNativeWindow() attachedSheet]
730              isKindOfClass:[NSWindow class]];
733 // Called to validate menu items when there are no key windows. All the
734 // items we care about have been set with the |commandDispatch:| action and
735 // a target of FirstResponder in IB. If it's not one of those, let it
736 // continue up the responder chain to be handled elsewhere. We pull out the
737 // tag as the cross-platform constant to differentiate and dispatch the
738 // various commands.
739 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
740   SEL action = [item action];
741   BOOL enable = NO;
742   if (action == @selector(commandDispatch:) ||
743       action == @selector(commandFromDock:)) {
744     NSInteger tag = [item tag];
745     if (menuState_ &&  // NULL in tests.
746         menuState_->SupportsCommand(tag)) {
747       switch (tag) {
748         // The File Menu commands are not automatically disabled by Cocoa when a
749         // dialog sheet obscures the browser window, so we disable several of
750         // them here.  We don't need to include IDC_CLOSE_WINDOW, because
751         // app_controller is only activated when there are no key windows (see
752         // function comment).
753         case IDC_RESTORE_TAB:
754           enable = ![self keyWindowIsModal] && [self canRestoreTab];
755           break;
756         // Browser-level items that open in new tabs should not open if there's
757         // a window- or app-modal dialog.
758         case IDC_OPEN_FILE:
759         case IDC_NEW_TAB:
760         case IDC_SHOW_HISTORY:
761         case IDC_SHOW_BOOKMARK_MANAGER:
762           enable = ![self keyWindowIsModal];
763           break;
764         // Browser-level items that open in new windows.
765         case IDC_TASK_MANAGER:
766           // Allow the user to open a new window if there's a window-modal
767           // dialog.
768           enable = ![self keyWindowIsModal];
769           break;
770         case IDC_SHOW_SYNC_SETUP: {
771           Profile* lastProfile = [self lastProfile];
772           // The profile may be NULL during shutdown -- see
773           // http://code.google.com/p/chromium/issues/detail?id=43048 .
774           //
775           // TODO(akalin,viettrungluu): Figure out whether this method
776           // can be prevented from being called if lastProfile is
777           // NULL.
778           if (!lastProfile) {
779             LOG(WARNING)
780                 << "NULL lastProfile detected -- not doing anything";
781             break;
782           }
783           SigninManager* signin = SigninManagerFactory::GetForProfile(
784               lastProfile->GetOriginalProfile());
785           enable = signin->IsSigninAllowed() &&
786               ![self keyWindowIsModal];
787           [BrowserWindowController updateSigninItem:item
788                                          shouldShow:enable
789                                      currentProfile:lastProfile];
790           break;
791         }
792         case IDC_FEEDBACK:
793           enable = NO;
794           break;
795         default:
796           enable = menuState_->IsCommandEnabled(tag) ?
797                    ![self keyWindowIsModal] : NO;
798       }
799     }
800   } else if (action == @selector(terminate:)) {
801     enable = YES;
802   } else if (action == @selector(showPreferences:)) {
803     enable = YES;
804   } else if (action == @selector(orderFrontStandardAboutPanel:)) {
805     enable = YES;
806   } else if (action == @selector(commandFromDock:)) {
807     enable = YES;
808   } else if (action == @selector(toggleConfirmToQuit:)) {
809     [self updateConfirmToQuitPrefMenuItem:static_cast<NSMenuItem*>(item)];
810     enable = YES;
811   } else if (action == @selector(executeApplication:)) {
812     enable = YES;
813   }
814   return enable;
817 // Called when the user picks a menu item when there are no key windows, or when
818 // there is no foreground browser window. Calls through to the browser object to
819 // execute the command. This assumes that the command is supported and doesn't
820 // check, otherwise it should have been disabled in the UI in
821 // |-validateUserInterfaceItem:|.
822 - (void)commandDispatch:(id)sender {
823   Profile* lastProfile = [self lastProfile];
825   // Handle the case where we're dispatching a command from a sender that's in a
826   // browser window. This means that the command came from a background window
827   // and is getting here because the foreground window is not a browser window.
828   if ([sender respondsToSelector:@selector(window)]) {
829     id delegate = [[sender window] windowController];
830     if ([delegate isKindOfClass:[BrowserWindowController class]]) {
831       [delegate commandDispatch:sender];
832       return;
833     }
834   }
836   // Ignore commands during session restore's browser creation.  It uses a
837   // nested message loop and commands dispatched during this operation cause
838   // havoc.
839   if (SessionRestore::IsRestoring(lastProfile) &&
840       MessageLoop::current()->IsNested())
841     return;
843   NSInteger tag = [sender tag];
844   switch (tag) {
845     case IDC_NEW_TAB:
846       // Create a new tab in an existing browser window (which we activate) if
847       // possible.
848       if (Browser* browser = ActivateBrowser(lastProfile)) {
849         chrome::ExecuteCommand(browser, IDC_NEW_TAB);
850         break;
851       }
852       // Else fall through to create new window.
853     case IDC_NEW_WINDOW:
854       CreateBrowser(lastProfile);
855       break;
856     case IDC_FOCUS_LOCATION:
857       chrome::ExecuteCommand(ActivateOrCreateBrowser(lastProfile),
858                              IDC_FOCUS_LOCATION);
859       break;
860     case IDC_FOCUS_SEARCH:
861       chrome::ExecuteCommand(ActivateOrCreateBrowser(lastProfile),
862                              IDC_FOCUS_SEARCH);
863       break;
864     case IDC_NEW_INCOGNITO_WINDOW:
865       CreateBrowser(lastProfile->GetOffTheRecordProfile());
866       break;
867     case IDC_RESTORE_TAB:
868       // There is only the native desktop on Mac.
869       chrome::OpenWindowWithRestoredTabs(lastProfile,
870                                          chrome::HOST_DESKTOP_TYPE_NATIVE);
871       break;
872     case IDC_OPEN_FILE:
873       chrome::ExecuteCommand(CreateBrowser(lastProfile), IDC_OPEN_FILE);
874       break;
875     case IDC_CLEAR_BROWSING_DATA: {
876       // There may not be a browser open, so use the default profile.
877       if (Browser* browser = ActivateBrowser(lastProfile)) {
878         chrome::ShowClearBrowsingDataDialog(browser);
879       } else {
880         chrome::OpenClearBrowsingDataDialogWindow(lastProfile);
881       }
882       break;
883     }
884     case IDC_IMPORT_SETTINGS: {
885       if (Browser* browser = ActivateBrowser(lastProfile)) {
886         chrome::ShowImportDialog(browser);
887       } else {
888         chrome::OpenImportSettingsDialogWindow(lastProfile);
889       }
890       break;
891     }
892     case IDC_SHOW_BOOKMARK_MANAGER:
893       content::RecordAction(UserMetricsAction("ShowBookmarkManager"));
894       if (Browser* browser = ActivateBrowser(lastProfile)) {
895         chrome::ShowBookmarkManager(browser);
896       } else {
897         // No browser window, so create one for the bookmark manager tab.
898         chrome::OpenBookmarkManagerWindow(lastProfile);
899       }
900       break;
901     case IDC_SHOW_HISTORY:
902       if (Browser* browser = ActivateBrowser(lastProfile))
903         chrome::ShowHistory(browser);
904       else
905         chrome::OpenHistoryWindow(lastProfile);
906       break;
907     case IDC_SHOW_DOWNLOADS:
908       if (Browser* browser = ActivateBrowser(lastProfile))
909         chrome::ShowDownloads(browser);
910       else
911         chrome::OpenDownloadsWindow(lastProfile);
912       break;
913     case IDC_MANAGE_EXTENSIONS:
914       if (Browser* browser = ActivateBrowser(lastProfile))
915         chrome::ShowExtensions(browser, std::string());
916       else
917         chrome::OpenExtensionsWindow(lastProfile);
918       break;
919     case IDC_HELP_PAGE_VIA_MENU:
920       if (Browser* browser = ActivateBrowser(lastProfile))
921         chrome::ShowHelp(browser, chrome::HELP_SOURCE_MENU);
922       else
923         chrome::OpenHelpWindow(lastProfile, chrome::HELP_SOURCE_MENU);
924       break;
925     case IDC_SHOW_SYNC_SETUP:
926       if (Browser* browser = ActivateBrowser(lastProfile))
927         chrome::ShowBrowserSignin(browser, SyncPromoUI::SOURCE_MENU);
928       else
929         chrome::OpenSyncSetupWindow(lastProfile, SyncPromoUI::SOURCE_MENU);
930       break;
931     case IDC_TASK_MANAGER:
932       content::RecordAction(UserMetricsAction("TaskManager"));
933       TaskManagerMac::Show(false);
934       break;
935     case IDC_OPTIONS:
936       [self showPreferences:sender];
937       break;
938   }
941 // Run a (background) application in a new tab.
942 - (void)executeApplication:(id)sender {
943   NSInteger tag = [sender tag];
944   Profile* profile = [self lastProfile];
945   DCHECK(profile);
946   BackgroundApplicationListModel applications(profile);
947   DCHECK(tag >= 0 &&
948          tag < static_cast<int>(applications.size()));
949   const extensions::Extension* extension = applications.GetExtension(tag);
950   BackgroundModeManager::LaunchBackgroundApplication(profile, extension);
953 // Same as |-commandDispatch:|, but executes commands using a disposition
954 // determined by the key flags. This will get called in the case where the
955 // frontmost window is not a browser window, and the user has command-clicked
956 // a button in a background browser window whose action is
957 // |-commandDispatchUsingKeyModifiers:|
958 - (void)commandDispatchUsingKeyModifiers:(id)sender {
959   DCHECK(sender);
960   if ([sender respondsToSelector:@selector(window)]) {
961     id delegate = [[sender window] windowController];
962     if ([delegate isKindOfClass:[BrowserWindowController class]]) {
963       [delegate commandDispatchUsingKeyModifiers:sender];
964     }
965   }
968 // NSApplication delegate method called when someone clicks on the
969 // dock icon and there are no open windows.  To match standard mac
970 // behavior, we should open a new window.
971 - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
972                     hasVisibleWindows:(BOOL)flag {
973   // If the browser is currently trying to quit, don't do anything and return NO
974   // to prevent AppKit from doing anything.
975   // TODO(rohitrao): Remove this code when http://crbug.com/40861 is resolved.
976   if (browser_shutdown::IsTryingToQuit())
977     return NO;
979   // Don't do anything if there are visible tabbed or popup windows.  This will
980   // cause AppKit to unminimize the most recently minimized window. If the
981   // visible windows are panels or notifications, we still need to open a new
982   // window.
983   if (flag) {
984     for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
985       Browser* browser = *iter;
986       if (browser->is_type_tabbed() || browser->is_type_popup())
987         return YES;
988     }
989   }
991   // If launched as a hidden login item (due to installation of a persistent app
992   // or by the user, for example in System Preferences->Accounts->Login Items),
993   // allow session to be restored first time the user clicks on a Dock icon.
994   // Normally, it'd just open a new empty page.
995   {
996     static BOOL doneOnce = NO;
997     if (!doneOnce) {
998       doneOnce = YES;
999       if (base::mac::WasLaunchedAsHiddenLoginItem()) {
1000         SessionService* sessionService =
1001             SessionServiceFactory::GetForProfile([self lastProfile]);
1002         if (sessionService &&
1003             sessionService->RestoreIfNecessary(std::vector<GURL>()))
1004           return NO;
1005       }
1006     }
1007   }
1009   // Platform apps don't use browser windows so don't do anything if there are
1010   // visible windows.
1011   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
1012   if (flag && command_line.HasSwitch(switches::kAppId))
1013     return YES;
1015   // Otherwise open a new window.
1016   {
1017     base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
1018     int return_code;
1019     StartupBrowserCreator browser_creator;
1020     browser_creator.LaunchBrowser(
1021         command_line, [self lastProfile], base::FilePath(),
1022         chrome::startup::IS_NOT_PROCESS_STARTUP,
1023         chrome::startup::IS_NOT_FIRST_RUN, &return_code);
1024   }
1026   // We've handled the reopen event, so return NO to tell AppKit not
1027   // to do anything.
1028   return NO;
1031 - (void)initMenuState {
1032   menuState_.reset(new CommandUpdater(NULL));
1033   menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
1034   menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
1035   menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
1036   menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
1037   menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
1038   menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
1039   menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
1040   menuState_->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true);
1041   menuState_->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true);
1042   menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
1043   menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
1044   menuState_->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true);
1045   menuState_->UpdateCommandEnabled(IDC_HELP_PAGE_VIA_MENU, true);
1046   menuState_->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true);
1047   menuState_->UpdateCommandEnabled(IDC_FEEDBACK, true);
1048   menuState_->UpdateCommandEnabled(IDC_SHOW_SYNC_SETUP, true);
1049   menuState_->UpdateCommandEnabled(IDC_TASK_MANAGER, true);
1052 // Conditionally adds the Profile menu to the main menu bar.
1053 - (void)initProfileMenu {
1054   NSMenu* mainMenu = [NSApp mainMenu];
1055   NSMenuItem* profileMenu = [mainMenu itemWithTag:IDC_PROFILE_MAIN_MENU];
1057   if (!ProfileManager::IsMultipleProfilesEnabled()) {
1058     [mainMenu removeItem:profileMenu];
1059     return;
1060   }
1062   // The controller will unhide the menu if necessary.
1063   [profileMenu setHidden:YES];
1065   profileMenuController_.reset(
1066       [[ProfileMenuController alloc] initWithMainMenuItem:profileMenu]);
1069 // The Confirm to Quit preference is atypical in that the preference lives in
1070 // the app menu right above the Quit menu item. This method will refresh the
1071 // display of that item depending on the preference state.
1072 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item {
1073   // Format the string so that the correct key equivalent is displayed.
1074   NSString* acceleratorString = [ConfirmQuitPanelController keyCommandString];
1075   NSString* title = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_OPTION,
1076       base::SysNSStringToUTF16(acceleratorString));
1077   [item setTitle:title];
1079   const PrefService* prefService = g_browser_process->local_state();
1080   bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1081   [item setState:enabled ? NSOnState : NSOffState];
1084 - (void)registerServicesMenuTypesTo:(NSApplication*)app {
1085   // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which
1086   // handles requests from services.
1087   NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
1088   [app registerServicesMenuSendTypes:types returnTypes:types];
1091 - (Profile*)lastProfile {
1092   // Return the profile of the last-used BrowserWindowController, if available.
1093   if (lastProfile_)
1094     return lastProfile_;
1096   // On first launch, no profile will be stored, so use last from Local State.
1097   if (g_browser_process->profile_manager())
1098     return g_browser_process->profile_manager()->GetLastUsedProfile();
1100   return NULL;
1103 // Various methods to open URLs that we get in a native fashion. We use
1104 // StartupBrowserCreator here because on the other platforms, URLs to open come
1105 // through the ProcessSingleton, and it calls StartupBrowserCreator. It's best
1106 // to bottleneck the openings through that for uniform handling.
1108 - (void)openUrls:(const std::vector<GURL>&)urls {
1109   // If the browser hasn't started yet, just queue up the URLs.
1110   if (!startupComplete_) {
1111     startupUrls_.insert(startupUrls_.end(), urls.begin(), urls.end());
1112     return;
1113   }
1115   Browser* browser = chrome::GetLastActiveBrowser();
1116   // if no browser window exists then create one with no tabs to be filled in
1117   if (!browser) {
1118     browser = new Browser(Browser::CreateParams(
1119         [self lastProfile], chrome::HOST_DESKTOP_TYPE_NATIVE));
1120     browser->window()->Show();
1121   }
1123   CommandLine dummy(CommandLine::NO_PROGRAM);
1124   chrome::startup::IsFirstRun first_run = first_run::IsChromeFirstRun() ?
1125       chrome::startup::IS_FIRST_RUN : chrome::startup::IS_NOT_FIRST_RUN;
1126   StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
1127   launch.OpenURLsInBrowser(browser, false, urls);
1130 - (void)getUrl:(NSAppleEventDescriptor*)event
1131      withReply:(NSAppleEventDescriptor*)reply {
1132   NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
1133                       stringValue];
1135   GURL gurl(base::SysNSStringToUTF8(urlStr));
1136   std::vector<GURL> gurlVector;
1137   gurlVector.push_back(gurl);
1139   [self openUrls:gurlVector];
1142 // Apple Event handler that receives print event from service
1143 // process, gets the required data and launches Print dialog.
1144 - (void)submitCloudPrintJob:(NSAppleEventDescriptor*)event {
1145   // Pull parameter list out of Apple Event.
1146   NSAppleEventDescriptor* paramList =
1147       [event paramDescriptorForKeyword:cloud_print::kAECloudPrintClass];
1149   if (paramList != nil) {
1150     // Pull required fields out of parameter list.
1151     NSString* mime = [[paramList descriptorAtIndex:1] stringValue];
1152     NSString* inputPath = [[paramList descriptorAtIndex:2] stringValue];
1153     NSString* printTitle = [[paramList descriptorAtIndex:3] stringValue];
1154     NSString* printTicket = [[paramList descriptorAtIndex:4] stringValue];
1155     // Convert the title to UTF 16 as required.
1156     string16 title16 = base::SysNSStringToUTF16(printTitle);
1157     string16 printTicket16 = base::SysNSStringToUTF16(printTicket);
1158     print_dialog_cloud::CreatePrintDialogForFile(
1159         ProfileManager::GetDefaultProfile(), NULL,
1160         base::FilePath([inputPath UTF8String]), title16,
1161         printTicket16, [mime UTF8String], /*delete_on_close=*/false);
1162   }
1165 - (void)application:(NSApplication*)sender
1166           openFiles:(NSArray*)filenames {
1167   std::vector<GURL> gurlVector;
1168   for (NSString* file in filenames) {
1169     GURL gurl =
1170         net::FilePathToFileURL(base::FilePath(base::SysNSStringToUTF8(file)));
1171     gurlVector.push_back(gurl);
1172   }
1173   if (!gurlVector.empty())
1174     [self openUrls:gurlVector];
1175   else
1176     NOTREACHED() << "Nothing to open!";
1178   [sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
1181 // Show the preferences window, or bring it to the front if it's already
1182 // visible.
1183 - (IBAction)showPreferences:(id)sender {
1184   if (Browser* browser = ActivateBrowser([self lastProfile])) {
1185     // Show options tab in the active browser window.
1186     chrome::ShowSettings(browser);
1187   } else {
1188     // No browser window, so create one for the options tab.
1189     chrome::OpenOptionsWindow([self lastProfile]);
1190   }
1193 - (IBAction)orderFrontStandardAboutPanel:(id)sender {
1194   if (Browser* browser = ActivateBrowser([self lastProfile])) {
1195     chrome::ShowAboutChrome(browser);
1196   } else {
1197     // No browser window, so create one for the about tab.
1198     chrome::OpenAboutWindow([self lastProfile]);
1199   }
1202 - (IBAction)toggleConfirmToQuit:(id)sender {
1203   PrefService* prefService = g_browser_process->local_state();
1204   bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1205   prefService->SetBoolean(prefs::kConfirmToQuitEnabled, !enabled);
1208 // Explicitly bring to the foreground when creating new windows from the dock.
1209 - (void)commandFromDock:(id)sender {
1210   [NSApp activateIgnoringOtherApps:YES];
1211   [self commandDispatch:sender];
1214 - (NSMenu*)applicationDockMenu:(NSApplication*)sender {
1215   NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
1216   Profile* profile = [self lastProfile];
1218   BOOL profilesAdded = [profileMenuController_ insertItemsIntoMenu:dockMenu
1219                                                           atOffset:0
1220                                                           fromDock:YES];
1221   if (profilesAdded)
1222     [dockMenu addItem:[NSMenuItem separatorItem]];
1224   NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
1225   scoped_nsobject<NSMenuItem> item(
1226       [[NSMenuItem alloc] initWithTitle:titleStr
1227                                  action:@selector(commandFromDock:)
1228                           keyEquivalent:@""]);
1229   [item setTarget:self];
1230   [item setTag:IDC_NEW_WINDOW];
1231   [item setEnabled:[self validateUserInterfaceItem:item]];
1232   [dockMenu addItem:item];
1234   titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
1235   item.reset(
1236       [[NSMenuItem alloc] initWithTitle:titleStr
1237                                  action:@selector(commandFromDock:)
1238                           keyEquivalent:@""]);
1239   [item setTarget:self];
1240   [item setTag:IDC_NEW_INCOGNITO_WINDOW];
1241   [item setEnabled:[self validateUserInterfaceItem:item]];
1242   [dockMenu addItem:item];
1244   // TODO(rickcam): Mock out BackgroundApplicationListModel, then add unit
1245   // tests which use the mock in place of the profile-initialized model.
1247   // Avoid breaking unit tests which have no profile.
1248   if (profile) {
1249     BackgroundApplicationListModel applications(profile);
1250     if (applications.size()) {
1251       int position = 0;
1252       NSString* menuStr =
1253           l10n_util::GetNSStringWithFixup(IDS_BACKGROUND_APPS_MAC);
1254       scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:menuStr]);
1255       for (extensions::ExtensionList::const_iterator cursor =
1256                applications.begin();
1257            cursor != applications.end();
1258            ++cursor, ++position) {
1259         DCHECK_EQ(applications.GetPosition(*cursor), position);
1260         NSString* itemStr =
1261             base::SysUTF16ToNSString(UTF8ToUTF16((*cursor)->name()));
1262         scoped_nsobject<NSMenuItem> appItem([[NSMenuItem alloc]
1263             initWithTitle:itemStr
1264                    action:@selector(executeApplication:)
1265             keyEquivalent:@""]);
1266         [appItem setTarget:self];
1267         [appItem setTag:position];
1268         [appMenu addItem:appItem];
1269       }
1271       scoped_nsobject<NSMenuItem> appMenuItem([[NSMenuItem alloc]
1272           initWithTitle:menuStr
1273                  action:@selector(executeApplication:)
1274           keyEquivalent:@""]);
1275       [appMenuItem setTarget:self];
1276       [appMenuItem setTag:IDC_VIEW_BACKGROUND_PAGES];
1277       [appMenuItem setSubmenu:appMenu];
1278       [dockMenu addItem:appMenuItem];
1279     }
1280   }
1282   return dockMenu;
1285 - (const std::vector<GURL>&)startupUrls {
1286   return startupUrls_;
1289 - (void)clearStartupUrls {
1290   startupUrls_.clear();
1293 - (BookmarkMenuBridge*)bookmarkMenuBridge {
1294   return bookmarkMenuBridge_.get();
1297 - (void)addObserverForWorkAreaChange:(ui::WorkAreaWatcherObserver*)observer {
1298   workAreaChangeObservers_.AddObserver(observer);
1301 - (void)removeObserverForWorkAreaChange:(ui::WorkAreaWatcherObserver*)observer {
1302   workAreaChangeObservers_.RemoveObserver(observer);
1305 - (void)applicationDidChangeScreenParameters:(NSNotification*)notification {
1306   // During this callback the working area is not always already updated. Defer.
1307   [self performSelector:@selector(delayedScreenParametersUpdate)
1308              withObject:nil
1309              afterDelay:0];
1312 - (void)delayedScreenParametersUpdate {
1313   FOR_EACH_OBSERVER(ui::WorkAreaWatcherObserver, workAreaChangeObservers_,
1314       WorkAreaChanged());
1317 @end  // @implementation AppController
1319 //---------------------------------------------------------------------------
1321 namespace app_controller_mac {
1323 bool IsOpeningNewWindow() {
1324   return g_is_opening_new_window;
1327 }  // namespace app_controller_mac