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