app_controller_mac.mm revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2011 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/command_line.h" 9#include "base/file_path.h" 10#include "base/mac/mac_util.h" 11#include "base/message_loop.h" 12#include "base/string_number_conversions.h" 13#include "base/sys_string_conversions.h" 14#include "chrome/app/chrome_command_ids.h" 15#include "chrome/browser/background_application_list_model.h" 16#include "chrome/browser/browser_process.h" 17#include "chrome/browser/browser_shutdown.h" 18#include "chrome/browser/browser_thread.h" 19#include "chrome/browser/command_updater.h" 20#include "chrome/browser/download/download_manager.h" 21#include "chrome/browser/metrics/user_metrics.h" 22#include "chrome/browser/printing/print_job_manager.h" 23#include "chrome/browser/profiles/profile_manager.h" 24#include "chrome/browser/sessions/tab_restore_service.h" 25#include "chrome/browser/sync/profile_sync_service.h" 26#include "chrome/browser/sync/sync_ui_util.h" 27#include "chrome/browser/sync/sync_ui_util_mac.h" 28#include "chrome/browser/tab_contents/tab_contents.h" 29#include "chrome/browser/ui/browser.h" 30#include "chrome/browser/ui/browser_init.h" 31#include "chrome/browser/ui/browser_list.h" 32#include "chrome/browser/ui/browser_window.h" 33#import "chrome/browser/ui/cocoa/about_window_controller.h" 34#import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h" 35#import "chrome/browser/ui/cocoa/browser_window_cocoa.h" 36#import "chrome/browser/ui/cocoa/browser_window_controller.h" 37#import "chrome/browser/ui/cocoa/bug_report_window_controller.h" 38#import "chrome/browser/ui/cocoa/clear_browsing_data_controller.h" 39#import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h" 40#import "chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.h" 41#import "chrome/browser/ui/cocoa/history_menu_bridge.h" 42#import "chrome/browser/ui/cocoa/importer/import_settings_dialog.h" 43#import "chrome/browser/ui/cocoa/options/preferences_window_controller.h" 44#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 45#import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h" 46#include "chrome/browser/ui/cocoa/task_manager_mac.h" 47#include "chrome/browser/ui/options/options_window.h" 48#include "chrome/common/app_mode_common_mac.h" 49#include "chrome/common/chrome_paths_internal.h" 50#include "chrome/common/chrome_switches.h" 51#include "chrome/common/notification_service.h" 52#include "chrome/common/url_constants.h" 53#include "grit/chromium_strings.h" 54#include "grit/generated_resources.h" 55#include "net/base/net_util.h" 56#include "ui/base/l10n/l10n_util.h" 57#include "ui/base/l10n/l10n_util_mac.h" 58 59// 10.6 adds a public API for the Spotlight-backed search menu item in the Help 60// menu. Provide the declaration so it can be called below when building with 61// the 10.5 SDK. 62#if !defined(MAC_OS_X_VERSION_10_6) || \ 63 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6 64@interface NSApplication (SnowLeopardSDKDeclarations) 65- (void)setHelpMenu:(NSMenu*)helpMenu; 66@end 67#endif 68 69namespace { 70 71// True while AppController is calling Browser::OpenEmptyWindow(). We need a 72// global flag here, analogue to BrowserInit::InProcessStartup() because 73// otherwise the SessionService will try to restore sessions when we make a new 74// window while there are no other active windows. 75bool g_is_opening_new_window = false; 76 77// Activates a browser window having the given profile (the last one active) if 78// possible and returns a pointer to the activate |Browser| or NULL if this was 79// not possible. If the last active browser is minimized (in particular, if 80// there are only minimized windows), it will unminimize it. 81Browser* ActivateBrowser(Profile* profile) { 82 Browser* browser = BrowserList::GetLastActiveWithProfile(profile); 83 if (browser) 84 browser->window()->Activate(); 85 return browser; 86} 87 88// Creates an empty browser window with the given profile and returns a pointer 89// to the new |Browser|. 90Browser* CreateBrowser(Profile* profile) { 91 { 92 AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true); 93 Browser::OpenEmptyWindow(profile); 94 } 95 96 Browser* browser = BrowserList::GetLastActive(); 97 CHECK(browser); 98 return browser; 99} 100 101// Activates a browser window having the given profile (the last one active) if 102// possible or creates an empty one if necessary. Returns a pointer to the 103// activated/new |Browser|. 104Browser* ActivateOrCreateBrowser(Profile* profile) { 105 if (Browser* browser = ActivateBrowser(profile)) 106 return browser; 107 return CreateBrowser(profile); 108} 109 110// This task synchronizes preferences (under "org.chromium.Chromium" or 111// "com.google.Chrome"), in particular, writes them out to disk. 112class PrefsSyncTask : public Task { 113 public: 114 PrefsSyncTask() {} 115 virtual ~PrefsSyncTask() {} 116 virtual void Run() { 117 if (!CFPreferencesAppSynchronize(app_mode::kAppPrefsID)) 118 LOG(WARNING) << "Error recording application bundle path."; 119 } 120}; 121 122// Record the location of the application bundle (containing the main framework) 123// from which Chromium was loaded. This is used by app mode shims to find 124// Chromium. 125void RecordLastRunAppBundlePath() { 126 // Going up three levels from |chrome::GetVersionedDirectory()| gives the 127 // real, user-visible app bundle directory. (The alternatives give either the 128 // framework's path or the initial app's path, which may be an app mode shim 129 // or a unit test.) 130 FilePath appBundlePath = 131 chrome::GetVersionedDirectory().DirName().DirName().DirName(); 132 CFPreferencesSetAppValue(app_mode::kLastRunAppBundlePathPrefsKey, 133 base::SysUTF8ToCFStringRef(appBundlePath.value()), 134 app_mode::kAppPrefsID); 135 136 // Sync after a delay avoid I/O contention on startup; 1500 ms is plenty. 137 BrowserThread::PostDelayedTask(BrowserThread::FILE, FROM_HERE, 138 new PrefsSyncTask(), 1500); 139} 140 141} // anonymous namespace 142 143@interface AppController(Private) 144- (void)initMenuState; 145- (void)registerServicesMenuTypesTo:(NSApplication*)app; 146- (void)openUrls:(const std::vector<GURL>&)urls; 147- (void)getUrl:(NSAppleEventDescriptor*)event 148 withReply:(NSAppleEventDescriptor*)reply; 149- (void)windowLayeringDidChange:(NSNotification*)inNotification; 150- (void)checkForAnyKeyWindows; 151- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount; 152- (BOOL)shouldQuitWithInProgressDownloads; 153- (void)showPreferencesWindow:(id)sender 154 page:(OptionsPage)page 155 profile:(Profile*)profile; 156- (void)executeApplication:(id)sender; 157@end 158 159@implementation AppController 160 161@synthesize startupComplete = startupComplete_; 162 163// This method is called very early in application startup (ie, before 164// the profile is loaded or any preferences have been registered). Defer any 165// user-data initialization until -applicationDidFinishLaunching:. 166- (void)awakeFromNib { 167 // We need to register the handlers early to catch events fired on launch. 168 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager]; 169 [em setEventHandler:self 170 andSelector:@selector(getUrl:withReply:) 171 forEventClass:kInternetEventClass 172 andEventID:kAEGetURL]; 173 [em setEventHandler:self 174 andSelector:@selector(getUrl:withReply:) 175 forEventClass:'WWW!' // A particularly ancient AppleEvent that dates 176 andEventID:'OURL']; // back to the Spyglass days. 177 178 // Register for various window layering changes. We use these to update 179 // various UI elements (command-key equivalents, etc) when the frontmost 180 // window changes. 181 NSNotificationCenter* notificationCenter = 182 [NSNotificationCenter defaultCenter]; 183 [notificationCenter 184 addObserver:self 185 selector:@selector(windowLayeringDidChange:) 186 name:NSWindowDidBecomeKeyNotification 187 object:nil]; 188 [notificationCenter 189 addObserver:self 190 selector:@selector(windowLayeringDidChange:) 191 name:NSWindowDidResignKeyNotification 192 object:nil]; 193 [notificationCenter 194 addObserver:self 195 selector:@selector(windowLayeringDidChange:) 196 name:NSWindowDidBecomeMainNotification 197 object:nil]; 198 [notificationCenter 199 addObserver:self 200 selector:@selector(windowLayeringDidChange:) 201 name:NSWindowDidResignMainNotification 202 object:nil]; 203 204 // Register for a notification that the number of tabs changes in windows 205 // so we can adjust the close tab/window command keys. 206 [notificationCenter 207 addObserver:self 208 selector:@selector(tabsChanged:) 209 name:kTabStripNumberOfTabsChanged 210 object:nil]; 211 212 // Set up the command updater for when there are no windows open 213 [self initMenuState]; 214 215 // Activate (bring to foreground) if asked to do so. On 216 // Windows this logic isn't necessary since 217 // BrowserWindow::Activate() calls ::SetForegroundWindow() which is 218 // adequate. On Mac, BrowserWindow::Activate() calls -[NSWindow 219 // makeKeyAndOrderFront:] which does not activate the application 220 // itself. 221 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 222 if (parsed_command_line.HasSwitch(switches::kActivateOnLaunch)) { 223 [NSApp activateIgnoringOtherApps:YES]; 224 } 225} 226 227// (NSApplicationDelegate protocol) This is the Apple-approved place to override 228// the default handlers. 229- (void)applicationWillFinishLaunching:(NSNotification*)notification { 230 // Nothing here right now. 231} 232 233- (BOOL)tryToTerminateApplication:(NSApplication*)app { 234 // Check for in-process downloads, and prompt the user if they really want 235 // to quit (and thus cancel downloads). Only check if we're not already 236 // shutting down, else the user might be prompted multiple times if the 237 // download isn't stopped before terminate is called again. 238 if (!browser_shutdown::IsTryingToQuit() && 239 ![self shouldQuitWithInProgressDownloads]) 240 return NO; 241 242 // TODO(viettrungluu): Remove Apple Event handlers here? (It's safe to leave 243 // them in, but I'm not sure about UX; we'd also want to disable other things 244 // though.) http://crbug.com/40861 245 246 // Check if the user really wants to quit by employing the confirm-to-quit 247 // mechanism. 248 if (!browser_shutdown::IsTryingToQuit() && 249 [self applicationShouldTerminate:app] != NSTerminateNow) 250 return NO; 251 252 size_t num_browsers = BrowserList::size(); 253 254 // Give any print jobs in progress time to finish. 255 if (!browser_shutdown::IsTryingToQuit()) 256 g_browser_process->print_job_manager()->StopJobs(true); 257 258 // Initiate a shutdown (via BrowserList::CloseAllBrowsers()) if we aren't 259 // already shutting down. 260 if (!browser_shutdown::IsTryingToQuit()) 261 BrowserList::CloseAllBrowsers(); 262 263 return num_browsers == 0 ? YES : NO; 264} 265 266- (void)stopTryingToTerminateApplication:(NSApplication*)app { 267 if (browser_shutdown::IsTryingToQuit()) { 268 // Reset the "trying to quit" state, so that closing all browser windows 269 // will no longer lead to termination. 270 browser_shutdown::SetTryingToQuit(false); 271 272 // TODO(viettrungluu): Were we to remove Apple Event handlers above, we 273 // would have to reinstall them here. http://crbug.com/40861 274 } 275} 276 277- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)app { 278 // Check if the experiment is enabled. 279 const CommandLine* commandLine(CommandLine::ForCurrentProcess()); 280 if (!commandLine->HasSwitch(switches::kEnableConfirmToQuit)) 281 return NSTerminateNow; 282 283 // If the application is going to terminate as the result of a Cmd+Q 284 // invocation, use the special sauce to prevent accidental quitting. 285 // http://dev.chromium.org/developers/design-documents/confirm-to-quit-experiment 286 287 // How long the user must hold down Cmd+Q to confirm the quit. 288 const NSTimeInterval kTimeToConfirmQuit = 1.5; 289 // Leeway between the |targetDate| and the current time that will confirm a 290 // quit. 291 const NSTimeInterval kTimeDeltaFuzzFactor = 1.0; 292 // Duration of the window fade out animation. 293 const NSTimeInterval kWindowFadeAnimationDuration = 0.2; 294 295 // This logic is only for keyboard-initiated quits. 296 if ([[app currentEvent] type] != NSKeyDown) 297 return NSTerminateNow; 298 299 // If this is the second of two such attempts to quit within a certain time 300 // interval, then just quit. 301 // Time of last quit attempt, if any. 302 static NSDate* lastQuitAttempt; // Initially nil, as it's static. 303 NSDate* timeNow = [NSDate date]; 304 if (lastQuitAttempt && 305 [timeNow timeIntervalSinceDate:lastQuitAttempt] < kTimeDeltaFuzzFactor) { 306 return NSTerminateNow; 307 } else { 308 [lastQuitAttempt release]; // Harmless if already nil. 309 lastQuitAttempt = [timeNow retain]; // Record this attempt for next time. 310 } 311 312 // Show the info panel that explains what the user must to do confirm quit. 313 [[ConfirmQuitPanelController sharedController] showWindow:self]; 314 315 // Spin a nested run loop until the |targetDate| is reached or a KeyUp event 316 // is sent. 317 NSDate* targetDate = 318 [NSDate dateWithTimeIntervalSinceNow:kTimeToConfirmQuit]; 319 BOOL willQuit = NO; 320 NSEvent* nextEvent = nil; 321 do { 322 // Dequeue events until a key up is received. 323 nextEvent = [app nextEventMatchingMask:NSKeyUpMask 324 untilDate:nil 325 inMode:NSEventTrackingRunLoopMode 326 dequeue:YES]; 327 328 // Wait for the time expiry to happen. Once past the hold threshold, 329 // commit to quitting and hide all the open windows. 330 if (!willQuit) { 331 NSDate* now = [NSDate date]; 332 NSTimeInterval difference = [targetDate timeIntervalSinceDate:now]; 333 if (difference < kTimeDeltaFuzzFactor) { 334 willQuit = YES; 335 336 // At this point, the quit has been confirmed and windows should all 337 // fade out to convince the user to release the key combo to finalize 338 // the quit. 339 [NSAnimationContext beginGrouping]; 340 [[NSAnimationContext currentContext] setDuration: 341 kWindowFadeAnimationDuration]; 342 for (NSWindow* aWindow in [app windows]) { 343 // Windows that are set to animate and have a delegate do not 344 // expect to be animated by other things and could result in an 345 // invalid state. If a window is set up like so, just force the 346 // alpha value to 0. Otherwise, animate all pretty and stuff. 347 if (![[aWindow animationForKey:@"alphaValue"] delegate]) { 348 [[aWindow animator] setAlphaValue:0.0]; 349 } else { 350 [aWindow setAlphaValue:0.0]; 351 } 352 } 353 [NSAnimationContext endGrouping]; 354 } 355 } 356 } while (!nextEvent); 357 358 // The user has released the key combo. Discard any events (i.e. the 359 // repeated KeyDown Cmd+Q). 360 [app discardEventsMatchingMask:NSAnyEventMask beforeEvent:nextEvent]; 361 362 if (willQuit) { 363 // The user held down the combination long enough that quitting should 364 // happen. 365 return NSTerminateNow; 366 } else { 367 // Slowly fade the confirm window out in case the user doesn't 368 // understand what they have to do to quit. 369 [[ConfirmQuitPanelController sharedController] dismissPanel]; 370 return NSTerminateCancel; 371 } 372 373 // Default case: terminate. 374 return NSTerminateNow; 375} 376 377// Called when the app is shutting down. Clean-up as appropriate. 378- (void)applicationWillTerminate:(NSNotification*)aNotification { 379 NSAppleEventManager* em = [NSAppleEventManager sharedAppleEventManager]; 380 [em removeEventHandlerForEventClass:kInternetEventClass 381 andEventID:kAEGetURL]; 382 [em removeEventHandlerForEventClass:'WWW!' 383 andEventID:'OURL']; 384 385 // There better be no browser windows left at this point. 386 CHECK_EQ(BrowserList::size(), 0u); 387 388 // Tell BrowserList not to keep the browser process alive. Once all the 389 // browsers get dealloc'd, it will stop the RunLoop and fall back into main(). 390 BrowserList::EndKeepAlive(); 391 392 // Close these off if they have open windows. 393 [prefsController_ close]; 394 [aboutController_ close]; 395 396 [[NSNotificationCenter defaultCenter] removeObserver:self]; 397} 398 399- (void)didEndMainMessageLoop { 400 DCHECK(!BrowserList::HasBrowserWithProfile([self defaultProfile])); 401 if (!BrowserList::HasBrowserWithProfile([self defaultProfile])) { 402 // As we're shutting down, we need to nuke the TabRestoreService, which 403 // will start the shutdown of the NavigationControllers and allow for 404 // proper shutdown. If we don't do this, Chrome won't shut down cleanly, 405 // and may end up crashing when some thread tries to use the IO thread (or 406 // another thread) that is no longer valid. 407 [self defaultProfile]->ResetTabRestoreService(); 408 } 409} 410 411// Helper routine to get the window controller if the key window is a tabbed 412// window, or nil if not. Examples of non-tabbed windows are "about" or 413// "preferences". 414- (TabWindowController*)keyWindowTabController { 415 NSWindowController* keyWindowController = 416 [[NSApp keyWindow] windowController]; 417 if ([keyWindowController isKindOfClass:[TabWindowController class]]) 418 return (TabWindowController*)keyWindowController; 419 420 return nil; 421} 422 423// Helper routine to get the window controller if the main window is a tabbed 424// window, or nil if not. Examples of non-tabbed windows are "about" or 425// "preferences". 426- (TabWindowController*)mainWindowTabController { 427 NSWindowController* mainWindowController = 428 [[NSApp mainWindow] windowController]; 429 if ([mainWindowController isKindOfClass:[TabWindowController class]]) 430 return (TabWindowController*)mainWindowController; 431 432 return nil; 433} 434 435// If the window has tabs, make "close window" be cmd-shift-w, otherwise leave 436// it as the normal cmd-w. Capitalization of the key equivalent affects whether 437// the shift modifer is used. 438- (void)adjustCloseWindowMenuItemKeyEquivalent:(BOOL)inHaveTabs { 439 [closeWindowMenuItem_ setKeyEquivalent:(inHaveTabs ? @"W" : @"w")]; 440 [closeWindowMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask]; 441} 442 443// If the window has tabs, make "close tab" take over cmd-w, otherwise it 444// shouldn't have any key-equivalent because it should be disabled. 445- (void)adjustCloseTabMenuItemKeyEquivalent:(BOOL)hasTabs { 446 if (hasTabs) { 447 [closeTabMenuItem_ setKeyEquivalent:@"w"]; 448 [closeTabMenuItem_ setKeyEquivalentModifierMask:NSCommandKeyMask]; 449 } else { 450 [closeTabMenuItem_ setKeyEquivalent:@""]; 451 [closeTabMenuItem_ setKeyEquivalentModifierMask:0]; 452 } 453} 454 455// Explicitly remove any command-key equivalents from the close tab/window 456// menus so that nothing can go haywire if we get a user action during pending 457// updates. 458- (void)clearCloseMenuItemKeyEquivalents { 459 [closeTabMenuItem_ setKeyEquivalent:@""]; 460 [closeTabMenuItem_ setKeyEquivalentModifierMask:0]; 461 [closeWindowMenuItem_ setKeyEquivalent:@""]; 462 [closeWindowMenuItem_ setKeyEquivalentModifierMask:0]; 463} 464 465// See if we have a window with tabs open, and adjust the key equivalents for 466// Close Tab/Close Window accordingly. 467- (void)fixCloseMenuItemKeyEquivalents { 468 fileMenuUpdatePending_ = NO; 469 TabWindowController* tabController = [self keyWindowTabController]; 470 if (!tabController && ![NSApp keyWindow]) { 471 // There might be a small amount of time where there is no key window, 472 // so just use our main browser window if there is one. 473 tabController = [self mainWindowTabController]; 474 } 475 BOOL windowWithMultipleTabs = 476 (tabController && [tabController numberOfTabs] > 1); 477 [self adjustCloseWindowMenuItemKeyEquivalent:windowWithMultipleTabs]; 478 [self adjustCloseTabMenuItemKeyEquivalent:windowWithMultipleTabs]; 479} 480 481// Fix up the "close tab/close window" command-key equivalents. We do this 482// after a delay to ensure that window layer state has been set by the time 483// we do the enabling. This should only be called on the main thread, code that 484// calls this (even as a side-effect) from other threads needs to be fixed. 485- (void)delayedFixCloseMenuItemKeyEquivalents { 486 DCHECK([NSThread isMainThread]); 487 if (!fileMenuUpdatePending_) { 488 // The OS prefers keypresses to timers, so it's possible that a cmd-w 489 // can sneak in before this timer fires. In order to prevent that from 490 // having any bad consequences, just clear the keys combos altogether. They 491 // will be reset when the timer eventually fires. 492 if ([NSThread isMainThread]) { 493 fileMenuUpdatePending_ = YES; 494 [self clearCloseMenuItemKeyEquivalents]; 495 [self performSelector:@selector(fixCloseMenuItemKeyEquivalents) 496 withObject:nil 497 afterDelay:0]; 498 } else { 499 // This shouldn't be happening, but if it does, force it to the main 500 // thread to avoid dropping the update. Don't mess with 501 // |fileMenuUpdatePending_| as it's not expected to be threadsafe and 502 // there could be a race between the selector finishing and setting the 503 // flag. 504 [self 505 performSelectorOnMainThread:@selector(fixCloseMenuItemKeyEquivalents) 506 withObject:nil 507 waitUntilDone:NO]; 508 } 509 } 510} 511 512// Called when we get a notification about the window layering changing to 513// update the UI based on the new main window. 514- (void)windowLayeringDidChange:(NSNotification*)notify { 515 [self delayedFixCloseMenuItemKeyEquivalents]; 516 517 if ([notify name] == NSWindowDidResignKeyNotification) { 518 // If a window is closed, this notification is fired but |[NSApp keyWindow]| 519 // returns nil regardless of whether any suitable candidates for the key 520 // window remain. It seems that the new key window for the app is not set 521 // until after this notification is fired, so a check is performed after the 522 // run loop is allowed to spin. 523 [self performSelector:@selector(checkForAnyKeyWindows) 524 withObject:nil 525 afterDelay:0.0]; 526 } 527} 528 529- (void)checkForAnyKeyWindows { 530 if ([NSApp keyWindow]) 531 return; 532 533 NotificationService::current()->Notify( 534 NotificationType::NO_KEY_WINDOW, 535 NotificationService::AllSources(), 536 NotificationService::NoDetails()); 537} 538 539// Called when the number of tabs changes in one of the browser windows. The 540// object is the tab strip controller, but we don't currently care. 541- (void)tabsChanged:(NSNotification*)notify { 542 // We don't need to do this on a delay, as in the method above, because the 543 // window layering isn't changing. As a result, there's no chance that a 544 // different window will sneak in as the key window and cause the problems 545 // we hacked around above by clearing the key equivalents. 546 [self fixCloseMenuItemKeyEquivalents]; 547} 548 549// If the auto-update interval is not set, make it 5 hours. 550// This code is specific to Mac Chrome Dev Channel. 551// Placed here for 2 reasons: 552// 1) Same spot as other Pref stuff 553// 2) Try and be friendly by keeping this after app launch 554// TODO(jrg): remove once we go Beta. 555- (void)setUpdateCheckInterval { 556#if defined(GOOGLE_CHROME_BUILD) 557 CFStringRef app = (CFStringRef)@"com.google.Keystone.Agent"; 558 CFStringRef checkInterval = (CFStringRef)@"checkInterval"; 559 CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app); 560 if (!plist) { 561 const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0; 562 NSNumber* value = [NSNumber numberWithFloat:fiveHoursInSeconds]; 563 CFPreferencesSetAppValue(checkInterval, value, app); 564 CFPreferencesAppSynchronize(app); 565 } 566#endif 567} 568 569// This is called after profiles have been loaded and preferences registered. 570// It is safe to access the default profile here. 571- (void)applicationDidFinishLaunching:(NSNotification*)notify { 572 // Notify BrowserList to keep the application running so it doesn't go away 573 // when all the browser windows get closed. 574 BrowserList::StartKeepAlive(); 575 576 bookmarkMenuBridge_.reset(new BookmarkMenuBridge([self defaultProfile])); 577 historyMenuBridge_.reset(new HistoryMenuBridge([self defaultProfile])); 578 579 [self setUpdateCheckInterval]; 580 581 // Build up the encoding menu, the order of the items differs based on the 582 // current locale (see http://crbug.com/7647 for details). 583 // We need a valid g_browser_process to get the profile which is why we can't 584 // call this from awakeFromNib. 585 NSMenu* view_menu = [[[NSApp mainMenu] itemWithTag:IDC_VIEW_MENU] submenu]; 586 NSMenuItem* encoding_menu_item = [view_menu itemWithTag:IDC_ENCODING_MENU]; 587 NSMenu* encoding_menu = [encoding_menu_item submenu]; 588 EncodingMenuControllerDelegate::BuildEncodingMenu([self defaultProfile], 589 encoding_menu); 590 591 // Since Chrome is localized to more languages than the OS, tell Cocoa which 592 // menu is the Help so it can add the search item to it. 593 if (helpMenu_ && [NSApp respondsToSelector:@selector(setHelpMenu:)]) 594 [NSApp setHelpMenu:helpMenu_]; 595 596 // Record the path to the (browser) app bundle; this is used by the app mode 597 // shim. 598 RecordLastRunAppBundlePath(); 599 600 // Makes "Services" menu items available. 601 [self registerServicesMenuTypesTo:[notify object]]; 602 603 startupComplete_ = YES; 604 605 // TODO(viettrungluu): This is very temporary, since this should be done "in" 606 // |BrowserMain()|, i.e., this list of startup URLs should be appended to the 607 // (probably-empty) list of URLs from the command line. 608 if (startupUrls_.size()) { 609 [self openUrls:startupUrls_]; 610 [self clearStartupUrls]; 611 } 612} 613 614// This is called after profiles have been loaded and preferences registered. 615// It is safe to access the default profile here. 616- (void)applicationDidBecomeActive:(NSNotification*)notify { 617 NotificationService::current()->Notify(NotificationType::APP_ACTIVATED, 618 NotificationService::AllSources(), 619 NotificationService::NoDetails()); 620} 621 622// Helper function for populating and displaying the in progress downloads at 623// exit alert panel. 624- (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount { 625 NSString* warningText = nil; 626 NSString* explanationText = nil; 627 NSString* waitTitle = nil; 628 NSString* exitTitle = nil; 629 630 string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); 631 632 // Set the dialog text based on whether or not there are multiple downloads. 633 if (downloadCount == 1) { 634 // Dialog text: warning and explanation. 635 warningText = l10n_util::GetNSStringF( 636 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_WARNING, product_name); 637 explanationText = l10n_util::GetNSStringF( 638 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_EXPLANATION, product_name); 639 640 // Cancel download and exit button text. 641 exitTitle = l10n_util::GetNSString( 642 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_OK_BUTTON_LABEL); 643 644 // Wait for download button text. 645 waitTitle = l10n_util::GetNSString( 646 IDS_SINGLE_DOWNLOAD_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL); 647 } else { 648 // Dialog text: warning and explanation. 649 warningText = l10n_util::GetNSStringF( 650 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_WARNING, product_name, 651 base::IntToString16(downloadCount)); 652 explanationText = l10n_util::GetNSStringF( 653 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_EXPLANATION, product_name); 654 655 // Cancel downloads and exit button text. 656 exitTitle = l10n_util::GetNSString( 657 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_OK_BUTTON_LABEL); 658 659 // Wait for downloads button text. 660 waitTitle = l10n_util::GetNSString( 661 IDS_MULTIPLE_DOWNLOADS_REMOVE_CONFIRM_CANCEL_BUTTON_LABEL); 662 } 663 664 // 'waitButton' is the default choice. 665 int choice = NSRunAlertPanel(warningText, explanationText, 666 waitTitle, exitTitle, nil); 667 return choice == NSAlertDefaultReturn ? YES : NO; 668} 669 670// Check all profiles for in progress downloads, and if we find any, prompt the 671// user to see if we should continue to exit (and thus cancel the downloads), or 672// if we should wait. 673- (BOOL)shouldQuitWithInProgressDownloads { 674 ProfileManager* profile_manager = g_browser_process->profile_manager(); 675 if (!profile_manager) 676 return YES; 677 678 ProfileManager::const_iterator it = profile_manager->begin(); 679 for (; it != profile_manager->end(); ++it) { 680 Profile* profile = *it; 681 DownloadManager* download_manager = profile->GetDownloadManager(); 682 if (download_manager && download_manager->in_progress_count() > 0) { 683 int downloadCount = download_manager->in_progress_count(); 684 if ([self userWillWaitForInProgressDownloads:downloadCount]) { 685 // Create a new browser window (if necessary) and navigate to the 686 // downloads page if the user chooses to wait. 687 Browser* browser = BrowserList::FindBrowserWithProfile(profile); 688 if (!browser) { 689 browser = Browser::Create(profile); 690 browser->window()->Show(); 691 } 692 DCHECK(browser); 693 browser->ShowDownloadsTab(); 694 return NO; 695 } 696 697 // User wants to exit. 698 return YES; 699 } 700 } 701 702 // No profiles or active downloads found, okay to exit. 703 return YES; 704} 705 706// Called to determine if we should enable the "restore tab" menu item. 707// Checks with the TabRestoreService to see if there's anything there to 708// restore and returns YES if so. 709- (BOOL)canRestoreTab { 710 TabRestoreService* service = [self defaultProfile]->GetTabRestoreService(); 711 return service && !service->entries().empty(); 712} 713 714// Returns true if there is not a modal window (either window- or application- 715// modal) blocking the active browser. Note that tab modal dialogs (HTTP auth 716// sheets) will not count as blocking the browser. But things like open/save 717// dialogs that are window modal will block the browser. 718- (BOOL)keyWindowIsNotModal { 719 Browser* browser = BrowserList::GetLastActive(); 720 return [NSApp modalWindow] == nil && (!browser || 721 ![[browser->window()->GetNativeHandle() attachedSheet] 722 isKindOfClass:[NSWindow class]]); 723} 724 725// Called to validate menu items when there are no key windows. All the 726// items we care about have been set with the |commandDispatch:| action and 727// a target of FirstResponder in IB. If it's not one of those, let it 728// continue up the responder chain to be handled elsewhere. We pull out the 729// tag as the cross-platform constant to differentiate and dispatch the 730// various commands. 731- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { 732 SEL action = [item action]; 733 BOOL enable = NO; 734 if (action == @selector(commandDispatch:)) { 735 NSInteger tag = [item tag]; 736 if (menuState_->SupportsCommand(tag)) { 737 switch (tag) { 738 // The File Menu commands are not automatically disabled by Cocoa when a 739 // dialog sheet obscures the browser window, so we disable several of 740 // them here. We don't need to include IDC_CLOSE_WINDOW, because 741 // app_controller is only activated when there are no key windows (see 742 // function comment). 743 case IDC_RESTORE_TAB: 744 enable = [self keyWindowIsNotModal] && [self canRestoreTab]; 745 break; 746 // Browser-level items that open in new tabs should not open if there's 747 // a window- or app-modal dialog. 748 case IDC_OPEN_FILE: 749 case IDC_NEW_TAB: 750 case IDC_SHOW_HISTORY: 751 case IDC_SHOW_BOOKMARK_MANAGER: 752 enable = [self keyWindowIsNotModal]; 753 break; 754 // Browser-level items that open in new windows. 755 case IDC_NEW_WINDOW: 756 case IDC_TASK_MANAGER: 757 // Allow the user to open a new window if there's a window-modal 758 // dialog. 759 enable = [self keyWindowIsNotModal] || ([NSApp modalWindow] == nil); 760 break; 761 case IDC_SYNC_BOOKMARKS: { 762 Profile* defaultProfile = [self defaultProfile]; 763 // The profile may be NULL during shutdown -- see 764 // http://code.google.com/p/chromium/issues/detail?id=43048 . 765 // 766 // TODO(akalin,viettrungluu): Figure out whether this method 767 // can be prevented from being called if defaultProfile is 768 // NULL. 769 if (!defaultProfile) { 770 LOG(WARNING) 771 << "NULL defaultProfile detected -- not doing anything"; 772 break; 773 } 774 enable = defaultProfile->IsSyncAccessible() && 775 [self keyWindowIsNotModal]; 776 sync_ui_util::UpdateSyncItem(item, enable, defaultProfile); 777 break; 778 } 779 default: 780 enable = menuState_->IsCommandEnabled(tag) ? 781 [self keyWindowIsNotModal] : NO; 782 } 783 } 784 } else if (action == @selector(terminate:)) { 785 enable = YES; 786 } else if (action == @selector(showPreferences:)) { 787 enable = YES; 788 } else if (action == @selector(orderFrontStandardAboutPanel:)) { 789 enable = YES; 790 } else if (action == @selector(commandFromDock:)) { 791 enable = YES; 792 } 793 return enable; 794} 795 796// Called when the user picks a menu item when there are no key windows, or when 797// there is no foreground browser window. Calls through to the browser object to 798// execute the command. This assumes that the command is supported and doesn't 799// check, otherwise it should have been disabled in the UI in 800// |-validateUserInterfaceItem:|. 801- (void)commandDispatch:(id)sender { 802 Profile* defaultProfile = [self defaultProfile]; 803 804 // Handle the case where we're dispatching a command from a sender that's in a 805 // browser window. This means that the command came from a background window 806 // and is getting here because the foreground window is not a browser window. 807 if ([sender respondsToSelector:@selector(window)]) { 808 id delegate = [[sender window] windowController]; 809 if ([delegate isKindOfClass:[BrowserWindowController class]]) { 810 [delegate commandDispatch:sender]; 811 return; 812 } 813 } 814 815 NSInteger tag = [sender tag]; 816 switch (tag) { 817 case IDC_NEW_TAB: 818 // Create a new tab in an existing browser window (which we activate) if 819 // possible. 820 if (Browser* browser = ActivateBrowser(defaultProfile)) { 821 browser->ExecuteCommand(IDC_NEW_TAB); 822 break; 823 } 824 // Else fall through to create new window. 825 case IDC_NEW_WINDOW: 826 CreateBrowser(defaultProfile); 827 break; 828 case IDC_FOCUS_LOCATION: 829 ActivateOrCreateBrowser(defaultProfile)-> 830 ExecuteCommand(IDC_FOCUS_LOCATION); 831 break; 832 case IDC_FOCUS_SEARCH: 833 ActivateOrCreateBrowser(defaultProfile)->ExecuteCommand(IDC_FOCUS_SEARCH); 834 break; 835 case IDC_NEW_INCOGNITO_WINDOW: 836 Browser::OpenEmptyWindow(defaultProfile->GetOffTheRecordProfile()); 837 break; 838 case IDC_RESTORE_TAB: 839 Browser::OpenWindowWithRestoredTabs(defaultProfile); 840 break; 841 case IDC_OPEN_FILE: 842 CreateBrowser(defaultProfile)->ExecuteCommand(IDC_OPEN_FILE); 843 break; 844 case IDC_CLEAR_BROWSING_DATA: { 845 // There may not be a browser open, so use the default profile. 846 if (CommandLine::ForCurrentProcess()->HasSwitch( 847 switches::kDisableTabbedOptions)) { 848 [ClearBrowsingDataController 849 showClearBrowsingDialogForProfile:defaultProfile]; 850 } else { 851 if (Browser* browser = ActivateBrowser(defaultProfile)) { 852 browser->OpenClearBrowsingDataDialog(); 853 } else { 854 Browser::OpenClearBrowingDataDialogWindow(defaultProfile); 855 } 856 } 857 break; 858 } 859 case IDC_IMPORT_SETTINGS: { 860 if (CommandLine::ForCurrentProcess()->HasSwitch( 861 switches::kDisableTabbedOptions)) { 862 UserMetrics::RecordAction(UserMetricsAction("Import_ShowDlg"), 863 defaultProfile); 864 [ImportSettingsDialogController 865 showImportSettingsDialogForProfile:defaultProfile]; 866 } else { 867 if (Browser* browser = ActivateBrowser(defaultProfile)) { 868 browser->OpenImportSettingsDialog(); 869 } else { 870 Browser::OpenImportSettingsDialogWindow(defaultProfile); 871 } 872 } 873 break; 874 } 875 case IDC_SHOW_BOOKMARK_MANAGER: 876 UserMetrics::RecordAction(UserMetricsAction("ShowBookmarkManager"), 877 defaultProfile); 878 if (Browser* browser = ActivateBrowser(defaultProfile)) { 879 // Open a bookmark manager tab. 880 browser->OpenBookmarkManager(); 881 } else { 882 // No browser window, so create one for the bookmark manager tab. 883 Browser::OpenBookmarkManagerWindow(defaultProfile); 884 } 885 break; 886 case IDC_SHOW_HISTORY: 887 if (Browser* browser = ActivateBrowser(defaultProfile)) 888 browser->ShowHistoryTab(); 889 else 890 Browser::OpenHistoryWindow(defaultProfile); 891 break; 892 case IDC_SHOW_DOWNLOADS: 893 if (Browser* browser = ActivateBrowser(defaultProfile)) 894 browser->ShowDownloadsTab(); 895 else 896 Browser::OpenDownloadsWindow(defaultProfile); 897 break; 898 case IDC_MANAGE_EXTENSIONS: 899 if (Browser* browser = ActivateBrowser(defaultProfile)) 900 browser->ShowExtensionsTab(); 901 else 902 Browser::OpenExtensionsWindow(defaultProfile); 903 break; 904 case IDC_HELP_PAGE: 905 if (Browser* browser = ActivateBrowser(defaultProfile)) 906 browser->OpenHelpTab(); 907 else 908 Browser::OpenHelpWindow(defaultProfile); 909 break; 910 case IDC_FEEDBACK: { 911 Browser* browser = BrowserList::GetLastActive(); 912 TabContents* currentTab = 913 browser ? browser->GetSelectedTabContents() : NULL; 914 BugReportWindowController* controller = 915 [[BugReportWindowController alloc] 916 initWithTabContents:currentTab 917 profile:[self defaultProfile]]; 918 [controller runModalDialog]; 919 break; 920 } 921 case IDC_SYNC_BOOKMARKS: 922 // The profile may be NULL during shutdown -- see 923 // http://code.google.com/p/chromium/issues/detail?id=43048 . 924 // 925 // TODO(akalin,viettrungluu): Figure out whether this method can 926 // be prevented from being called if defaultProfile is NULL. 927 if (!defaultProfile) { 928 LOG(WARNING) << "NULL defaultProfile detected -- not doing anything"; 929 break; 930 } 931 // TODO(akalin): Add a constant to denote starting sync from the 932 // main menu and use that instead of START_FROM_WRENCH. 933 sync_ui_util::OpenSyncMyBookmarksDialog( 934 defaultProfile, ActivateBrowser(defaultProfile), 935 ProfileSyncService::START_FROM_WRENCH); 936 break; 937 case IDC_TASK_MANAGER: 938 UserMetrics::RecordAction(UserMetricsAction("TaskManager"), 939 defaultProfile); 940 TaskManagerMac::Show(false); 941 break; 942 case IDC_OPTIONS: 943 [self showPreferences:sender]; 944 break; 945 default: 946 // Background Applications use dynamic values that must be less than the 947 // smallest value among the predefined IDC_* labels. 948 if ([sender tag] < IDC_MinimumLabelValue) 949 [self executeApplication:sender]; 950 break; 951 } 952} 953 954// Run a (background) application in a new tab. 955- (void)executeApplication:(id)sender { 956 NSInteger tag = [sender tag]; 957 Profile* profile = [self defaultProfile]; 958 DCHECK(profile); 959 BackgroundApplicationListModel applications(profile); 960 DCHECK(tag >= 0 && 961 tag < static_cast<int>(applications.size())); 962 Browser* browser = BrowserList::GetLastActive(); 963 if (!browser) { 964 Browser::OpenEmptyWindow(profile); 965 browser = BrowserList::GetLastActive(); 966 } 967 const Extension* extension = applications.GetExtension(tag); 968 browser->OpenApplicationTab(profile, extension, NULL); 969} 970 971// Same as |-commandDispatch:|, but executes commands using a disposition 972// determined by the key flags. This will get called in the case where the 973// frontmost window is not a browser window, and the user has command-clicked 974// a button in a background browser window whose action is 975// |-commandDispatchUsingKeyModifiers:| 976- (void)commandDispatchUsingKeyModifiers:(id)sender { 977 DCHECK(sender); 978 if ([sender respondsToSelector:@selector(window)]) { 979 id delegate = [[sender window] windowController]; 980 if ([delegate isKindOfClass:[BrowserWindowController class]]) { 981 [delegate commandDispatchUsingKeyModifiers:sender]; 982 } 983 } 984} 985 986// NSApplication delegate method called when someone clicks on the 987// dock icon and there are no open windows. To match standard mac 988// behavior, we should open a new window. 989- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication 990 hasVisibleWindows:(BOOL)flag { 991 // If the browser is currently trying to quit, don't do anything and return NO 992 // to prevent AppKit from doing anything. 993 // TODO(rohitrao): Remove this code when http://crbug.com/40861 is resolved. 994 if (browser_shutdown::IsTryingToQuit()) 995 return NO; 996 997 // Don't do anything if there are visible windows. This will cause 998 // AppKit to unminimize the most recently minimized window. 999 if (flag) 1000 return YES; 1001 1002 // If launched as a hidden login item (due to installation of a persistent app 1003 // or by the user, for example in System Preferenecs->Accounts->Login Items), 1004 // allow session to be restored first time the user clicks on a Dock icon. 1005 // Normally, it'd just open a new empty page. 1006 { 1007 static BOOL doneOnce = NO; 1008 if (!doneOnce) { 1009 doneOnce = YES; 1010 if (base::mac::WasLaunchedAsHiddenLoginItem()) { 1011 SessionService* sessionService = 1012 [self defaultProfile]->GetSessionService(); 1013 if (sessionService && 1014 sessionService->RestoreIfNecessary(std::vector<GURL>())) 1015 return NO; 1016 } 1017 } 1018 } 1019 // Otherwise open a new window. 1020 { 1021 AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true); 1022 Browser::OpenEmptyWindow([self defaultProfile]); 1023 } 1024 1025 // We've handled the reopen event, so return NO to tell AppKit not 1026 // to do anything. 1027 return NO; 1028} 1029 1030- (void)initMenuState { 1031 menuState_.reset(new CommandUpdater(NULL)); 1032 menuState_->UpdateCommandEnabled(IDC_NEW_TAB, true); 1033 menuState_->UpdateCommandEnabled(IDC_NEW_WINDOW, true); 1034 menuState_->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true); 1035 menuState_->UpdateCommandEnabled(IDC_OPEN_FILE, true); 1036 menuState_->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true); 1037 menuState_->UpdateCommandEnabled(IDC_RESTORE_TAB, false); 1038 menuState_->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true); 1039 menuState_->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true); 1040 menuState_->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true); 1041 menuState_->UpdateCommandEnabled(IDC_SHOW_HISTORY, true); 1042 menuState_->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true); 1043 menuState_->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true); 1044 menuState_->UpdateCommandEnabled(IDC_HELP_PAGE, true); 1045 menuState_->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true); 1046 menuState_->UpdateCommandEnabled(IDC_FEEDBACK, true); 1047 menuState_->UpdateCommandEnabled(IDC_SYNC_BOOKMARKS, true); 1048 menuState_->UpdateCommandEnabled(IDC_TASK_MANAGER, true); 1049} 1050 1051- (void)registerServicesMenuTypesTo:(NSApplication*)app { 1052 // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which 1053 // handles requests from services. 1054 NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil]; 1055 [app registerServicesMenuSendTypes:types returnTypes:types]; 1056} 1057 1058- (Profile*)defaultProfile { 1059 // TODO(jrg): Find a better way to get the "default" profile. 1060 if (g_browser_process->profile_manager()) 1061 return *g_browser_process->profile_manager()->begin(); 1062 1063 return NULL; 1064} 1065 1066// Various methods to open URLs that we get in a native fashion. We use 1067// BrowserInit here because on the other platforms, URLs to open come through 1068// the ProcessSingleton, and it calls BrowserInit. It's best to bottleneck the 1069// openings through that for uniform handling. 1070 1071- (void)openUrls:(const std::vector<GURL>&)urls { 1072 // If the browser hasn't started yet, just queue up the URLs. 1073 if (!startupComplete_) { 1074 startupUrls_.insert(startupUrls_.end(), urls.begin(), urls.end()); 1075 return; 1076 } 1077 1078 Browser* browser = BrowserList::GetLastActive(); 1079 // if no browser window exists then create one with no tabs to be filled in 1080 if (!browser) { 1081 browser = Browser::Create([self defaultProfile]); 1082 browser->window()->Show(); 1083 } 1084 1085 CommandLine dummy(CommandLine::NO_PROGRAM); 1086 BrowserInit::LaunchWithProfile launch(FilePath(), dummy); 1087 launch.OpenURLsInBrowser(browser, false, urls); 1088} 1089 1090- (void)getUrl:(NSAppleEventDescriptor*)event 1091 withReply:(NSAppleEventDescriptor*)reply { 1092 NSString* urlStr = [[event paramDescriptorForKeyword:keyDirectObject] 1093 stringValue]; 1094 1095 GURL gurl(base::SysNSStringToUTF8(urlStr)); 1096 std::vector<GURL> gurlVector; 1097 gurlVector.push_back(gurl); 1098 1099 [self openUrls:gurlVector]; 1100} 1101 1102- (void)application:(NSApplication*)sender 1103 openFiles:(NSArray*)filenames { 1104 std::vector<GURL> gurlVector; 1105 for (NSString* file in filenames) { 1106 GURL gurl = net::FilePathToFileURL(FilePath(base::SysNSStringToUTF8(file))); 1107 gurlVector.push_back(gurl); 1108 } 1109 if (!gurlVector.empty()) 1110 [self openUrls:gurlVector]; 1111 else 1112 NOTREACHED() << "Nothing to open!"; 1113 1114 [sender replyToOpenOrPrint:NSApplicationDelegateReplySuccess]; 1115} 1116 1117// Called when the preferences window is closed. We use this to release the 1118// window controller. 1119- (void)prefsWindowClosed:(NSNotification*)notification { 1120 NSWindow* window = [prefsController_ window]; 1121 DCHECK([notification object] == window); 1122 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; 1123 [defaultCenter removeObserver:self 1124 name:NSWindowWillCloseNotification 1125 object:window]; 1126 // PreferencesWindowControllers are autoreleased in 1127 // -[PreferencesWindowController windowWillClose:]. 1128 prefsController_ = nil; 1129} 1130 1131// Show the preferences window, or bring it to the front if it's already 1132// visible. 1133- (IBAction)showPreferences:(id)sender { 1134 const CommandLine& parsed_command_line = *CommandLine::ForCurrentProcess(); 1135 if (!parsed_command_line.HasSwitch(switches::kDisableTabbedOptions)) { 1136 if (Browser* browser = ActivateBrowser([self defaultProfile])) { 1137 // Show options tab in the active browser window. 1138 browser->ShowOptionsTab(chrome::kDefaultOptionsSubPage); 1139 } else { 1140 // No browser window, so create one for the options tab. 1141 Browser::OpenOptionsWindow([self defaultProfile]); 1142 } 1143 } else { 1144 [self showPreferencesWindow:sender 1145 page:OPTIONS_PAGE_DEFAULT 1146 profile:[self defaultProfile]]; 1147 } 1148} 1149 1150- (void)showPreferencesWindow:(id)sender 1151 page:(OptionsPage)page 1152 profile:(Profile*)profile { 1153 if (prefsController_) { 1154 [prefsController_ switchToPage:page animate:YES]; 1155 } else { 1156 prefsController_ = 1157 [[PreferencesWindowController alloc] initWithProfile:profile 1158 initialPage:page]; 1159 // Watch for a notification of when it goes away so that we can destroy 1160 // the controller. 1161 [[NSNotificationCenter defaultCenter] 1162 addObserver:self 1163 selector:@selector(prefsWindowClosed:) 1164 name:NSWindowWillCloseNotification 1165 object:[prefsController_ window]]; 1166 } 1167 [prefsController_ showPreferences:sender]; 1168} 1169 1170// Called when the about window is closed. We use this to release the 1171// window controller. 1172- (void)aboutWindowClosed:(NSNotification*)notification { 1173 NSWindow* window = [aboutController_ window]; 1174 DCHECK(window == [notification object]); 1175 [[NSNotificationCenter defaultCenter] 1176 removeObserver:self 1177 name:NSWindowWillCloseNotification 1178 object:window]; 1179 // AboutWindowControllers are autoreleased in 1180 // -[AboutWindowController windowWillClose:]. 1181 aboutController_ = nil; 1182} 1183 1184- (IBAction)orderFrontStandardAboutPanel:(id)sender { 1185 if (!aboutController_) { 1186 aboutController_ = 1187 [[AboutWindowController alloc] initWithProfile:[self defaultProfile]]; 1188 1189 // Watch for a notification of when it goes away so that we can destroy 1190 // the controller. 1191 [[NSNotificationCenter defaultCenter] 1192 addObserver:self 1193 selector:@selector(aboutWindowClosed:) 1194 name:NSWindowWillCloseNotification 1195 object:[aboutController_ window]]; 1196 } 1197 1198 [aboutController_ showWindow:self]; 1199} 1200 1201// Explicitly bring to the foreground when creating new windows from the dock. 1202- (void)commandFromDock:(id)sender { 1203 [NSApp activateIgnoringOtherApps:YES]; 1204 [self commandDispatch:sender]; 1205} 1206 1207- (NSMenu*)applicationDockMenu:(NSApplication*)sender { 1208 NSMenu* dockMenu = [[[NSMenu alloc] initWithTitle: @""] autorelease]; 1209 Profile* profile = [self defaultProfile]; 1210 1211 NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC); 1212 scoped_nsobject<NSMenuItem> item( 1213 [[NSMenuItem alloc] initWithTitle:titleStr 1214 action:@selector(commandFromDock:) 1215 keyEquivalent:@""]); 1216 [item setTarget:self]; 1217 [item setTag:IDC_NEW_WINDOW]; 1218 [dockMenu addItem:item]; 1219 1220 titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC); 1221 item.reset([[NSMenuItem alloc] initWithTitle:titleStr 1222 action:@selector(commandFromDock:) 1223 keyEquivalent:@""]); 1224 [item setTarget:self]; 1225 [item setTag:IDC_NEW_INCOGNITO_WINDOW]; 1226 [dockMenu addItem:item]; 1227 1228 // TODO(rickcam): Mock out BackgroundApplicationListModel, then add unit 1229 // tests which use the mock in place of the profile-initialized model. 1230 1231 // Avoid breaking unit tests which have no profile. 1232 if (profile) { 1233 BackgroundApplicationListModel applications(profile); 1234 if (applications.size()) { 1235 int position = 0; 1236 NSString* menuStr = 1237 l10n_util::GetNSStringWithFixup(IDS_BACKGROUND_APPS_MAC); 1238 scoped_nsobject<NSMenu> appMenu([[NSMenu alloc] initWithTitle:menuStr]); 1239 for (ExtensionList::const_iterator cursor = applications.begin(); 1240 cursor != applications.end(); 1241 ++cursor, ++position) { 1242 DCHECK(position == applications.GetPosition(*cursor)); 1243 NSString* itemStr = 1244 base::SysUTF16ToNSString(UTF8ToUTF16((*cursor)->name())); 1245 scoped_nsobject<NSMenuItem> appItem([[NSMenuItem alloc] 1246 initWithTitle:itemStr 1247 action:@selector(commandFromDock:) 1248 keyEquivalent:@""]); 1249 [appItem setTarget:self]; 1250 [appItem setTag:position]; 1251 [appMenu addItem:appItem]; 1252 } 1253 scoped_nsobject<NSMenuItem> appMenuItem([[NSMenuItem alloc] 1254 initWithTitle:menuStr 1255 action:@selector(commandFromDock:) 1256 keyEquivalent:@""]); 1257 [appMenuItem setTarget:self]; 1258 [appMenuItem setTag:position]; 1259 [appMenuItem setSubmenu:appMenu]; 1260 [dockMenu addItem:appMenuItem]; 1261 } 1262 } 1263 1264 return dockMenu; 1265} 1266 1267- (const std::vector<GURL>&)startupUrls { 1268 return startupUrls_; 1269} 1270 1271- (void)clearStartupUrls { 1272 startupUrls_.clear(); 1273} 1274 1275@end // @implementation AppController 1276 1277//--------------------------------------------------------------------------- 1278 1279void ShowOptionsWindow(OptionsPage page, 1280 OptionsGroup highlight_group, 1281 Profile* profile) { 1282 // TODO(akalin): Use highlight_group. 1283 AppController* appController = [NSApp delegate]; 1284 [appController showPreferencesWindow:nil page:page profile:profile]; 1285} 1286 1287namespace app_controller_mac { 1288 1289bool IsOpeningNewWindow() { 1290 return g_is_opening_new_window; 1291} 1292 1293} // namespace app_controller_mac 1294