Override server-side simple-cache trial with commandline switches.
[chromium-blink-merge.git] / chrome / browser / app_controller_mac.mm
blobfeb0f95f782cfa782706dee63350095316590490
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 // Placed here for 2 reasons:
553 // 1) Same spot as other Pref stuff
554 // 2) Try and be friendly by keeping this after app launch
555 - (void)setUpdateCheckInterval {
556 #if defined(GOOGLE_CHROME_BUILD)
557   CFStringRef app = CFSTR("com.google.Keystone.Agent");
558   CFStringRef checkInterval = CFSTR("checkInterval");
559   CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
560   if (!plist) {
561     const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
562     NSNumber* value = [NSNumber numberWithFloat:fiveHoursInSeconds];
563     CFPreferencesSetAppValue(checkInterval, value, app);
564     CFPreferencesAppSynchronize(app);
565   }
566 #endif
569 // This is called after profiles have been loaded and preferences registered.
570 // It is safe to access the default profile here.
571 - (void)applicationDidFinishLaunching:(NSNotification*)notify {
572   // Notify BrowserList to keep the application running so it doesn't go away
573   // when all the browser windows get closed.
574   chrome::StartKeepAlive();
576   [self setUpdateCheckInterval];
578   // Build up the encoding menu, the order of the items differs based on the
579   // current locale (see http://crbug.com/7647 for details).
580   // We need a valid g_browser_process to get the profile which is why we can't
581   // call this from awakeFromNib.
582   NSMenu* viewMenu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu];
583   NSMenuItem* encodingMenuItem = [viewMenu itemWithTag:IDC_ENCODING_MENU];
584   NSMenu* encodingMenu = [encodingMenuItem submenu];
585   EncodingMenuControllerDelegate::BuildEncodingMenu([self lastProfile],
586                                                     encodingMenu);
588   // Since Chrome is localized to more languages than the OS, tell Cocoa which
589   // menu is the Help so it can add the search item to it.
590   [NSApp setHelpMenu:helpMenu_];
592   // Record the path to the (browser) app bundle; this is used by the app mode
593   // shim.
594   RecordLastRunAppBundlePath();
596   // Makes "Services" menu items available.
597   [self registerServicesMenuTypesTo:[notify object]];
599   startupComplete_ = YES;
601   // TODO(viettrungluu): This is very temporary, since this should be done "in"
602   // |BrowserMain()|, i.e., this list of startup URLs should be appended to the
603   // (probably-empty) list of URLs from the command line.
604   if (startupUrls_.size()) {
605     [self openUrls:startupUrls_];
606     [self clearStartupUrls];
607   }
609   const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess();
610   if (!parsed_command_line.HasSwitch(switches::kEnableExposeForTabs)) {
611     [tabposeMenuItem_ setHidden:YES];
612   }
614   PrefService* localState = g_browser_process->local_state();
615   if (localState) {
616     localPrefRegistrar_.Init(localState);
617     localPrefRegistrar_.Add(
618         prefs::kAllowFileSelectionDialogs,
619         base::Bind(&chrome::BrowserCommandController::UpdateOpenFileState,
620                    menuState_.get()));
621   }
624 // This is called after profiles have been loaded and preferences registered.
625 // It is safe to access the default profile here.
626 - (void)applicationDidBecomeActive:(NSNotification*)notify {
627   content::PluginService::GetInstance()->AppActivated();
630 // Helper function for populating and displaying the in progress downloads at
631 // exit alert panel.
632 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
633   NSString* titleText = nil;
634   NSString* explanationText = nil;
635   NSString* waitTitle = nil;
636   NSString* exitTitle = nil;
638   // Set the dialog text based on whether or not there are multiple downloads.
639   if (downloadCount == 1) {
640     // Dialog text: warning and explanation.
641     titleText = l10n_util::GetNSString(
642         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_TITLE);
643     explanationText = l10n_util::GetNSString(
644         IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION);
645   } else {
646     // Dialog text: warning and explanation.
647     titleText = l10n_util::GetNSStringF(
648         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_TITLE,
649         base::IntToString16(downloadCount));
650     explanationText = l10n_util::GetNSString(
651         IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION);
652   }
653   // Cancel download and exit button text.
654   exitTitle = l10n_util::GetNSString(
655       IDS_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL);
657   // Wait for download button text.
658   waitTitle = l10n_util::GetNSString(
659       IDS_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL);
661   // 'waitButton' is the default choice.
662   int choice = NSRunAlertPanel(titleText, explanationText,
663                                waitTitle, exitTitle, nil);
664   return choice == NSAlertDefaultReturn ? YES : NO;
667 // Check all profiles for in progress downloads, and if we find any, prompt the
668 // user to see if we should continue to exit (and thus cancel the downloads), or
669 // if we should wait.
670 - (BOOL)shouldQuitWithInProgressDownloads {
671   ProfileManager* profile_manager = g_browser_process->profile_manager();
672   if (!profile_manager)
673     return YES;
675   std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
676   for (size_t i = 0; i < profiles.size(); ++i) {
677     DownloadService* download_service =
678       DownloadServiceFactory::GetForProfile(profiles[i]);
679     DownloadManager* download_manager =
680         (download_service->HasCreatedDownloadManager() ?
681          BrowserContext::GetDownloadManager(profiles[i]) : NULL);
682     if (download_manager && download_manager->InProgressCount() > 0) {
683       int downloadCount = download_manager->InProgressCount();
684       if ([self userWillWaitForInProgressDownloads:downloadCount]) {
685         // Create a new browser window (if necessary) and navigate to the
686         // downloads page if the user chooses to wait.
687         Browser* browser = chrome::FindBrowserWithProfile(
688             profiles[i], chrome::HOST_DESKTOP_TYPE_NATIVE);
689         if (!browser) {
690           browser = new Browser(Browser::CreateParams(
691               profiles[i], chrome::HOST_DESKTOP_TYPE_NATIVE));
692           browser->window()->Show();
693         }
694         DCHECK(browser);
695         chrome::ShowDownloads(browser);
696         return NO;
697       }
699       // User wants to exit.
700       return YES;
701     }
702   }
704   // No profiles or active downloads found, okay to exit.
705   return YES;
708 // Called to determine if we should enable the "restore tab" menu item.
709 // Checks with the TabRestoreService to see if there's anything there to
710 // restore and returns YES if so.
711 - (BOOL)canRestoreTab {
712   TabRestoreService* service =
713       TabRestoreServiceFactory::GetForProfile([self lastProfile]);
714   return service && !service->entries().empty();
717 // Returns true if there is a modal window (either window- or application-
718 // modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
719 // sheets) will not count as blocking the browser. But things like open/save
720 // dialogs that are window modal will block the browser.
721 - (BOOL)keyWindowIsModal {
722   if ([NSApp modalWindow])
723     return YES;
725   Browser* browser = chrome::GetLastActiveBrowser();
726   return browser &&
727          [[browser->window()->GetNativeWindow() attachedSheet]
728              isKindOfClass:[NSWindow class]];
731 // Called to validate menu items when there are no key windows. All the
732 // items we care about have been set with the |commandDispatch:| action and
733 // a target of FirstResponder in IB. If it's not one of those, let it
734 // continue up the responder chain to be handled elsewhere. We pull out the
735 // tag as the cross-platform constant to differentiate and dispatch the
736 // various commands.
737 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
738   SEL action = [item action];
739   BOOL enable = NO;
740   if (action == @selector(commandDispatch:) ||
741       action == @selector(commandFromDock:)) {
742     NSInteger tag = [item tag];
743     if (menuState_ &&  // NULL in tests.
744         menuState_->SupportsCommand(tag)) {
745       switch (tag) {
746         // The File Menu commands are not automatically disabled by Cocoa when a
747         // dialog sheet obscures the browser window, so we disable several of
748         // them here.  We don't need to include IDC_CLOSE_WINDOW, because
749         // app_controller is only activated when there are no key windows (see
750         // function comment).
751         case IDC_RESTORE_TAB:
752           enable = ![self keyWindowIsModal] && [self canRestoreTab];
753           break;
754         // Browser-level items that open in new tabs should not open if there's
755         // a window- or app-modal dialog.
756         case IDC_OPEN_FILE:
757         case IDC_NEW_TAB:
758         case IDC_SHOW_HISTORY:
759         case IDC_SHOW_BOOKMARK_MANAGER:
760           enable = ![self keyWindowIsModal];
761           break;
762         // Browser-level items that open in new windows.
763         case IDC_TASK_MANAGER:
764           // Allow the user to open a new window if there's a window-modal
765           // dialog.
766           enable = ![self keyWindowIsModal];
767           break;
768         case IDC_SHOW_SYNC_SETUP: {
769           Profile* lastProfile = [self lastProfile];
770           // The profile may be NULL during shutdown -- see
771           // http://code.google.com/p/chromium/issues/detail?id=43048 .
772           //
773           // TODO(akalin,viettrungluu): Figure out whether this method
774           // can be prevented from being called if lastProfile is
775           // NULL.
776           if (!lastProfile) {
777             LOG(WARNING)
778                 << "NULL lastProfile detected -- not doing anything";
779             break;
780           }
781           SigninManager* signin = SigninManagerFactory::GetForProfile(
782               lastProfile->GetOriginalProfile());
783           enable = signin->IsSigninAllowed() &&
784               ![self keyWindowIsModal];
785           [BrowserWindowController updateSigninItem:item
786                                          shouldShow:enable
787                                      currentProfile:lastProfile];
788           break;
789         }
790         case IDC_FEEDBACK:
791           enable = NO;
792           break;
793         default:
794           enable = menuState_->IsCommandEnabled(tag) ?
795                    ![self keyWindowIsModal] : NO;
796       }
797     }
798   } else if (action == @selector(terminate:)) {
799     enable = YES;
800   } else if (action == @selector(showPreferences:)) {
801     enable = YES;
802   } else if (action == @selector(orderFrontStandardAboutPanel:)) {
803     enable = YES;
804   } else if (action == @selector(commandFromDock:)) {
805     enable = YES;
806   } else if (action == @selector(toggleConfirmToQuit:)) {
807     [self updateConfirmToQuitPrefMenuItem:static_cast<NSMenuItem*>(item)];
808     enable = YES;
809   } else if (action == @selector(executeApplication:)) {
810     enable = YES;
811   }
812   return enable;
815 // Called when the user picks a menu item when there are no key windows, or when
816 // there is no foreground browser window. Calls through to the browser object to
817 // execute the command. This assumes that the command is supported and doesn't
818 // check, otherwise it should have been disabled in the UI in
819 // |-validateUserInterfaceItem:|.
820 - (void)commandDispatch:(id)sender {
821   Profile* lastProfile = [self lastProfile];
823   // Handle the case where we're dispatching a command from a sender that's in a
824   // browser window. This means that the command came from a background window
825   // and is getting here because the foreground window is not a browser window.
826   if ([sender respondsToSelector:@selector(window)]) {
827     id delegate = [[sender window] windowController];
828     if ([delegate isKindOfClass:[BrowserWindowController class]]) {
829       [delegate commandDispatch:sender];
830       return;
831     }
832   }
834   // Ignore commands during session restore's browser creation.  It uses a
835   // nested message loop and commands dispatched during this operation cause
836   // havoc.
837   if (SessionRestore::IsRestoring(lastProfile) &&
838       MessageLoop::current()->IsNested())
839     return;
841   NSInteger tag = [sender tag];
842   switch (tag) {
843     case IDC_NEW_TAB:
844       // Create a new tab in an existing browser window (which we activate) if
845       // possible.
846       if (Browser* browser = ActivateBrowser(lastProfile)) {
847         chrome::ExecuteCommand(browser, IDC_NEW_TAB);
848         break;
849       }
850       // Else fall through to create new window.
851     case IDC_NEW_WINDOW:
852       CreateBrowser(lastProfile);
853       break;
854     case IDC_FOCUS_LOCATION:
855       chrome::ExecuteCommand(ActivateOrCreateBrowser(lastProfile),
856                              IDC_FOCUS_LOCATION);
857       break;
858     case IDC_FOCUS_SEARCH:
859       chrome::ExecuteCommand(ActivateOrCreateBrowser(lastProfile),
860                              IDC_FOCUS_SEARCH);
861       break;
862     case IDC_NEW_INCOGNITO_WINDOW:
863       CreateBrowser(lastProfile->GetOffTheRecordProfile());
864       break;
865     case IDC_RESTORE_TAB:
866       // There is only the native desktop on Mac.
867       chrome::OpenWindowWithRestoredTabs(lastProfile,
868                                          chrome::HOST_DESKTOP_TYPE_NATIVE);
869       break;
870     case IDC_OPEN_FILE:
871       chrome::ExecuteCommand(CreateBrowser(lastProfile), IDC_OPEN_FILE);
872       break;
873     case IDC_CLEAR_BROWSING_DATA: {
874       // There may not be a browser open, so use the default profile.
875       if (Browser* browser = ActivateBrowser(lastProfile)) {
876         chrome::ShowClearBrowsingDataDialog(browser);
877       } else {
878         chrome::OpenClearBrowsingDataDialogWindow(lastProfile);
879       }
880       break;
881     }
882     case IDC_IMPORT_SETTINGS: {
883       if (Browser* browser = ActivateBrowser(lastProfile)) {
884         chrome::ShowImportDialog(browser);
885       } else {
886         chrome::OpenImportSettingsDialogWindow(lastProfile);
887       }
888       break;
889     }
890     case IDC_SHOW_BOOKMARK_MANAGER:
891       content::RecordAction(UserMetricsAction("ShowBookmarkManager"));
892       if (Browser* browser = ActivateBrowser(lastProfile)) {
893         chrome::ShowBookmarkManager(browser);
894       } else {
895         // No browser window, so create one for the bookmark manager tab.
896         chrome::OpenBookmarkManagerWindow(lastProfile);
897       }
898       break;
899     case IDC_SHOW_HISTORY:
900       if (Browser* browser = ActivateBrowser(lastProfile))
901         chrome::ShowHistory(browser);
902       else
903         chrome::OpenHistoryWindow(lastProfile);
904       break;
905     case IDC_SHOW_DOWNLOADS:
906       if (Browser* browser = ActivateBrowser(lastProfile))
907         chrome::ShowDownloads(browser);
908       else
909         chrome::OpenDownloadsWindow(lastProfile);
910       break;
911     case IDC_MANAGE_EXTENSIONS:
912       if (Browser* browser = ActivateBrowser(lastProfile))
913         chrome::ShowExtensions(browser, std::string());
914       else
915         chrome::OpenExtensionsWindow(lastProfile);
916       break;
917     case IDC_HELP_PAGE_VIA_MENU:
918       if (Browser* browser = ActivateBrowser(lastProfile))
919         chrome::ShowHelp(browser, chrome::HELP_SOURCE_MENU);
920       else
921         chrome::OpenHelpWindow(lastProfile, chrome::HELP_SOURCE_MENU);
922       break;
923     case IDC_SHOW_SYNC_SETUP:
924       if (Browser* browser = ActivateBrowser(lastProfile))
925         chrome::ShowBrowserSignin(browser, SyncPromoUI::SOURCE_MENU);
926       else
927         chrome::OpenSyncSetupWindow(lastProfile, SyncPromoUI::SOURCE_MENU);
928       break;
929     case IDC_TASK_MANAGER:
930       content::RecordAction(UserMetricsAction("TaskManager"));
931       TaskManagerMac::Show(false);
932       break;
933     case IDC_OPTIONS:
934       [self showPreferences:sender];
935       break;
936   }
939 // Run a (background) application in a new tab.
940 - (void)executeApplication:(id)sender {
941   NSInteger tag = [sender tag];
942   Profile* profile = [self lastProfile];
943   DCHECK(profile);
944   BackgroundApplicationListModel applications(profile);
945   DCHECK(tag >= 0 &&
946          tag < static_cast<int>(applications.size()));
947   const extensions::Extension* extension = applications.GetExtension(tag);
948   BackgroundModeManager::LaunchBackgroundApplication(profile, extension);
951 // Same as |-commandDispatch:|, but executes commands using a disposition
952 // determined by the key flags. This will get called in the case where the
953 // frontmost window is not a browser window, and the user has command-clicked
954 // a button in a background browser window whose action is
955 // |-commandDispatchUsingKeyModifiers:|
956 - (void)commandDispatchUsingKeyModifiers:(id)sender {
957   DCHECK(sender);
958   if ([sender respondsToSelector:@selector(window)]) {
959     id delegate = [[sender window] windowController];
960     if ([delegate isKindOfClass:[BrowserWindowController class]]) {
961       [delegate commandDispatchUsingKeyModifiers:sender];
962     }
963   }
966 // NSApplication delegate method called when someone clicks on the
967 // dock icon and there are no open windows.  To match standard mac
968 // behavior, we should open a new window.
969 - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
970                     hasVisibleWindows:(BOOL)flag {
971   // If the browser is currently trying to quit, don't do anything and return NO
972   // to prevent AppKit from doing anything.
973   // TODO(rohitrao): Remove this code when http://crbug.com/40861 is resolved.
974   if (browser_shutdown::IsTryingToQuit())
975     return NO;
977   // Don't do anything if there are visible tabbed or popup windows.  This will
978   // cause AppKit to unminimize the most recently minimized window. If the
979   // visible windows are panels or notifications, we still need to open a new
980   // window.
981   if (flag) {
982     for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
983       Browser* browser = *iter;
984       if (browser->is_type_tabbed() || browser->is_type_popup())
985         return YES;
986     }
987   }
989   // If launched as a hidden login item (due to installation of a persistent app
990   // or by the user, for example in System Preferences->Accounts->Login Items),
991   // allow session to be restored first time the user clicks on a Dock icon.
992   // Normally, it'd just open a new empty page.
993   {
994     static BOOL doneOnce = NO;
995     if (!doneOnce) {
996       doneOnce = YES;
997       if (base::mac::WasLaunchedAsHiddenLoginItem()) {
998         SessionService* sessionService =
999             SessionServiceFactory::GetForProfile([self lastProfile]);
1000         if (sessionService &&
1001             sessionService->RestoreIfNecessary(std::vector<GURL>()))
1002           return NO;
1003       }
1004     }
1005   }
1007   // Platform apps don't use browser windows so don't do anything if there are
1008   // visible windows.
1009   const CommandLine& command_line = *CommandLine::ForCurrentProcess();
1010   if (flag && command_line.HasSwitch(switches::kAppId))
1011     return YES;
1013   // Otherwise open a new window.
1014   {
1015     base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
1016     int return_code;
1017     StartupBrowserCreator browser_creator;
1018     browser_creator.LaunchBrowser(
1019         command_line, [self lastProfile], base::FilePath(),
1020         chrome::startup::IS_NOT_PROCESS_STARTUP,
1021         chrome::startup::IS_NOT_FIRST_RUN, &return_code);
1022   }
1024   // We've handled the reopen event, so return NO to tell AppKit not
1025   // to do anything.
1026   return NO;
1029 - (void)initMenuState {
1030   menuState_.reset(new CommandUpdater(NULL));
1031   menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true);
1032   menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
1033   menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
1034   menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true);
1035   menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
1036   menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
1037   menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
1038   menuState_->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true);
1039   menuState_->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true);
1040   menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
1041   menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
1042   menuState_->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true);
1043   menuState_->UpdateCommandEnabled(IDC_HELP_PAGE_VIA_MENU, true);
1044   menuState_->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true);
1045   menuState_->UpdateCommandEnabled(IDC_FEEDBACK, true);
1046   menuState_->UpdateCommandEnabled(IDC_SHOW_SYNC_SETUP, true);
1047   menuState_->UpdateCommandEnabled(IDC_TASK_MANAGER, true);
1050 // Conditionally adds the Profile menu to the main menu bar.
1051 - (void)initProfileMenu {
1052   NSMenu* mainMenu = [NSApp mainMenu];
1053   NSMenuItem* profileMenu = [mainMenu itemWithTag:IDC_PROFILE_MAIN_MENU];
1055   if (!ProfileManager::IsMultipleProfilesEnabled()) {
1056     [mainMenu removeItem:profileMenu];
1057     return;
1058   }
1060   // The controller will unhide the menu if necessary.
1061   [profileMenu setHidden:YES];
1063   profileMenuController_.reset(
1064       [[ProfileMenuController alloc] initWithMainMenuItem:profileMenu]);
1067 // The Confirm to Quit preference is atypical in that the preference lives in
1068 // the app menu right above the Quit menu item. This method will refresh the
1069 // display of that item depending on the preference state.
1070 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item {
1071   // Format the string so that the correct key equivalent is displayed.
1072   NSString* acceleratorString = [ConfirmQuitPanelController keyCommandString];
1073   NSString* title = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_OPTION,
1074       base::SysNSStringToUTF16(acceleratorString));
1075   [item setTitle:title];
1077   const PrefService* prefService = g_browser_process->local_state();
1078   bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1079   [item setState:enabled ? NSOnState : NSOffState];
1082 - (void)registerServicesMenuTypesTo:(NSApplication*)app {
1083   // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which
1084   // handles requests from services.
1085   NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
1086   [app registerServicesMenuSendTypes:types returnTypes:types];
1089 - (Profile*)lastProfile {
1090   // Return the profile of the last-used BrowserWindowController, if available.
1091   if (lastProfile_)
1092     return lastProfile_;
1094   // On first launch, no profile will be stored, so use last from Local State.
1095   if (g_browser_process->profile_manager())
1096     return g_browser_process->profile_manager()->GetLastUsedProfile();
1098   return NULL;
1101 // Various methods to open URLs that we get in a native fashion. We use
1102 // StartupBrowserCreator here because on the other platforms, URLs to open come
1103 // through the ProcessSingleton, and it calls StartupBrowserCreator. It's best
1104 // to bottleneck the openings through that for uniform handling.
1106 - (void)openUrls:(const std::vector<GURL>&)urls {
1107   // If the browser hasn't started yet, just queue up the URLs.
1108   if (!startupComplete_) {
1109     startupUrls_.insert(startupUrls_.end(), urls.begin(), urls.end());
1110     return;
1111   }
1113   Browser* browser = chrome::GetLastActiveBrowser();
1114   // if no browser window exists then create one with no tabs to be filled in
1115   if (!browser) {
1116     browser = new Browser(Browser::CreateParams(
1117         [self lastProfile], chrome::HOST_DESKTOP_TYPE_NATIVE));
1118     browser->window()->Show();
1119   }
1121   CommandLine dummy(CommandLine::NO_PROGRAM);
1122   chrome::startup::IsFirstRun first_run = first_run::IsChromeFirstRun() ?
1123       chrome::startup::IS_FIRST_RUN : chrome::startup::IS_NOT_FIRST_RUN;
1124   StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
1125   launch.OpenURLsInBrowser(browser, false, urls);
1128 - (void)getUrl:(NSAppleEventDescriptor*)event
1129      withReply:(NSAppleEventDescriptor*)reply {
1130   NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject]
1131                       stringValue];
1133   GURL gurl(base::SysNSStringToUTF8(urlStr));
1134   std::vector<GURL> gurlVector;
1135   gurlVector.push_back(gurl);
1137   [self openUrls:gurlVector];
1140 // Apple Event handler that receives print event from service
1141 // process, gets the required data and launches Print dialog.
1142 - (void)submitCloudPrintJob:(NSAppleEventDescriptor*)event {
1143   // Pull parameter list out of Apple Event.
1144   NSAppleEventDescriptor* paramList =
1145       [event paramDescriptorForKeyword:cloud_print::kAECloudPrintClass];
1147   if (paramList != nil) {
1148     // Pull required fields out of parameter list.
1149     NSString* mime = [[paramList descriptorAtIndex:1] stringValue];
1150     NSString* inputPath = [[paramList descriptorAtIndex:2] stringValue];
1151     NSString* printTitle = [[paramList descriptorAtIndex:3] stringValue];
1152     NSString* printTicket = [[paramList descriptorAtIndex:4] stringValue];
1153     // Convert the title to UTF 16 as required.
1154     string16 title16 = base::SysNSStringToUTF16(printTitle);
1155     string16 printTicket16 = base::SysNSStringToUTF16(printTicket);
1156     print_dialog_cloud::CreatePrintDialogForFile(
1157         ProfileManager::GetDefaultProfile(), NULL,
1158         base::FilePath([inputPath UTF8String]), title16,
1159         printTicket16, [mime UTF8String], /*delete_on_close=*/false);
1160   }
1163 - (void)application:(NSApplication*)sender
1164           openFiles:(NSArray*)filenames {
1165   std::vector<GURL> gurlVector;
1166   for (NSString* file in filenames) {
1167     GURL gurl =
1168         net::FilePathToFileURL(base::FilePath(base::SysNSStringToUTF8(file)));
1169     gurlVector.push_back(gurl);
1170   }
1171   if (!gurlVector.empty())
1172     [self openUrls:gurlVector];
1173   else
1174     NOTREACHED() << "Nothing to open!";
1176   [sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
1179 // Show the preferences window, or bring it to the front if it's already
1180 // visible.
1181 - (IBAction)showPreferences:(id)sender {
1182   if (Browser* browser = ActivateBrowser([self lastProfile])) {
1183     // Show options tab in the active browser window.
1184     chrome::ShowSettings(browser);
1185   } else {
1186     // No browser window, so create one for the options tab.
1187     chrome::OpenOptionsWindow([self lastProfile]);
1188   }
1191 - (IBAction)orderFrontStandardAboutPanel:(id)sender {
1192   if (Browser* browser = ActivateBrowser([self lastProfile])) {
1193     chrome::ShowAboutChrome(browser);
1194   } else {
1195     // No browser window, so create one for the about tab.
1196     chrome::OpenAboutWindow([self lastProfile]);
1197   }
1200 - (IBAction)toggleConfirmToQuit:(id)sender {
1201   PrefService* prefService = g_browser_process->local_state();
1202   bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1203   prefService->SetBoolean(prefs::kConfirmToQuitEnabled, !enabled);
1206 // Explicitly bring to the foreground when creating new windows from the dock.
1207 - (void)commandFromDock:(id)sender {
1208   [NSApp activateIgnoringOtherApps:YES];
1209   [self commandDispatch:sender];
1212 - (NSMenu*)applicationDockMenu:(NSApplication*)sender {
1213   NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease];
1214   Profile* profile = [self lastProfile];
1216   BOOL profilesAdded = [profileMenuController_ insertItemsIntoMenu:dockMenu
1217                                                           atOffset:0
1218                                                           fromDock:YES];
1219   if (profilesAdded)
1220     [dockMenu addItem:[NSMenuItem separatorItem]];
1222   NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
1223   scoped_nsobject<NSMenuItem> item(
1224       [[NSMenuItem alloc] initWithTitle:titleStr
1225                                  action:@selector(commandFromDock:)
1226                           keyEquivalent:@""]);
1227   [item setTarget:self];
1228   [item setTag:IDC_NEW_WINDOW];
1229   [item setEnabled:[self validateUserInterfaceItem:item]];
1230   [dockMenu addItem:item];
1232   titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
1233   item.reset(
1234       [[NSMenuItem alloc] initWithTitle:titleStr
1235                                  action:@selector(commandFromDock:)
1236                           keyEquivalent:@""]);
1237   [item setTarget:self];
1238   [item setTag:IDC_NEW_INCOGNITO_WINDOW];
1239   [item setEnabled:[self validateUserInterfaceItem:item]];
1240   [dockMenu addItem:item];
1242   // TODO(rickcam): Mock out BackgroundApplicationListModel, then add unit
1243   // tests which use the mock in place of the profile-initialized model.
1245   // Avoid breaking unit tests which have no profile.
1246   if (profile) {
1247     BackgroundApplicationListModel applications(profile);
1248     if (applications.size()) {
1249       int position = 0;
1250       NSString* menuStr =
1251           l10n_util::GetNSStringWithFixup(IDS_BACKGROUND_APPS_MAC);
1252       scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:menuStr]);
1253       for (extensions::ExtensionList::const_iterator cursor =
1254                applications.begin();
1255            cursor != applications.end();
1256            ++cursor, ++position) {
1257         DCHECK_EQ(applications.GetPosition(*cursor), position);
1258         NSString* itemStr =
1259             base::SysUTF16ToNSString(UTF8ToUTF16((*cursor)->name()));
1260         scoped_nsobject<NSMenuItem> appItem([[NSMenuItem alloc]
1261             initWithTitle:itemStr
1262                    action:@selector(executeApplication:)
1263             keyEquivalent:@""]);
1264         [appItem setTarget:self];
1265         [appItem setTag:position];
1266         [appMenu addItem:appItem];
1267       }
1269       scoped_nsobject<NSMenuItem> appMenuItem([[NSMenuItem alloc]
1270           initWithTitle:menuStr
1271                  action:@selector(executeApplication:)
1272           keyEquivalent:@""]);
1273       [appMenuItem setTarget:self];
1274       [appMenuItem setTag:IDC_VIEW_BACKGROUND_PAGES];
1275       [appMenuItem setSubmenu:appMenu];
1276       [dockMenu addItem:appMenuItem];
1277     }
1278   }
1280   return dockMenu;
1283 - (const std::vector<GURL>&)startupUrls {
1284   return startupUrls_;
1287 - (void)clearStartupUrls {
1288   startupUrls_.clear();
1291 - (BookmarkMenuBridge*)bookmarkMenuBridge {
1292   return bookmarkMenuBridge_.get();
1295 - (void)addObserverForWorkAreaChange:(ui::WorkAreaWatcherObserver*)observer {
1296   workAreaChangeObservers_.AddObserver(observer);
1299 - (void)removeObserverForWorkAreaChange:(ui::WorkAreaWatcherObserver*)observer {
1300   workAreaChangeObservers_.RemoveObserver(observer);
1303 - (void)applicationDidChangeScreenParameters:(NSNotification*)notification {
1304   // During this callback the working area is not always already updated. Defer.
1305   [self performSelector:@selector(delayedScreenParametersUpdate)
1306              withObject:nil
1307              afterDelay:0];
1310 - (void)delayedScreenParametersUpdate {
1311   FOR_EACH_OBSERVER(ui::WorkAreaWatcherObserver, workAreaChangeObservers_,
1312       WorkAreaChanged());
1315 @end  // @implementation AppController
1317 //---------------------------------------------------------------------------
1319 namespace app_controller_mac {
1321 bool IsOpeningNewWindow() {
1322   return g_is_opening_new_window;
1325 }  // namespace app_controller_mac