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