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