1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/app_controller_mac.h"
7 #include <dispatch/dispatch.h>
13 #include "base/apple/bridging.h"
14 #include "base/apple/foundation_util.h"
15 #include "base/auto_reset.h"
16 #include "base/check_op.h"
17 #include "base/command_line.h"
18 #include "base/debug/dump_without_crashing.h"
19 #include "base/files/file_path.h"
20 #include "base/functional/bind.h"
21 #include "base/mac/mac_util.h"
22 #include "base/memory/raw_ptr.h"
23 #include "base/metrics/histogram_macros.h"
24 #include "base/run_loop.h"
25 #include "base/scoped_multi_source_observation.h"
26 #include "base/scoped_observation.h"
27 #include "base/strings/string_number_conversions.h"
28 #include "base/strings/sys_string_conversions.h"
29 #include "base/strings/utf_string_conversions.h"
30 #include "base/task/thread_pool.h"
31 #include "base/threading/scoped_blocking_call.h"
32 #include "base/threading/thread_restrictions.h"
33 #include "build/branding_buildflags.h"
34 #include "chrome/app/chrome_command_ids.h"
35 #include "chrome/app/notification_metrics.h"
36 #include "chrome/browser/apps/app_shim/app_shim_termination_manager.h"
37 #include "chrome/browser/apps/platform_apps/app_window_registry_util.h"
38 #include "chrome/browser/browser_features.h"
39 #include "chrome/browser/browser_process.h"
40 #include "chrome/browser/browser_process_platform_part.h"
41 #include "chrome/browser/command_updater_impl.h"
42 #include "chrome/browser/download/download_core_service.h"
43 #include "chrome/browser/download/download_core_service_factory.h"
44 #include "chrome/browser/extensions/extension_service.h"
45 #include "chrome/browser/first_run/first_run.h"
46 #include "chrome/browser/lifetime/application_lifetime_desktop.h"
47 #include "chrome/browser/lifetime/browser_shutdown.h"
48 #include "chrome/browser/mac/auth_session_request.h"
49 #include "chrome/browser/mac/key_window_notifier.h"
50 #include "chrome/browser/mac/mac_startup_profiler.h"
51 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
52 #include "chrome/browser/prefs/incognito_mode_prefs.h"
53 #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
54 #include "chrome/browser/profiles/profile.h"
55 #include "chrome/browser/profiles/profile_attributes_entry.h"
56 #include "chrome/browser/profiles/profile_attributes_storage.h"
57 #include "chrome/browser/profiles/profile_manager.h"
58 #include "chrome/browser/profiles/profile_manager_observer.h"
59 #include "chrome/browser/profiles/profile_observer.h"
60 #include "chrome/browser/profiles/profiles_state.h"
61 #include "chrome/browser/sessions/session_restore.h"
62 #include "chrome/browser/sessions/session_service.h"
63 #include "chrome/browser/sessions/session_service_factory.h"
64 #include "chrome/browser/sessions/tab_restore_service_factory.h"
65 #include "chrome/browser/ui/browser.h"
66 #include "chrome/browser/ui/browser_command_controller.h"
67 #include "chrome/browser/ui/browser_commands.h"
68 #include "chrome/browser/ui/browser_dialogs.h"
69 #include "chrome/browser/ui/browser_finder.h"
70 #include "chrome/browser/ui/browser_list.h"
71 #include "chrome/browser/ui/browser_live_tab_context.h"
72 #include "chrome/browser/ui/browser_mac.h"
73 #include "chrome/browser/ui/browser_window.h"
74 #include "chrome/browser/ui/chrome_pages.h"
75 #import "chrome/browser/ui/cocoa/apps/app_shim_menu_controller_mac.h"
76 #include "chrome/browser/ui/cocoa/apps/quit_with_apps_controller_mac.h"
77 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_menu_bridge.h"
78 #import "chrome/browser/ui/cocoa/confirm_quit.h"
79 #import "chrome/browser/ui/cocoa/confirm_quit_panel_controller.h"
80 #include "chrome/browser/ui/cocoa/handoff_observer.h"
81 #import "chrome/browser/ui/cocoa/history_menu_bridge.h"
82 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
83 #import "chrome/browser/ui/cocoa/profiles/profile_menu_controller.h"
84 #import "chrome/browser/ui/cocoa/share_menu_controller.h"
85 #import "chrome/browser/ui/cocoa/tab_menu_bridge.h"
86 #include "chrome/browser/ui/extensions/application_launch.h"
87 #include "chrome/browser/ui/profiles/profile_picker.h"
88 #include "chrome/browser/ui/startup/first_run_service.h"
89 #include "chrome/browser/ui/startup/launch_mode_recorder.h"
90 #include "chrome/browser/ui/startup/startup_browser_creator.h"
91 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
92 #include "chrome/browser/ui/startup/startup_tab.h"
93 #include "chrome/browser/ui/startup/startup_types.h"
94 #include "chrome/browser/ui/tabs/tab_enums.h"
95 #include "chrome/browser/ui/tabs/tab_strip_model.h"
96 #include "chrome/browser/web_applications/os_integration/web_app_shortcut_mac.h"
97 #include "chrome/browser/web_applications/web_app_helpers.h"
98 #include "chrome/common/chrome_features.h"
99 #include "chrome/common/chrome_paths_internal.h"
100 #include "chrome/common/chrome_switches.h"
101 #include "chrome/common/extensions/extension_constants.h"
102 #include "chrome/common/mac/app_mode_common.h"
103 #include "chrome/common/pref_names.h"
104 #include "chrome/common/url_constants.h"
105 #include "chrome/common/webui_url_constants.h"
106 #include "chrome/grit/branded_strings.h"
107 #include "chrome/grit/generated_resources.h"
108 #include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
109 #include "components/handoff/handoff_manager.h"
110 #include "components/handoff/handoff_utility.h"
111 #include "components/keep_alive_registry/keep_alive_types.h"
112 #include "components/keep_alive_registry/scoped_keep_alive.h"
113 #include "components/policy/core/common/policy_pref_names.h"
114 #include "components/prefs/pref_service.h"
115 #include "components/sessions/core/tab_restore_service.h"
116 #include "content/public/browser/download_manager.h"
117 #include "extensions/browser/extension_registry.h"
118 #include "extensions/browser/extension_system.h"
119 #include "net/base/filename_util.h"
120 #include "net/base/mac/url_conversions.h"
121 #import "ui/base/cocoa/nsmenu_additions.h"
122 #import "ui/base/cocoa/nsmenuitem_additions.h"
123 #include "ui/base/l10n/l10n_util.h"
124 #include "ui/base/l10n/l10n_util_mac.h"
125 #include "ui/color/color_provider.h"
126 #include "ui/color/color_provider_manager.h"
127 #include "ui/native_theme/native_theme_mac.h"
128 #include "ui/native_theme/native_theme_observer.h"
129 #include "url/gurl.h"
133 // True while AppController is calling chrome::NewEmptyWindow(). We need a
134 // global flag here, analogue to StartupBrowserCreator::InProcessStartup()
135 // because otherwise the SessionService will try to restore sessions when we
136 // make a new window while there are no other active windows.
137 bool g_is_opening_new_window = false;
139 // Stores the pending web auth requests (typically while the profile is being
140 // loaded) until they are passed to the AuthSessionRequest class.
141 NSMutableDictionary<NSUUID*, ASWebAuthenticationSessionRequest*>*
142 GetPendingWebAuthRequests() {
143 static NSMutableDictionary* g_pending_requests =
144 [[NSMutableDictionary alloc] init];
145 return g_pending_requests;
148 // Open the urls in the last used browser from a regular profile.
149 void OpenUrlsInBrowserWithProfile(const std::vector<GURL>& urls,
152 // Returns true if the profile requires signin before being used.
153 bool IsProfileSignedOut(const base::FilePath& profile_path);
155 // Starts a web authentication session request.
156 void BeginHandlingWebAuthenticationSessionRequestWithProfile(
157 ASWebAuthenticationSessionRequest* request,
159 NSUUID* key = request.UUID;
160 if (![GetPendingWebAuthRequests() objectForKey:key])
161 return; // The request has been canceled, do not start the session.
163 [GetPendingWebAuthRequests() removeObjectForKey:key];
165 // If there is no safe profile, |profile| is nullptr, and the session will
167 AuthSessionRequest::StartNewAuthSession(request, profile);
170 // Activates a browser window having the given profile (the last one active) if
171 // possible and returns a pointer to the activate |Browser| or NULL if this was
172 // not possible. If the last active browser is minimized (in particular, if
173 // there are only minimized windows), it will unminimize it.
174 Browser* ActivateBrowser(Profile* profile) {
175 Browser* browser = chrome::FindLastActiveWithProfile(
176 profile->IsGuestSession()
177 ? profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)
181 browser = browser->GetBrowserForOpeningWebUi();
185 browser->window()->Activate();
189 // Launches a browser window associated with |profile|. Checks if we are in the
190 // first run of Chrome to decide if we need to launch a browser or not.
191 // The profile can be `nullptr` and in that case the last-used profile will be
193 void LaunchBrowserStartup(Profile* profile) {
194 if (StartupProfileModeFromReason(ProfilePicker::GetStartupModeReason()) ==
195 StartupProfileMode::kProfilePicker) {
196 ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
197 ProfilePicker::EntryPoint::kNewSessionOnExistingProcess));
202 base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
203 StartupBrowserCreator browser_creator;
204 browser_creator.LaunchBrowser(*base::CommandLine::ForCurrentProcess(),
205 profile, base::FilePath(),
206 chrome::startup::IsProcessStartup::kNo,
207 chrome::startup::IsFirstRun::kYes, nullptr);
210 // Creates an empty browser window with the given profile and returns a pointer
211 // to the new |Browser|.
212 Browser* CreateBrowser(Profile* profile) {
213 // Closes the first run if we open a new window.
214 if (auto* fre_service =
215 FirstRunServiceFactory::GetForBrowserContextIfExists(profile)) {
216 fre_service->FinishFirstRunWithoutResumeTask();
220 base::AutoReset<bool> auto_reset_in_run(&g_is_opening_new_window, true);
221 chrome::NewEmptyWindow(profile);
224 Browser* browser = chrome::GetLastActiveBrowser();
229 // Activates a browser window having the given profile (the last one active) if
230 // possible or creates an empty one if necessary. Returns a pointer to the
231 // activated/new |Browser|.
232 Browser* ActivateOrCreateBrowser(Profile* profile) {
233 if (Browser* browser = ActivateBrowser(profile))
235 return CreateBrowser(profile);
238 // Attempts restoring a previous session if there is one. Otherwise, opens
239 // either the profile picker or a new browser, depending on user preferences.
240 void AttemptSessionRestore(Profile* profile) {
241 DCHECK(!profile->IsGuestSession());
242 DCHECK(!IsProfileSignedOut(profile->GetPath()));
243 SessionService* sessionService =
244 SessionServiceFactory::GetForProfileForSessionRestore(profile);
245 if (sessionService &&
246 sessionService->RestoreIfNecessary(StartupTabs(),
247 /*restore_apps=*/false)) {
248 // Session was restored.
251 // No session to restore, proceed with normal startup.
252 LaunchBrowserStartup(profile);
255 // Record the location of the application bundle (containing the main framework)
256 // from which Chromium was loaded. This is used by app mode shims to find
258 void RecordLastRunAppBundlePath() {
259 // Going up three levels from |chrome::GetVersionedDirectory()| gives the
260 // real, user-visible app bundle directory. (The alternatives give either the
261 // framework's path or the initial app's path, which may be an app mode shim
263 base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
264 base::BlockingType::MAY_BLOCK);
266 // Go up five levels from the versioned sub-directory of the framework, which
267 // is at C.app/Contents/Frameworks/C.framework/Versions/V.
268 base::FilePath app_bundle_path = chrome::GetFrameworkBundlePath()
274 base::apple::ScopedCFTypeRef<CFStringRef> app_bundle_path_cfstring =
275 base::SysUTF8ToCFStringRef(app_bundle_path.value());
276 CFPreferencesSetAppValue(
277 base::apple::NSToCFPtrCast(app_mode::kLastRunAppBundlePathPrefsKey),
278 app_bundle_path_cfstring,
279 base::SysUTF8ToCFStringRef(base::apple::BaseBundleID()));
282 bool IsProfileSignedOut(const base::FilePath& profile_path) {
283 ProfileAttributesEntry* entry =
284 g_browser_process->profile_manager()
285 ->GetProfileAttributesStorage()
286 .GetProfileAttributesWithPath(profile_path);
287 return entry && entry->IsSigninRequired();
290 void ConfigureNSAppForKioskMode() {
291 NSApp.presentationOptions =
292 NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar |
293 NSApplicationPresentationDisableProcessSwitching |
294 NSApplicationPresentationDisableSessionTermination |
295 NSApplicationPresentationDisableForceQuit |
296 NSApplicationPresentationFullScreen;
299 // Returns the list of gfx::NativeWindows for all browser windows (excluding
301 std::set<gfx::NativeWindow> GetBrowserNativeWindows() {
302 std::set<gfx::NativeWindow> result;
303 for (auto* browser : *BrowserList::GetInstance()) {
306 // When focusing Chrome, don't focus any browser windows associated with
308 // https://crbug.com/960904
309 if (browser->is_type_app())
311 result.insert(browser->window()->GetNativeWindow());
316 void FocusWindowSetOnCurrentSpace(const std::set<gfx::NativeWindow>& windows) {
317 // This callback runs before AppKit picks its own window to
318 // deminiaturize, so we get to pick one from the right set. Limit to
319 // the windows on the current workspace. Otherwise we jump spaces
322 // Also consider both visible and hidden windows; this call races
323 // with the system unhiding the application. http://crbug.com/368238
325 // NOTE: If this is called in the
326 // applicationShouldHandleReopen:hasVisibleWindows: hook when
327 // clicking the dock icon, and that caused macOS to begin switch
328 // spaces, isOnActiveSpace gives the answer for the PREVIOUS
329 // space. This means that we actually raise and focus the wrong
330 // space's windows, leaving the new key window off-screen. To detect
331 // this, check if the key window is on the active space prior to
334 // Also, if we decide to deminiaturize a window during a space switch,
335 // that can switch spaces and then switch back. Fortunately, this only
336 // happens if, say, space 1 contains an app, space 2 contains a
337 // miniaturized browser. We click the icon, macOS switches to space 1,
338 // we deminiaturize the browser, and that triggers switching back.
340 // TODO(davidben): To limit those cases, consider preferentially
341 // deminiaturizing a window on the current space.
342 NSWindow* frontmost_window = nil;
343 NSWindow* frontmost_miniaturized_window = nil;
344 bool all_miniaturized = true;
345 for (NSWindow* win in [[NSApp orderedWindows] reverseObjectEnumerator]) {
346 if (windows.find(win) == windows.end())
348 if ([win isMiniaturized]) {
349 frontmost_miniaturized_window = win;
350 } else if ([win isVisible]) {
351 all_miniaturized = false;
352 if ([win isOnActiveSpace]) {
353 // Raise the old |frontmost_window| (if any). The topmost |win| will be
354 // raised with makeKeyAndOrderFront: below.
355 [frontmost_window orderFront:nil];
356 frontmost_window = win;
360 if (all_miniaturized && frontmost_miniaturized_window) {
361 DCHECK(!frontmost_window);
362 // Note the call to makeKeyAndOrderFront: will deminiaturize the window.
363 frontmost_window = frontmost_miniaturized_window;
366 if (frontmost_window) {
367 [frontmost_window makeKeyAndOrderFront:nil];
368 [NSApp activateIgnoringOtherApps:YES];
372 // Returns the profile path to be used at startup.
373 base::FilePath GetStartupProfilePathMac() {
374 // This profile path is used to open URLs passed in application:openURLs: and
375 // should not default to Guest when the profile picker is shown.
376 // TODO(https://crbug.com/1155158): Remove the ignore_profile_picker parameter
377 // once the picker supports opening URLs.
378 StartupProfilePathInfo profile_path_info = GetStartupProfilePath(
379 /*cur_dir=*/base::FilePath(), *base::CommandLine::ForCurrentProcess(),
380 /*ignore_profile_picker=*/true);
381 DCHECK_EQ(StartupProfileModeFromReason(profile_path_info.reason),
382 StartupProfileMode::kBrowserWindow);
383 return profile_path_info.path;
386 // Open the urls in the last used browser. Loads the profile asynchronously if
388 void OpenUrlsInBrowser(const std::vector<GURL>& urls) {
389 app_controller_mac::RunInLastProfileSafely(
390 base::BindOnce(&OpenUrlsInBrowserWithProfile, urls),
391 app_controller_mac::kShowProfilePickerOnFailure);
396 // This is extracted as a standalone function in order
397 // to be friend with base::ScopedAllowBlocking.
398 Profile* GetLastProfileMac() {
399 ProfileManager* profile_manager = g_browser_process->profile_manager();
400 if (!profile_manager)
403 base::FilePath profile_path = GetStartupProfilePathMac();
404 // ProfileManager::GetProfile() is blocking if the profile was not loaded yet.
405 // TODO(https://crbug.com/1176734): Change this code to return nullptr when
406 // the profile is not loaded, and update all callers to handle this case.
407 base::ScopedAllowBlocking allow_blocking;
408 return profile_manager->GetProfile(profile_path);
411 @interface AppController () <HandoffObserverDelegate>
412 - (void)initMenuState;
413 - (void)initProfileMenu;
414 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item;
415 - (void)registerServicesMenuTypesTo:(NSApplication*)app;
416 - (void)checkForAnyKeyWindows;
417 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount;
418 - (BOOL)shouldQuitWithInProgressDownloads;
419 - (void)profileWasRemoved:(const base::FilePath&)profilePath
420 forIncognito:(bool)isIncognito;
422 // This class cannot open urls until startup has finished. The urls that cannot
423 // be opened are cached in |startupUrls_|. This method must be called exactly
424 // once after startup has completed. It opens the urls in |startupUrls_|, and
425 // clears |startupUrls_|.
426 - (void)openStartupUrls;
428 // Opens a tab for each GURL in |urls|. If there is exactly one tab open before
429 // this method is called, and that tab is the NTP, then this method closes the
430 // NTP after all the |urls| have been opened.
431 - (void)openUrlsReplacingNTP:(const std::vector<GURL>&)urls;
433 // Returns |YES| if |webContents| can be sent to another device via Handoff.
434 - (BOOL)isHandoffEligible:(content::WebContents*)webContents;
436 // This method passes |handoffURL| and |handoffTitle| to |handoffManager_|.
437 // This is a separate method (vs. being inlined into `updateHandoffManager`
438 // below) so that it can be swizzled in tests.
439 - (void)updateHandoffManagerWithURL:(const GURL&)handoffURL
440 title:(const std::u16string&)handoffTitle;
442 // Lazily creates the Handoff Manager. Updates the state of the Handoff
443 // Manager. This method is idempotent. This should be called:
444 // - During initialization.
445 // - When the current tab navigates to a new URL.
446 // - When the active browser changes.
447 // - When the active browser's active tab switches.
448 // |webContents| should be the new, active WebContents.
449 - (void)updateHandoffManager:(content::WebContents*)webContents;
451 // Return false if Chrome startup is paused by dialog and AppController is
452 // called without any initialized Profile.
453 - (BOOL)isProfileReady;
456 class AppControllerProfileObserver : public ProfileAttributesStorage::Observer,
457 public ProfileManagerObserver,
458 public ProfileObserver {
460 AppControllerProfileObserver(
461 ProfileManager* profile_manager, AppController* app_controller)
462 : profile_manager_(profile_manager),
463 app_controller_(app_controller) {
464 DCHECK(profile_manager_);
465 DCHECK(app_controller_);
466 // Listen to ProfileObserver and ProfileManagerObserver.
467 profile_manager_observer_.Observe(profile_manager_.get());
468 for (Profile* profile : profile_manager_->GetLoadedProfiles()) {
469 profile_observers_.AddObservation(profile);
470 Profile* otr_profile =
471 profile->GetPrimaryOTRProfile(/*create_if_needed=*/false);
473 profile_observers_.AddObservation(otr_profile);
476 storage_observer_.Observe(&profile_manager_->GetProfileAttributesStorage());
479 AppControllerProfileObserver(const AppControllerProfileObserver&) = delete;
480 AppControllerProfileObserver& operator=(const AppControllerProfileObserver&) =
483 ~AppControllerProfileObserver() override = default;
486 // ProfileAttributesStorage::Observer implementation:
488 // `ProfileAttributesStorage::Observer::OnProfileAdded()` must be explicitly
489 // defined even if it's empty, because of the competing overload
490 // `ProfileManager::Observer::OnProfileAdded()`.
491 void OnProfileAdded(const base::FilePath& profile_path) override {}
493 void OnProfileWasRemoved(const base::FilePath& profile_path,
494 const std::u16string& profile_name) override {
495 // When a profile is deleted we need to notify the AppController,
496 // so it can correctly update its pointer to the last used profile.
497 [app_controller_ profileWasRemoved:profile_path forIncognito:false];
500 // ProfileManager::Observer implementation:
501 void OnProfileAdded(Profile* profile) override {
502 profile_observers_.AddObservation(profile);
505 // ProfileObserver implementation:
506 void OnProfileWillBeDestroyed(Profile* profile) override {
507 profile_observers_.RemoveObservation(profile);
509 bool is_profile_observed =
510 profile->IsOffTheRecord() || ObserveRegularProfiles();
512 // If the profile is not observed, then no need to call rest.
513 if (!is_profile_observed)
516 [app_controller_ profileWasRemoved:profile->GetPath()
517 forIncognito:profile->IsOffTheRecord()];
520 void OnOffTheRecordProfileCreated(Profile* off_the_record) override {
521 profile_observers_.AddObservation(off_the_record);
524 static bool ObserveRegularProfiles() {
525 return base::FeatureList::IsEnabled(
526 features::kDestroyProfileOnBrowserClose);
529 base::ScopedMultiSourceObservation<Profile, ProfileObserver>
530 profile_observers_{this};
531 base::ScopedObservation<ProfileAttributesStorage,
532 ProfileAttributesStorage::Observer>
533 storage_observer_{this};
534 base::ScopedObservation<ProfileManager, ProfileManagerObserver>
535 profile_manager_observer_{this};
537 const raw_ptr<ProfileManager> profile_manager_;
538 AppController* const app_controller_; // Weak; owns us.
541 class AppControllerNativeThemeObserver : public ui::NativeThemeObserver {
543 explicit AppControllerNativeThemeObserver(AppController* app_controller)
544 : app_controller_(app_controller) {
545 native_theme_observation_.Observe(
546 ui::NativeThemeMac::GetInstanceForNativeUi());
549 // NativeThemeObserver:
550 void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override {
551 [app_controller_ nativeThemeDidChange];
555 base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
556 native_theme_observation_{this};
557 AppController* const app_controller_; // Weak; owns us.
560 @implementation AppController {
561 // Manages the state of the command menu items.
562 std::unique_ptr<CommandUpdater> _menuState;
564 // The profile last used by a Browser. It is this profile that was used to
565 // build the user-data specific main menu items.
566 raw_ptr<Profile, DanglingUntriaged> _lastProfile;
568 // The ProfileObserver observes the ProfileAttributesStorage and gets notified
569 // when a profile has been deleted.
570 std::unique_ptr<AppControllerProfileObserver>
571 _profileAttributesStorageObserver;
573 // The NativeThemeObserver observes system-wide theme related settings
575 std::unique_ptr<AppControllerNativeThemeObserver> _nativeThemeObserver;
577 // Management of the bookmark menu which spans across all windows
578 // (and Browser*s). |profileBookmarkMenuBridgeMap_| is a cache that owns one
579 // pointer to a BookmarkMenuBridge for each profile. |bookmarkMenuBridge_| is
580 // a weak pointer that is updated to match the corresponding cache entry
581 // during a profile switch.
582 raw_ptr<BookmarkMenuBridge, DanglingUntriaged> _bookmarkMenuBridge;
583 std::map<base::FilePath, std::unique_ptr<BookmarkMenuBridge>>
584 _profileBookmarkMenuBridgeMap;
586 std::unique_ptr<HistoryMenuBridge> _historyMenuBridge;
588 // Controller that manages main menu items for packaged apps.
589 AppShimMenuController* __strong _appShimMenuController;
591 // The profile menu, which appears right before the Help menu. It is only
592 // available when multiple profiles is enabled.
593 ProfileMenuController* __strong _profileMenuController;
595 // Controller for the macOS system share menu.
596 ShareMenuController* __strong _shareMenuController;
598 std::unique_ptr<TabMenuBridge> _tabMenuBridge;
600 // If we're told to open URLs (in particular, via |-application:openURLs:| by
601 // Launch Services) before we've launched the browser, we queue them up in
602 // |startupUrls_| so that they can go in the first browser window/tab.
603 std::vector<GURL> _startupUrls;
604 BOOL _startupComplete;
606 // Outlets for the close tab/window menu items so that we can adjust the
607 // command-key equivalent depending on the kind of window and how many
609 NSMenuItem* __strong _closeTabMenuItemForTesting;
610 NSMenuItem* __strong _closeWindowMenuItemForTesting;
612 std::unique_ptr<PrefChangeRegistrar> _profilePrefRegistrar;
613 PrefChangeRegistrar _localPrefRegistrar;
615 // Displays a notification when quitting while apps are running.
616 scoped_refptr<QuitWithAppsController> _quitWithAppsController;
618 // Responsible for maintaining all state related to the Handoff feature.
619 HandoffManager* __strong _handoffManager;
621 // Observes changes to the active web contents.
622 std::unique_ptr<HandoffObserver> _handoffObserver;
624 // This will be true after receiving a NSWorkspaceWillPowerOffNotification.
627 // This will be true after receiving a |-applicationWillTerminate:| event.
628 BOOL _isShuttingDown;
630 // Request to keep the browser alive during that object's lifetime.
631 std::unique_ptr<ScopedKeepAlive> _keepAlive;
633 // Remembers whether _lastProfile had TabRestoreService entries. This is saved
634 // when _lastProfile is destroyed and Chromium enters the zero-profile state.
636 // By remembering this bit, Chromium knows whether to enable or disable
637 // Cmd+Shift+T and the related "File > Reopen Closed Tab" entry.
638 BOOL _tabRestoreWasEnabled;
640 // The color provider associated with the last active browser view.
641 raw_ptr<const ui::ColorProvider, DanglingUntriaged> _lastActiveColorProvider;
644 @synthesize startupComplete = _startupComplete;
646 + (AppController*)sharedController {
647 static AppController* sharedController = [] {
648 AppController* sharedController = [[AppController alloc] init];
649 NSApp.delegate = sharedController;
650 return sharedController;
653 CHECK_NE(nil, sharedController);
654 CHECK_EQ(NSApp.delegate, sharedController);
655 return sharedController;
658 - (instancetype)init {
659 if (self = [super init]) {
660 // -[NSMenu cr_menuItemForKeyEquivalentEvent:] lives in /content, but
661 // we need to execute special update code before the search begins.
662 // Setting this block gives us the hook we need.
663 [NSMenu cr_setMenuItemForKeyEquivalentEventPreSearchBlock:^{
664 // We avoid calling -[NSMenuDelegate menuNeedsUpdate:] on each submenu's
665 // delegate as that can be slow. Instead, we update the relevant
667 [AppController.sharedController updateMenuItemKeyEquivalents];
677 - (NSMenu*)fileMenu {
678 return [[NSApp.mainMenu itemWithTag:IDC_FILE_MENU] submenu];
681 - (NSMenuItem*)closeTabMenuItem {
682 if (_closeTabMenuItemForTesting != nil) {
683 return _closeTabMenuItemForTesting;
686 return [[self fileMenu] itemWithTag:IDC_CLOSE_TAB];
689 - (NSMenuItem*)closeWindowMenuItem {
690 if (_closeWindowMenuItemForTesting != nil) {
691 return _closeWindowMenuItemForTesting;
694 return [[self fileMenu] itemWithTag:IDC_CLOSE_WINDOW];
697 // This method is called very early in application startup (ie, before
698 // the profile is loaded or any preferences have been registered). Defer any
699 // user-data initialization until -applicationDidFinishLaunching:.
700 - (void)mainMenuCreated {
701 MacStartupProfiler::GetInstance()->Profile(
702 MacStartupProfiler::AWAKE_FROM_NIB);
703 NSNotificationCenter* notificationCenter = NSNotificationCenter.defaultCenter;
706 selector:@selector(windowDidResignKey:)
707 name:NSWindowDidResignKeyNotification
711 selector:@selector(windowDidBecomeMain:)
712 name:NSWindowDidBecomeMainNotification
715 [NSWorkspace.sharedWorkspace.notificationCenter
717 selector:@selector(willPowerOff:)
718 name:NSWorkspaceWillPowerOffNotification
721 DCHECK([self closeTabMenuItem]);
722 DCHECK([self closeWindowMenuItem]);
724 // Set up the command updater for when there are no windows open
725 [self initMenuState];
727 // Initialize the Profile menu.
728 [self initProfileMenu];
731 - (void)unregisterEventHandlers {
732 [NSNotificationCenter.defaultCenter removeObserver:self];
733 [NSWorkspace.sharedWorkspace.notificationCenter removeObserver:self];
736 // (NSApplicationDelegate protocol) This is the Apple-approved place to override
737 // the default handlers.
738 - (void)applicationWillFinishLaunching:(NSNotification*)notification {
739 MacStartupProfiler::GetInstance()->Profile(
740 MacStartupProfiler::WILL_FINISH_LAUNCHING);
742 NSWindow.allowsAutomaticWindowTabbing = NO;
744 [self initShareMenu];
747 - (BOOL)tryToTerminateApplication:(NSApplication*)app {
748 // Reset this now that we've received the call to terminate.
749 BOOL isPoweringOff = _isPoweringOff;
752 // Stop the browser from re-opening when we close Chrome while
753 // in the first run experience.
754 if (auto* profile = [self lastProfileIfLoaded]) {
755 if (auto* fre_service =
756 FirstRunServiceFactory::GetForBrowserContextIfExists(profile)) {
757 fre_service->FinishFirstRunWithoutResumeTask();
761 // Check for in-process downloads, and prompt the user if they really want
762 // to quit (and thus cancel downloads). Only check if we're not already
763 // shutting down, else the user might be prompted multiple times if the
764 // download isn't stopped before terminate is called again.
765 if (!browser_shutdown::IsTryingToQuit() &&
766 ![self shouldQuitWithInProgressDownloads])
769 // TODO(viettrungluu): Remove Apple Event handlers here? (It's safe to leave
770 // them in, but I'm not sure about UX; we'd also want to disable other things
771 // though.) http://crbug.com/40861
773 // Check for active apps. If quitting is prevented, only close browsers and
775 if (!browser_shutdown::IsTryingToQuit() && !isPoweringOff &&
776 _quitWithAppsController.get() && !_quitWithAppsController->ShouldQuit()) {
778 chrome::OnClosingAllBrowsers(true);
779 // This will close all browser sessions.
780 chrome::CloseAllBrowsers();
782 // At this point, the user has already chosen to cancel downloads. If we
783 // were to shut down as usual, the downloads would be cancelled in
784 // DownloadCoreService::Shutdown().
785 DownloadCoreService::CancelAllDownloads();
790 size_t num_browsers = chrome::GetTotalBrowserCount();
792 // Initiate a shutdown (via chrome::CloseAllBrowsersAndQuit()) if we aren't
793 // already shutting down.
794 if (!browser_shutdown::IsTryingToQuit()) {
795 chrome::OnClosingAllBrowsers(true);
796 chrome::CloseAllBrowsersAndQuit();
799 return num_browsers == 0 ? YES : NO;
802 - (void)stopTryingToTerminateApplication:(NSApplication*)app {
803 if (browser_shutdown::IsTryingToQuit()) {
804 // Reset the "trying to quit" state, so that closing all browser windows
805 // will no longer lead to termination.
806 browser_shutdown::SetTryingToQuit(false);
808 // TODO(viettrungluu): Were we to remove Apple Event handlers above, we
809 // would have to reinstall them here. http://crbug.com/40861
813 - (BOOL)runConfirmQuitPanel {
814 // If there are no windows, quit immediately.
815 if (BrowserList::GetInstance()->empty() &&
816 !AppWindowRegistryUtil::IsAppWindowVisibleInAnyProfile(0)) {
820 // Check if the preference is turned on.
821 const PrefService* prefs = g_browser_process->local_state();
822 if (!prefs->GetBoolean(prefs::kConfirmToQuitEnabled)) {
823 confirm_quit::RecordHistogram(confirm_quit::kNoConfirm);
827 // Run only for keyboard-initiated quits.
828 if ([[NSApp currentEvent] type] != NSEventTypeKeyDown)
829 return NSTerminateNow;
831 return [[ConfirmQuitPanelController sharedController]
832 runModalLoopForApplication:NSApp];
835 // Called when the app is shutting down. Clean-up as appropriate.
836 - (void)applicationWillTerminate:(NSNotification*)aNotification {
837 // There better be no browser windows left at this point.
838 CHECK_EQ(0u, chrome::GetTotalBrowserCount());
840 // Tell BrowserList not to keep the browser process alive. Once all the
841 // browsers get dealloc'd, it will stop the RunLoop and fall back into main().
844 // Reset local state watching, as this object outlives the prefs system.
845 _localPrefRegistrar.RemoveAll();
847 _isShuttingDown = true;
849 // It's safe to delete |_lastProfile| now.
850 [self setLastProfile:nullptr];
852 _profileAttributesStorageObserver.reset();
853 [self unregisterEventHandlers];
854 _appShimMenuController = nil;
855 _profileBookmarkMenuBridgeMap.clear();
858 - (void)didEndMainMessageLoop {
860 // If only the profile picker is open and closed again, there is no profile
861 // loaded when main message loop ends and we cannot load it from disk now.
864 DCHECK_EQ(0u, chrome::GetBrowserCount(_lastProfile));
865 if (!chrome::GetBrowserCount(_lastProfile)) {
866 // As we're shutting down, we need to nuke the TabRestoreService, which
867 // will start the shutdown of the NavigationControllers and allow for
868 // proper shutdown. If we don't do this, Chrome won't shut down cleanly,
869 // and may end up crashing when some thread tries to use the IO thread (or
870 // another thread) that is no longer valid.
871 TabRestoreServiceFactory::ResetForProfile(_lastProfile);
875 // See if the focused window window has tabs, and adjust the key equivalents for
876 // Close Tab/Close Window accordingly.
877 - (void)menuNeedsUpdate:(NSMenu*)menu {
878 DCHECK(menu == [self fileMenu]);
879 [self updateMenuItemKeyEquivalents];
882 - (void)windowDidResignKey:(NSNotification*)notify {
883 // If a window is closed, this notification is fired but |[NSApp keyWindow]|
884 // returns nil regardless of whether any suitable candidates for the key
885 // window remain. It seems that the new key window for the app is not set
886 // until after this notification is fired, so a check is performed after the
887 // run loop is allowed to spin.
888 [self performSelector:@selector(checkForAnyKeyWindows)
893 - (void)windowDidBecomeMain:(NSNotification*)notify {
894 Browser* browser = chrome::FindBrowserWithWindow([notify object]);
898 if (browser->is_type_normal()) {
899 _tabMenuBridge = std::make_unique<TabMenuBridge>(
900 browser->tab_strip_model(),
901 [[NSApp mainMenu] itemWithTag:IDC_TAB_MENU]);
902 _tabMenuBridge->BuildMenu();
904 _tabMenuBridge.reset();
907 Profile* profile = browser->profile();
909 [self setLastProfile:profile];
910 _lastActiveColorProvider = browser->window()->GetColorProvider();
913 // Called when shutting down or logging out.
914 - (void)willPowerOff:(NSNotification*)notify {
915 // Don't attempt any shutdown here. Cocoa will shortly call
916 // -[BrowserCrApplication terminate:].
917 _isPoweringOff = YES;
920 - (void)checkForAnyKeyWindows {
921 if ([NSApp keyWindow])
924 g_browser_process->platform_part()->key_window_notifier().NotifyNoKeyWindow();
927 // If the auto-update interval is not set, make it 5 hours.
928 // Placed here for 2 reasons:
929 // 1) Same spot as other Pref stuff
930 // 2) Try and be friendly by keeping this after app launch
931 - (void)setUpdateCheckInterval {
932 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
933 CFStringRef app = CFSTR("com.google.Keystone.Agent");
934 CFStringRef checkInterval = CFSTR("checkInterval");
935 CFPropertyListRef plist = CFPreferencesCopyAppValue(checkInterval, app);
937 const float fiveHoursInSeconds = 5.0 * 60.0 * 60.0;
938 CFPreferencesSetAppValue(
939 checkInterval, base::apple::NSToCFPtrCast(@(fiveHoursInSeconds)), app);
940 CFPreferencesAppSynchronize(app);
945 - (void)openStartupUrls {
946 DCHECK(_startupComplete);
947 [self openUrlsReplacingNTP:_startupUrls];
948 _startupUrls.clear();
951 - (void)openUrlsReplacingNTP:(const std::vector<GURL>&)urls {
955 // On Mac, the URLs are passed in via Cocoa, not command line. The Chrome
956 // NSApplication is created in MainMessageLoop, and then the shortcut urls
957 // are passed in via Apple events. At this point, the first browser is
958 // already loaded in PreMainMessageLoop. If we initialize NSApplication
959 // before PreMainMessageLoop to capture shortcut URL events, it may cause
960 // more problems because it relies on things created in PreMainMessageLoop
961 // and may break existing message loop design.
963 // If the browser hasn't started yet, just queue up the URLs.
964 if (!_startupComplete) {
965 _startupUrls.insert(_startupUrls.end(), urls.begin(), urls.end());
969 OpenUrlsInBrowser(urls);
972 // This is called after profiles have been loaded and preferences registered.
973 // It is safe to access the default profile here.
974 - (void)applicationDidFinishLaunching:(NSNotification*)notify {
975 // Check if Chrome got launched by clicking on a notification.
976 if ([notify userInfo][NSApplicationLaunchUserNotificationKey])
977 LogLaunchedViaNotificationAction(NotificationActionSource::kBrowser);
979 if (g_browser_process->browser_policy_connector()
980 ->chrome_browser_cloud_management_controller()
981 ->IsEnterpriseStartupDialogShowing()) {
982 // As Chrome is not ready when the Enterprise startup dialog is being shown.
983 // Store the notification as it will be reposted when the dialog is closed.
987 MacStartupProfiler::GetInstance()->Profile(
988 MacStartupProfiler::DID_FINISH_LAUNCHING);
989 MacStartupProfiler::GetInstance()->RecordMetrics();
991 // Notify BrowserList to keep the application running so it doesn't go away
992 // when all the browser windows get closed.
993 _keepAlive = std::make_unique<ScopedKeepAlive>(
994 KeepAliveOrigin::APP_CONTROLLER, KeepAliveRestartOption::DISABLED);
996 [self setUpdateCheckInterval];
998 // Start managing the menu for app windows. This needs to be done here because
999 // main menu item titles are not yet initialized in awakeFromNib.
1000 [self initAppShimMenuController];
1002 // If enabled, keep Chrome alive when apps are open instead of quitting all
1004 _quitWithAppsController = new QuitWithAppsController();
1006 // Dynamically update shortcuts for "Close Window" and "Close Tab" menu items.
1007 [self fileMenu].delegate = self;
1009 // Instantiate the ProfileAttributesStorage observer so that we can get
1010 // notified when a profile is deleted.
1011 _profileAttributesStorageObserver =
1012 std::make_unique<AppControllerProfileObserver>(
1013 g_browser_process->profile_manager(), self);
1015 // Observe native theme change (e.g. light and dark mode).
1016 _nativeThemeObserver =
1017 std::make_unique<AppControllerNativeThemeObserver>(self);
1019 // Record the path to the (browser) app bundle; this is used by the app mode
1021 if (base::apple::AmIBundled()) {
1022 base::ThreadPool::PostTask(
1024 {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
1025 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
1026 base::BindOnce(&RecordLastRunAppBundlePath));
1029 // Makes "Services" menu items available.
1030 [self registerServicesMenuTypesTo:[notify object]];
1032 _startupComplete = YES;
1034 if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode))
1035 ConfigureNSAppForKioskMode();
1037 Browser* browser = chrome::FindLastActive();
1038 content::WebContents* activeWebContents = nullptr;
1039 _lastActiveColorProvider = nullptr;
1041 activeWebContents = browser->tab_strip_model()->GetActiveWebContents();
1042 _lastActiveColorProvider = browser->window()->GetColorProvider();
1044 [self updateHandoffManager:activeWebContents];
1045 [self openStartupUrls];
1047 PrefService* localState = g_browser_process->local_state();
1049 _localPrefRegistrar.Init(localState);
1050 _localPrefRegistrar.Add(
1051 prefs::kAllowFileSelectionDialogs,
1052 base::BindRepeating(
1053 [](CommandUpdater* commandUpdater) {
1054 bool enabled = g_browser_process->local_state()->GetBoolean(
1055 prefs::kAllowFileSelectionDialogs);
1056 commandUpdater->UpdateCommandEnabled(IDC_OPEN_FILE, enabled);
1061 _handoffObserver = std::make_unique<HandoffObserver>(self);
1063 ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager
1064 .sessionHandler = self;
1067 // Helper function for populating and displaying the in progress downloads at
1068 // exit alert panel.
1069 - (BOOL)userWillWaitForInProgressDownloads:(int)downloadCount {
1070 // Set the dialog text based on whether or not there are multiple downloads.
1071 // Dialog text: warning and explanation.
1072 NSString* titleText = l10n_util::GetPluralNSStringF(
1073 IDS_ABANDON_DOWNLOAD_DIALOG_TITLE, downloadCount);
1074 NSString* explanationText =
1075 l10n_util::GetNSString(IDS_ABANDON_DOWNLOAD_DIALOG_BROWSER_MESSAGE);
1077 // "Cancel download and exit" button text.
1078 NSString* exitTitle =
1079 l10n_util::GetNSString(IDS_ABANDON_DOWNLOAD_DIALOG_EXIT_BUTTON);
1081 // "Wait for download" button text.
1082 NSString* waitTitle =
1083 l10n_util::GetNSString(IDS_ABANDON_DOWNLOAD_DIALOG_CONTINUE_BUTTON);
1085 NSAlert* alert = [[NSAlert alloc] init];
1086 alert.messageText = titleText;
1087 alert.informativeText = explanationText;
1088 [alert addButtonWithTitle:waitTitle];
1089 [alert addButtonWithTitle:exitTitle];
1091 // 'waitButton' is the default choice.
1092 int choice = [alert runModal];
1093 return choice == NSAlertFirstButtonReturn ? YES : NO;
1096 // Check all profiles for in progress downloads, and if we find any, prompt the
1097 // user to see if we should continue to exit (and thus cancel the downloads), or
1098 // if we should wait.
1099 - (BOOL)shouldQuitWithInProgressDownloads {
1100 ProfileManager* profile_manager = g_browser_process->profile_manager();
1101 if (!profile_manager)
1104 std::vector<Profile*> profiles(profile_manager->GetLoadedProfiles());
1106 std::vector<Profile*> added_profiles;
1107 for (Profile* profile : profiles) {
1108 for (Profile* otr : profile->GetAllOffTheRecordProfiles())
1109 added_profiles.push_back(otr);
1111 profiles.insert(profiles.end(), added_profiles.begin(), added_profiles.end());
1113 for (Profile* profile : profiles) {
1114 DownloadCoreService* download_core_service =
1115 DownloadCoreServiceFactory::GetForBrowserContext(profile);
1116 // `DownloadCoreService` can be nullptr for some irregular profiles, e.g.
1117 // the System Profile.
1118 content::DownloadManager* download_manager =
1119 download_core_service &&
1120 download_core_service->HasCreatedDownloadManager()
1121 ? profile->GetDownloadManager()
1123 if (download_manager && download_manager->BlockingShutdownCount() > 0) {
1124 int downloadCount = download_manager->BlockingShutdownCount();
1125 if ([self userWillWaitForInProgressDownloads:downloadCount]) {
1126 // Create a new browser window (if necessary) and navigate to the
1127 // downloads page if the user chooses to wait.
1128 Browser* browser = chrome::FindBrowserWithProfile(profile);
1130 browser = Browser::Create(Browser::CreateParams(profile, true));
1131 browser->window()->Show();
1134 chrome::ShowDownloads(browser);
1138 // User wants to exit.
1143 // No profiles or active downloads found, okay to exit.
1147 // Called to determine if we should enable the "restore tab" menu item.
1148 // Checks with the TabRestoreService to see if there's anything there to
1149 // restore and returns YES if so.
1151 // In the zero-profile state, use the value from when the last profile was
1152 // still loaded (if ever).
1153 - (BOOL)canRestoreTab {
1154 Profile* lastProfile = [self lastProfileIfLoaded];
1156 return _tabRestoreWasEnabled;
1157 sessions::TabRestoreService* service =
1158 TabRestoreServiceFactory::GetForProfile(lastProfile);
1159 return service && !service->entries().empty();
1162 // Called from the AppControllerProfileObserver every time a profile is deleted.
1163 - (void)profileWasRemoved:(const base::FilePath&)profilePath
1164 forIncognito:(bool)isOffTheRecord {
1165 // If the lastProfile has been deleted, the profile manager has
1166 // already loaded a new one, so the pointer needs to be updated;
1167 // otherwise we will try to start up a browser window with a pointer
1168 // to the old profile.
1170 // In a browser test, the application is not brought to the front, so
1171 // |_lastProfile| might be null.
1172 if (!_lastProfile || (profilePath == _lastProfile->GetPath() &&
1173 isOffTheRecord == _lastProfile->IsOffTheRecord())) {
1174 Profile* last_used_profile = nullptr;
1175 auto* profile_manager = g_browser_process->profile_manager();
1176 if (profile_manager) {
1177 // |profile_manager| is null in browser tests during shutdown.
1178 last_used_profile = profile_manager->GetLastUsedProfileIfLoaded();
1180 [self setLastProfile:last_used_profile];
1183 auto it = _profileBookmarkMenuBridgeMap.find(profilePath);
1184 if (it != _profileBookmarkMenuBridgeMap.end() &&
1185 (!base::FeatureList::IsEnabled(features::kDestroyProfileOnBrowserClose) ||
1186 (it->second->GetProfile() && !isOffTheRecord))) {
1187 // Clean up the dangling Profile* in |_profileBookmarkMenuBridgeMap|.
1189 // No need to clean up when |isOffTheRecord|, because BookmarkMenuBridge
1190 // always points to a non-OTR profile.
1191 _profileBookmarkMenuBridgeMap.erase(it);
1195 // Returns true if there is a modal window (either window- or application-
1196 // modal) blocking the active browser. Note that tab modal dialogs (HTTP auth
1197 // sheets) will not count as blocking the browser. But things like open/save
1198 // dialogs that are window modal will block the browser.
1199 - (BOOL)keyWindowIsModal {
1200 if ([NSApp modalWindow])
1203 Browser* browser = chrome::GetLastActiveBrowser();
1204 return browser && [[browser->window()->GetNativeWindow().GetNativeNSWindow()
1205 attachedSheet] isKindOfClass:[NSWindow class]];
1208 - (BOOL)canOpenNewBrowser {
1209 Profile* unsafeLastProfile = [self lastProfileIfLoaded];
1210 // If the profile is not loaded, try to load it. If it's not usable, the
1211 // profile picker will be open instead.
1212 if (!unsafeLastProfile)
1214 return [self safeProfileForNewWindows:unsafeLastProfile] ? YES : NO;
1217 // Validates menu items in the dock (always) and in the menu bar (if there is no
1219 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
1220 SEL action = [item action];
1222 // Whether the profile is loaded and opening a new browser window is allowed.
1223 BOOL canOpenNewBrowser = [self canOpenNewBrowser];
1224 BOOL hasLoadedProfile = [self lastProfileIfLoaded] ? YES : NO;
1225 // Commands from dock are always handled by commandFromDock:, but commands
1226 // from the menu bar are only handled by commandDispatch: if there is no key
1228 if (action == @selector(commandDispatch:) ||
1229 action == @selector(commandFromDock:)) {
1230 NSInteger tag = [item tag];
1231 if (_menuState && // NULL in tests.
1232 _menuState->SupportsCommand(tag)) {
1234 // The File Menu commands are not automatically disabled by Cocoa when a
1235 // dialog sheet obscures the browser window, so we disable several of
1236 // them here. We don't need to include IDC_CLOSE_WINDOW, because
1237 // app_controller is only activated when there are no key windows (see
1238 // function comment).
1239 case IDC_RESTORE_TAB:
1240 enable = ![self keyWindowIsModal] && [self canRestoreTab];
1242 // Profile-level items that affect how the profile's UI looks should
1243 // only be available while there is a Profile opened.
1244 case IDC_SHOW_FULL_URLS:
1245 enable = hasLoadedProfile;
1247 // Browser-level items that open in new tabs or perform an action in a
1248 // current tab should not open if there's a window- or app-modal dialog.
1251 case IDC_FOCUS_LOCATION:
1252 case IDC_FOCUS_SEARCH:
1253 case IDC_SHOW_HISTORY:
1254 case IDC_SHOW_BOOKMARK_MANAGER:
1255 case IDC_CLEAR_BROWSING_DATA:
1256 case IDC_SHOW_DOWNLOADS:
1257 case IDC_IMPORT_SETTINGS:
1258 case IDC_MANAGE_EXTENSIONS:
1259 case IDC_HELP_PAGE_VIA_MENU:
1261 enable = canOpenNewBrowser && ![self keyWindowIsModal];
1263 // Browser-level items that open in new windows: allow the user to open
1264 // a new window even if there's a window-modal dialog.
1265 case IDC_NEW_WINDOW:
1266 enable = canOpenNewBrowser;
1268 case IDC_TASK_MANAGER:
1271 case IDC_NEW_INCOGNITO_WINDOW:
1272 enable = _menuState->IsCommandEnabled(tag) ? canOpenNewBrowser : NO;
1275 enable = _menuState->IsCommandEnabled(tag) ?
1276 ![self keyWindowIsModal] : NO;
1281 // "Show as tab" should only appear when the current window is a popup.
1282 // Since |validateUserInterfaceItem:| is called only when there are no
1283 // key windows, we should just hide this.
1284 // This is handled outside of the switch statement because we want to hide
1285 // this regardless if the command is supported or not.
1286 if (tag == IDC_SHOW_AS_TAB) {
1287 NSMenuItem* menuItem = base::apple::ObjCCast<NSMenuItem>(item);
1288 [menuItem setHidden:YES];
1290 } else if (action == @selector(terminate:)) {
1292 } else if (action == @selector(showPreferences:)) {
1293 enable = canOpenNewBrowser;
1294 } else if (action == @selector(orderFrontStandardAboutPanel:)) {
1295 enable = canOpenNewBrowser;
1296 } else if (action == @selector(toggleConfirmToQuit:)) {
1297 [self updateConfirmToQuitPrefMenuItem:static_cast<NSMenuItem*>(item)];
1298 enable = [self shouldEnableConfirmToQuitPrefMenuItem];
1303 - (void)commandDispatch:(id)sender {
1304 // Drop commands received after shutdown was initiated.
1305 if (g_browser_process->IsShuttingDown())
1308 // Handle the case where we're dispatching a command from a sender that's in a
1309 // browser window. This means that the command came from a background window
1310 // and is getting here because the foreground window is not a browser window.
1311 if ([sender respondsToSelector:@selector(window)]) {
1312 id delegate = [[sender window] windowController];
1313 if ([delegate respondsToSelector:@selector(commandDispatch:)]) {
1314 [delegate commandDispatch:sender];
1319 // If not between -applicationDidFinishLaunching: and
1320 // -applicationWillTerminate:, ignore. This can happen when events are sitting
1321 // in the event queue while the browser is shutting down.
1326 Profile* unsafeLastProfile = [self lastProfileIfLoaded];
1327 Profile* lastProfile = [self safeProfileForNewWindows:unsafeLastProfile];
1328 // Ignore commands during session restore's browser creation. It uses a
1329 // nested run loop and commands dispatched during this operation cause
1331 if (lastProfile && SessionRestore::IsRestoring(lastProfile) &&
1332 base::RunLoop::IsNestedOnCurrentThread()) {
1336 NSInteger tag = [sender tag];
1337 // The task manager can be shown without profile.
1338 if (tag == IDC_TASK_MANAGER) {
1339 chrome::OpenTaskManager(nullptr);
1343 if (unsafeLastProfile && !lastProfile) {
1344 // The profile is disallowed by policy (locked or guest mode disabled).
1345 ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
1346 ProfilePicker::EntryPoint::kProfileLocked));
1350 // Asynchronously load profile first if needed.
1351 app_controller_mac::RunInLastProfileSafely(
1352 base::BindOnce(^(Profile* profile) {
1353 [self executeCommand:sender withProfile:profile];
1355 app_controller_mac::kShowProfilePickerOnFailure);
1358 - (void)executeCommand:(id)sender withProfile:(Profile*)profile {
1360 // Couldn't load the Profile. RunInSafeProfileHelper will show the
1361 // ProfilePicker instead.
1365 NSInteger tag = [sender tag];
1369 // Create a new tab in an existing browser window (which we activate) if
1371 if (Browser* browser = ActivateBrowser(profile)) {
1372 chrome::ExecuteCommand(browser, IDC_NEW_TAB);
1375 [[fallthrough]]; // To create new window.
1376 case IDC_NEW_WINDOW:
1377 CreateBrowser(profile->GetOriginalProfile());
1379 case IDC_FOCUS_LOCATION:
1380 chrome::ExecuteCommand(ActivateOrCreateBrowser(profile),
1381 IDC_FOCUS_LOCATION);
1383 case IDC_FOCUS_SEARCH:
1384 chrome::ExecuteCommand(ActivateOrCreateBrowser(profile),
1387 case IDC_NEW_INCOGNITO_WINDOW:
1388 CreateBrowser(profile->GetPrimaryOTRProfile(/*create_if_needed=*/true));
1390 case IDC_RESTORE_TAB:
1391 app_controller_mac::TabRestorer::RestoreMostRecent(profile);
1394 chrome::ExecuteCommand(CreateBrowser(profile), IDC_OPEN_FILE);
1396 case IDC_CLEAR_BROWSING_DATA: {
1397 // There may not be a browser open, so use the default profile.
1398 if (Browser* browser = ActivateBrowser(profile)) {
1399 chrome::ShowClearBrowsingDataDialog(browser);
1401 chrome::OpenClearBrowsingDataDialogWindow(profile);
1405 case IDC_IMPORT_SETTINGS: {
1406 if (Browser* browser = ActivateBrowser(profile)) {
1407 chrome::ShowImportDialog(browser);
1409 chrome::OpenImportSettingsDialogWindow(profile);
1413 case IDC_SHOW_BOOKMARK_MANAGER:
1414 if (Browser* browser = ActivateBrowser(profile)) {
1415 chrome::ShowBookmarkManager(browser);
1417 // No browser window, so create one for the bookmark manager tab.
1418 chrome::OpenBookmarkManagerWindow(profile);
1421 case IDC_SHOW_HISTORY:
1422 if (Browser* browser = ActivateBrowser(profile))
1423 chrome::ShowHistory(browser);
1425 chrome::OpenHistoryWindow(profile);
1427 case IDC_SHOW_DOWNLOADS:
1428 if (Browser* browser = ActivateBrowser(profile))
1429 chrome::ShowDownloads(browser);
1431 chrome::OpenDownloadsWindow(profile);
1433 case IDC_MANAGE_EXTENSIONS:
1434 if (Browser* browser = ActivateBrowser(profile))
1435 chrome::ShowExtensions(browser);
1437 chrome::OpenExtensionsWindow(profile);
1439 case IDC_HELP_PAGE_VIA_MENU:
1440 if (Browser* browser = ActivateBrowser(profile))
1441 chrome::ShowHelp(browser, chrome::HELP_SOURCE_MENU);
1443 chrome::OpenHelpWindow(profile, chrome::HELP_SOURCE_MENU);
1446 [self showPreferences:sender];
1451 // Same as |-commandDispatch:|, but executes commands using a disposition
1452 // determined by the key flags. This will get called in the case where the
1453 // frontmost window is not a browser window, and the user has command-clicked
1454 // a button in a background browser window whose action is
1455 // |-commandDispatchUsingKeyModifiers:|
1456 - (void)commandDispatchUsingKeyModifiers:(id)sender {
1458 if ([sender respondsToSelector:@selector(window)]) {
1459 id delegate = [[sender window] windowController];
1460 if ([delegate respondsToSelector:
1461 @selector(commandDispatchUsingKeyModifiers:)]) {
1462 [delegate commandDispatchUsingKeyModifiers:sender];
1467 // NSApplication delegate method called when someone clicks on the dock icon.
1468 // To match standard mac behavior, we should open a new window if there are no
1470 - (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
1471 hasVisibleWindows:(BOOL)hasVisibleWindows {
1472 // If the browser is currently trying to quit, don't do anything and return NO
1473 // to prevent AppKit from doing anything.
1474 if (browser_shutdown::IsTryingToQuit())
1477 // Bring all browser windows to the front. Specifically, this brings them in
1478 // front of any app windows. FocusWindowSet will also unminimize the most
1479 // recently minimized window if no windows in the set are visible.
1480 // If there are any, return here. Otherwise, the windows are panels or
1481 // notifications so we still need to open a new window.
1482 if (hasVisibleWindows) {
1483 std::set<gfx::NativeWindow> browserWindows = GetBrowserNativeWindows();
1484 if (!browserWindows.empty()) {
1485 FocusWindowSetOnCurrentSpace(browserWindows);
1486 // We've performed the unminimize, so AppKit shouldn't do anything.
1491 base::FilePath lastProfilePath = GetStartupProfilePathMac();
1492 DCHECK_NE(lastProfilePath, ProfileManager::GetSystemProfilePath());
1494 // If launched as a hidden login item (due to installation of a persistent app
1495 // or by the user, for example in System Preferences->Accounts->Login Items),
1496 // allow session to be restored first time the user clicks on a Dock icon.
1497 // Normally, it'd just open a new empty page.
1498 static BOOL doneOnce = NO;
1499 BOOL attemptRestore =
1500 apps::AppShimTerminationManager::Get()->ShouldRestoreSession() ||
1501 (!doneOnce && base::mac::WasLaunchedAsHiddenLoginItem());
1504 // If the profile is locked or was off-the-record, open the profile picker.
1505 if (lastProfilePath == ProfileManager::GetGuestProfilePath() ||
1506 IsProfileSignedOut(lastProfilePath)) {
1507 ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
1508 ProfilePicker::EntryPoint::kProfileLocked));
1512 if (attemptRestore) {
1513 // Load the profile and attempt session restore.
1514 app_controller_mac::RunInLastProfileSafely(
1515 base::BindOnce(&AttemptSessionRestore),
1516 app_controller_mac::kShowProfilePickerOnFailure);
1520 // Open the profile picker (for multi-profile users) or a new window.
1521 if (StartupProfileModeFromReason(ProfilePicker::GetStartupModeReason()) ==
1522 StartupProfileMode::kProfilePicker) {
1523 ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
1524 ProfilePicker::EntryPoint::kNewSessionOnExistingProcess));
1526 // Asynchronously load profile first if needed.
1527 // TODO(crbug.com/1426857): Replace CreateBrowser by LaunchBrowserStartup
1528 app_controller_mac::RunInLastProfileSafely(
1529 base::BindOnce(base::IgnoreResult(&CreateBrowser)),
1530 app_controller_mac::kShowProfilePickerOnFailure);
1533 // We've handled the reopen event, so return NO to tell AppKit not
1538 - (void)initMenuState {
1539 _menuState = std::make_unique<CommandUpdaterImpl>(nullptr);
1540 _menuState->UpdateCommandEnabled(IDC_NEW_TAB, true);
1541 _menuState->UpdateCommandEnabled(IDC_NEW_WINDOW, true);
1542 _menuState->UpdateCommandEnabled(IDC_NEW_INCOGNITO_WINDOW, true);
1543 _menuState->UpdateCommandEnabled(IDC_OPEN_FILE, true);
1544 _menuState->UpdateCommandEnabled(IDC_CLEAR_BROWSING_DATA, true);
1545 _menuState->UpdateCommandEnabled(IDC_RESTORE_TAB, false);
1546 _menuState->UpdateCommandEnabled(IDC_FOCUS_LOCATION, true);
1547 _menuState->UpdateCommandEnabled(IDC_FOCUS_SEARCH, true);
1548 _menuState->UpdateCommandEnabled(IDC_SHOW_BOOKMARK_MANAGER, true);
1549 _menuState->UpdateCommandEnabled(IDC_SHOW_HISTORY, true);
1550 _menuState->UpdateCommandEnabled(IDC_SHOW_DOWNLOADS, true);
1551 _menuState->UpdateCommandEnabled(IDC_MANAGE_EXTENSIONS, true);
1552 _menuState->UpdateCommandEnabled(IDC_HELP_PAGE_VIA_MENU, true);
1553 _menuState->UpdateCommandEnabled(IDC_IMPORT_SETTINGS, true);
1554 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
1555 _menuState->UpdateCommandEnabled(IDC_FEEDBACK, true);
1557 _menuState->UpdateCommandEnabled(IDC_TASK_MANAGER, true);
1560 // Conditionally adds the Profile menu to the main menu bar.
1561 - (void)initProfileMenu {
1562 NSMenu* mainMenu = [NSApp mainMenu];
1563 NSMenuItem* profileMenu = [mainMenu itemWithTag:IDC_PROFILE_MAIN_MENU];
1565 if (!profiles::IsMultipleProfilesEnabled()) {
1566 [mainMenu removeItem:profileMenu];
1570 // The controller will unhide the menu if necessary.
1571 [profileMenu setHidden:YES];
1573 _profileMenuController =
1574 [[ProfileMenuController alloc] initWithMainMenuItem:profileMenu];
1577 - (void)initShareMenu {
1578 _shareMenuController = [[ShareMenuController alloc] init];
1579 NSMenu* fileMenu = [NSApp.mainMenu itemWithTag:IDC_FILE_MENU].submenu;
1580 NSString* shareMenuTitle = l10n_util::GetNSString(IDS_SHARE_MAC);
1581 NSMenuItem* shareMenuItem = [fileMenu itemWithTitle:shareMenuTitle];
1582 NSMenu* shareSubmenu = [[NSMenu alloc] initWithTitle:shareMenuTitle];
1583 shareSubmenu.delegate = _shareMenuController;
1584 shareMenuItem.submenu = shareSubmenu;
1587 // The Confirm to Quit preference is atypical in that the preference lives in
1588 // the app menu right above the Quit menu item. This method will refresh the
1589 // display of that item depending on the preference state.
1590 - (void)updateConfirmToQuitPrefMenuItem:(NSMenuItem*)item {
1591 // Format the string so that the correct key equivalent is displayed.
1592 NSString* acceleratorString = [ConfirmQuitPanelController keyCommandString];
1593 NSString* title = l10n_util::GetNSStringF(IDS_CONFIRM_TO_QUIT_OPTION,
1594 base::SysNSStringToUTF16(acceleratorString));
1595 [item setTitle:title];
1597 const PrefService* prefService = g_browser_process->local_state();
1598 bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1599 [item setState:enabled ? NSControlStateValueOn : NSControlStateValueOff];
1602 - (BOOL)shouldEnableConfirmToQuitPrefMenuItem {
1603 const PrefService* prefService = g_browser_process->local_state();
1604 return !prefService->FindPreference(prefs::kConfirmToQuitEnabled)
1608 - (void)registerServicesMenuTypesTo:(NSApplication*)app {
1609 // Note that RenderWidgetHostViewCocoa implements NSServicesRequests which
1610 // handles requests from services.
1611 [app registerServicesMenuSendTypes:@[ NSPasteboardTypeString ]
1612 returnTypes:@[ NSPasteboardTypeString ]];
1615 // Returns null if the profile is not loaded in memory.
1616 - (Profile*)lastProfileIfLoaded {
1617 // Return the profile of the last-used Browser, if available.
1619 return _lastProfile;
1621 if (![self isProfileReady])
1624 ProfileManager* profile_manager = g_browser_process->profile_manager();
1625 if (!profile_manager)
1628 // GetProfileByPath() returns nullptr if the profile is not loaded.
1629 return profile_manager->GetProfileByPath(GetStartupProfilePathMac());
1632 // Returns null if Chrome is not ready or there is no ProfileManager.
1633 // DEPRECATED: use lastProfileIfLoaded instead.
1634 // TODO(https://crbug.com/1176734): May be blocking, migrate all callers to
1635 // |-lastProfileIfLoaded|.
1636 - (Profile*)lastProfile {
1637 Profile* lastLoadedProfile = [self lastProfileIfLoaded];
1638 if (lastLoadedProfile)
1639 return lastLoadedProfile;
1641 if (![self isProfileReady])
1644 return GetLastProfileMac();
1647 - (Profile*)safeProfileForNewWindows:(Profile*)profile {
1651 DCHECK(!profile->IsSystemProfile());
1652 if (profile->IsGuestSession() && !profiles::IsGuestModeEnabled())
1655 if (IsProfileSignedOut(profile->GetPath()))
1656 return nullptr; // Profile is locked.
1658 return ProfileManager::MaybeForceOffTheRecordMode(profile);
1661 - (void)application:(NSApplication*)sender openURLs:(NSArray<NSURL*>*)urls {
1662 std::vector<GURL> gurlVector;
1663 for (NSURL* url in urls)
1664 gurlVector.push_back(net::GURLWithNSURL(url));
1666 if (!gurlVector.empty())
1667 [self openUrlsReplacingNTP:gurlVector];
1670 // Show the preferences window, or bring it to the front if it's already
1672 - (IBAction)showPreferences:(id)sender {
1673 // Asynchronously load profile first if needed.
1674 app_controller_mac::RunInLastProfileSafely(
1675 base::BindOnce(^(Profile* profile) {
1676 [self showPreferencesForProfile:profile];
1678 app_controller_mac::kShowProfilePickerOnFailure);
1681 - (IBAction)showPreferencesForProfile:(Profile*)profile {
1683 // Failed to load profile, show Profile Picker instead.
1686 // Re-use an existing browser, or create a new one.
1687 if (Browser* browser = ActivateBrowser(profile))
1688 chrome::ShowSettings(browser);
1690 chrome::OpenOptionsWindow(profile);
1693 - (IBAction)orderFrontStandardAboutPanel:(id)sender {
1694 // Asynchronously load profile first if needed.
1695 app_controller_mac::RunInLastProfileSafely(
1696 base::BindOnce(^(Profile* profile) {
1697 [self orderFrontStandardAboutPanelForProfile:profile];
1699 app_controller_mac::kShowProfilePickerOnFailure);
1702 - (IBAction)orderFrontStandardAboutPanelForProfile:(Profile*)profile {
1704 // Failed to load profile, show Profile Picker instead.
1707 // Re-use an existing browser, or create a new one.
1708 if (Browser* browser = ActivateBrowser(profile))
1709 chrome::ShowAboutChrome(browser);
1711 chrome::OpenAboutWindow(profile);
1714 - (IBAction)toggleConfirmToQuit:(id)sender {
1715 PrefService* prefService = g_browser_process->local_state();
1716 bool enabled = prefService->GetBoolean(prefs::kConfirmToQuitEnabled);
1717 prefService->SetBoolean(prefs::kConfirmToQuitEnabled, !enabled);
1720 // Explicitly bring to the foreground when creating new windows from the dock.
1721 - (void)commandFromDock:(id)sender {
1722 [NSApp activateIgnoringOtherApps:YES];
1723 [self commandDispatch:sender];
1726 - (NSMenu*)applicationDockMenu:(NSApplication*)sender {
1727 NSMenu* dockMenu = [[NSMenu alloc] initWithTitle:@""];
1729 BOOL profilesAdded = [_profileMenuController insertItemsIntoMenu:dockMenu
1732 if (profilesAdded) {
1733 [dockMenu addItem:[NSMenuItem separatorItem]];
1736 NSString* titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_WINDOW_MAC);
1738 [[NSMenuItem alloc] initWithTitle:titleStr
1739 action:@selector(commandFromDock:)
1742 item.tag = IDC_NEW_WINDOW;
1743 item.enabled = [self validateUserInterfaceItem:item];
1744 [dockMenu addItem:item];
1746 Profile* profile = [self lastProfileIfLoaded];
1748 // Buttons below require the profile to be loaded. In particular, if the
1749 // profile picker is shown at startup, these buttons won't be added until the
1750 // user picks a profile.
1755 if (IncognitoModePrefs::GetAvailability(profile->GetPrefs()) !=
1756 policy::IncognitoModeAvailability::kDisabled) {
1757 titleStr = l10n_util::GetNSStringWithFixup(IDS_NEW_INCOGNITO_WINDOW_MAC);
1758 item = [[NSMenuItem alloc] initWithTitle:titleStr
1759 action:@selector(commandFromDock:)
1762 item.tag = IDC_NEW_INCOGNITO_WINDOW;
1763 item.enabled = [self validateUserInterfaceItem:item];
1764 [dockMenu addItem:item];
1770 - (const std::vector<GURL>&)startupUrls {
1771 return _startupUrls;
1774 - (BookmarkMenuBridge*)bookmarkMenuBridge {
1775 return _bookmarkMenuBridge;
1778 - (HistoryMenuBridge*)historyMenuBridge {
1779 return _historyMenuBridge.get();
1782 - (TabMenuBridge*)tabMenuBridge {
1783 return _tabMenuBridge.get();
1786 - (void)initAppShimMenuController {
1787 if (!_appShimMenuController)
1788 _appShimMenuController = [[AppShimMenuController alloc] init];
1791 - (void)setLastProfile:(Profile*)profile {
1792 if (profile == _lastProfile)
1795 // If _lastProfile becomes null, remember the last state of Cmd+Shift+T so the
1796 // command can continue working (or stay disabled). This is primarily meant
1797 // for the zero-profile state.
1798 _tabRestoreWasEnabled = profile == nullptr && [self canRestoreTab];
1800 // Before tearing down the menu controller bridges, return the history menu to
1801 // its initial state.
1802 if (profile != nullptr) {
1803 if (_historyMenuBridge)
1804 _historyMenuBridge->ResetMenu();
1805 _historyMenuBridge.reset();
1806 } else if (_historyMenuBridge && !_isShuttingDown) {
1807 _historyMenuBridge->OnProfileWillBeDestroyed();
1810 _profilePrefRegistrar.reset();
1812 NSMenuItem* bookmarkItem = [NSApp.mainMenu itemWithTag:IDC_BOOKMARKS_MENU];
1813 BOOL hidden = bookmarkItem.hidden;
1814 if (profile != nullptr) {
1815 // Rebuild the menus with the new profile. The bookmarks submenu is cached
1816 // to avoid slowdowns when switching between profiles with large numbers of
1817 // bookmarks. Before caching, store whether it is hidden, make the menu item
1818 // visible, and restore its original hidden state after resetting the
1819 // submenu. This works around an apparent AppKit bug where setting a
1820 // *different* NSMenu submenu on a *hidden* menu item forces the item to
1821 // become visible. See https://crbug.com/497813 for more details.
1822 bookmarkItem.hidden = NO;
1823 _bookmarkMenuBridge = nullptr;
1824 } else if (_bookmarkMenuBridge && !_isShuttingDown) {
1825 DCHECK_EQ(_bookmarkMenuBridge->GetProfile(),
1826 _lastProfile->GetOriginalProfile());
1827 // |_bookmarkMenuBridge| always points to the original profile. So, no need
1828 // to call OnProfileWillBeDestroyed() when the OTR profile is destroyed.
1829 if (!_lastProfile->IsOffTheRecord()) {
1830 _bookmarkMenuBridge->OnProfileWillBeDestroyed();
1834 _lastProfile = profile;
1836 if (_lastProfile == nullptr)
1839 auto& entry = _profileBookmarkMenuBridgeMap[profile->GetPath()];
1840 if (!entry || !entry->GetProfile()) {
1841 // This creates a deep copy, but only the first 3 items in the root menu
1842 // are really wanted. This can probably be optimized, but lazy-loading of
1843 // the menu should reduce the impact in most flows.
1844 NSMenu* submenu = [bookmarkItem.submenu copy];
1845 submenu.delegate = nil; // The delegate is also copied. Remove it.
1847 // The original profile outlives the OTR profile. Always create the bridge
1848 // on the original profile, to prevent bugs WRT profile lifetime.
1849 entry = std::make_unique<BookmarkMenuBridge>(profile->GetOriginalProfile(),
1852 // Clear bookmarks from the old profile.
1853 entry->ClearBookmarkMenu();
1855 _bookmarkMenuBridge = entry.get();
1857 // No need to |BuildMenu| here. It is done lazily upon menu access.
1858 bookmarkItem.submenu = _bookmarkMenuBridge->BookmarkMenu();
1859 bookmarkItem.hidden = hidden;
1861 _historyMenuBridge = std::make_unique<HistoryMenuBridge>(_lastProfile);
1862 _historyMenuBridge->BuildMenu();
1864 chrome::BrowserCommandController::
1865 UpdateSharedCommandsForIncognitoAvailability(
1866 _menuState.get(), _lastProfile);
1867 _profilePrefRegistrar = std::make_unique<PrefChangeRegistrar>();
1868 _profilePrefRegistrar->Init(_lastProfile->GetPrefs());
1869 _profilePrefRegistrar->Add(
1870 policy::policy_prefs::kIncognitoModeAvailability,
1871 base::BindRepeating(&chrome::BrowserCommandController::
1872 UpdateSharedCommandsForIncognitoAvailability,
1873 _menuState.get(), _lastProfile));
1876 - (const ui::ColorProvider&)lastActiveColorProvider {
1877 // In rare situation the last active color provider is not properly tracked,
1878 // probably because -windowDidBecomeMain: is not fired.
1879 // TODO(crbug.com/1364279): DCHECK(_lastActiveColorProvider). If this is not
1880 // possible, investigate if we should make a GetDefaultColorProvider(), or
1881 // GetColorProviderForProfile().
1882 if (!_lastActiveColorProvider) {
1883 base::debug::DumpWithoutCrashing();
1884 return *ui::ColorProviderManager::Get().GetColorProviderFor(
1885 ui::NativeTheme::GetInstanceForNativeUi()->GetColorProviderKey(
1889 return *_lastActiveColorProvider;
1892 - (void)nativeThemeDidChange {
1893 // Some tests manually notify native theme change without setting
1894 // a profile for app controller, so `_lastProfile` will be nullptr.
1896 Browser* browser = chrome::FindBrowserWithProfile(_lastProfile);
1897 if (browser && browser->window())
1898 _lastActiveColorProvider = browser->window()->GetColorProvider();
1902 - (BOOL)windowHasBrowserTabs:(NSWindow*)window {
1906 Browser* browser = chrome::FindBrowserWithWindow(window);
1907 return browser && browser->is_type_normal();
1910 - (void)updateMenuItemKeyEquivalents {
1911 id target = [NSApp targetForAction:@selector(performClose:)];
1913 // If `target` is a popover (likely the dictionary lookup popover) the
1914 // main window should handle the close menu item action.
1915 NSWindow* targetWindow = nil;
1916 if ([target isKindOfClass:[NSPopover class]]) {
1918 [[[base::apple::ObjCCast<NSPopover>(target) contentViewController] view]
1921 targetWindow = base::apple::ObjCCast<NSWindow>(target);
1924 if (targetWindow != nil) {
1925 // If `targetWindow` is a child (a popover or bubble) the parent should
1926 // handle the command.
1927 if ([targetWindow parentWindow] != nil) {
1928 targetWindow = [targetWindow parentWindow];
1932 NSMenuItem* closeTabMenuItem = [self closeTabMenuItem];
1933 NSMenuItem* closeWindowMenuItem = [self closeWindowMenuItem];
1935 // If the browser window has tabs, assign Cmd-Shift-W to "Close Window",
1936 // otherwise leave it as the normal Cmd-W. Capitalization of the key
1937 // equivalent affects whether the Shift modifier is used.
1938 if ([self windowHasBrowserTabs:targetWindow]) {
1939 [closeTabMenuItem cr_setKeyEquivalent:@"w"
1940 modifierMask:NSEventModifierFlagCommand];
1941 [closeWindowMenuItem cr_setKeyEquivalent:@"W"
1942 modifierMask:NSEventModifierFlagCommand];
1944 [closeTabMenuItem cr_clearKeyEquivalent];
1945 [closeWindowMenuItem cr_setKeyEquivalent:@"w"
1946 modifierMask:NSEventModifierFlagCommand];
1950 // This only has an effect on macOS 12+, and requests any state restoration
1951 // archive to be created with secure encoding. See the article at
1952 // https://sector7.computest.nl/post/2022-08-process-injection-breaking-all-macos-security-layers-with-a-single-vulnerability/
1953 // for more details.
1954 - (BOOL)applicationSupportsSecureRestorableState:(NSApplication*)app {
1958 - (BOOL)application:(NSApplication*)application
1959 willContinueUserActivityWithType:(NSString*)userActivityType {
1960 return [userActivityType isEqualToString:NSUserActivityTypeBrowsingWeb];
1963 - (BOOL)application:(NSApplication*)application
1964 continueUserActivity:(NSUserActivity*)userActivity
1966 (void (^)(NSArray<id<NSUserActivityRestoring>>*))restorationHandler
1968 if (![userActivity.activityType
1969 isEqualToString:NSUserActivityTypeBrowsingWeb]) {
1973 NSString* originString = base::apple::ObjCCast<NSString>(
1974 (userActivity.userInfo)[handoff::kOriginKey]);
1975 handoff::Origin origin = handoff::OriginFromString(originString);
1976 UMA_HISTOGRAM_ENUMERATION(
1977 "OSX.Handoff.Origin", origin, handoff::ORIGIN_COUNT);
1979 NSURL* url = userActivity.webpageURL;
1983 GURL gurl(base::SysNSStringToUTF8([url absoluteString]));
1984 std::vector<GURL> gurlVector;
1985 gurlVector.push_back(gurl);
1987 [self openUrlsReplacingNTP:gurlVector];
1991 - (void)application:(NSApplication*)application
1992 didFailToContinueUserActivityWithType:(NSString*)userActivityType
1993 error:(NSError*)error {
1996 #pragma mark - Handoff Manager
1998 - (void)updateHandoffManagerWithURL:(const GURL&)handoffURL
1999 title:(const std::u16string&)handoffTitle {
2000 [_handoffManager updateActiveURL:handoffURL];
2001 [_handoffManager updateActiveTitle:handoffTitle];
2004 - (void)updateHandoffManager:(content::WebContents*)webContents {
2005 if (!_handoffManager)
2006 _handoffManager = [[HandoffManager alloc] init];
2008 if ([self isHandoffEligible:webContents]) {
2009 [self updateHandoffManagerWithURL:webContents->GetVisibleURL()
2010 title:webContents->GetTitle()];
2012 [self updateHandoffManagerWithURL:GURL() title:std::u16string()];
2016 - (BOOL)isHandoffEligible:(content::WebContents*)webContents {
2021 Profile::FromBrowserContext(webContents->GetBrowserContext());
2025 // Handoff is not allowed from an incognito profile. To err on the safe side,
2026 // also disallow Handoff from a guest profile.
2027 return profile->IsRegularProfile();
2030 - (BOOL)isProfileReady {
2031 return !g_browser_process->browser_policy_connector()
2032 ->chrome_browser_cloud_management_controller()
2033 ->IsEnterpriseStartupDialogShowing();
2036 #pragma mark - HandoffObserverDelegate
2038 - (void)handoffContentsChanged:(content::WebContents*)webContents {
2039 [self updateHandoffManager:webContents];
2042 #pragma mark - ASWebAuthenticationSessionWebBrowserSessionHandling
2044 // Note that both of these WebAuthenticationSession calls come in on a random
2045 // worker thread, so it's important to hop to the main thread.
2047 - (void)beginHandlingWebAuthenticationSessionRequest:
2048 (ASWebAuthenticationSessionRequest*)request {
2049 dispatch_async(dispatch_get_main_queue(), ^(void) {
2050 // Start tracking the pending request, so it's possible to cancel it before
2051 // the session actually starts.
2052 NSUUID* key = request.UUID;
2053 DCHECK(![GetPendingWebAuthRequests() objectForKey:key])
2054 << "Duplicate ASWebAuthenticationSessionRequest";
2055 [GetPendingWebAuthRequests() setObject:request forKey:key];
2057 app_controller_mac::RunInLastProfileSafely(
2058 base::BindOnce(&BeginHandlingWebAuthenticationSessionRequestWithProfile,
2060 app_controller_mac::kShowProfilePickerOnFailure);
2064 - (void)cancelWebAuthenticationSessionRequest:
2065 (ASWebAuthenticationSessionRequest*)request {
2066 dispatch_async(dispatch_get_main_queue(), ^(void) {
2067 NSUUID* key = request.UUID;
2068 if ([GetPendingWebAuthRequests() objectForKey:key]) {
2069 // Remove the pending request: for the case when the session is not
2071 [GetPendingWebAuthRequests() removeObjectForKey:key];
2073 // Take care of the undocumented requirement (https://crbug.com/1400714)
2074 // that -[ASWebAuthenticationSessionRequest cancelWithError:] be called
2075 // for authentication sessions canceled by the OS.
2076 NSError* error = [NSError
2077 errorWithDomain:ASWebAuthenticationSessionErrorDomain
2078 code:ASWebAuthenticationSessionErrorCodeCanceledLogin
2080 [request cancelWithError:error];
2082 // Cancel the session: for the case when it was already started.
2083 AuthSessionRequest::CancelAuthSession(request);
2088 - (void)setCloseWindowMenuItemForTesting:(NSMenuItem*)menuItem {
2089 _closeWindowMenuItemForTesting = menuItem;
2092 - (void)setCloseTabMenuItemForTesting:(NSMenuItem*)menuItem {
2093 _closeTabMenuItemForTesting = menuItem;
2096 - (void)setLastProfileForTesting:(Profile*)profile {
2097 _lastProfile = profile;
2098 Browser* browser = chrome::FindLastActiveWithProfile(profile);
2099 _lastActiveColorProvider = browser->window()->GetColorProvider();
2102 @end // @implementation AppController
2104 //---------------------------------------------------------------------------
2108 void UpdateProfileInUse(Profile* profile) {
2112 [AppController.sharedController setLastProfile:profile];
2115 void OpenUrlsInBrowserWithProfile(const std::vector<GURL>& urls,
2118 return; // No suitable profile to open the URLs, do nothing.
2119 // Prefer a regular (non-incognito) profile
2120 if (profile->IsIncognitoProfile()) {
2121 profile = ProfileManager::MaybeForceOffTheRecordMode(
2122 profile->GetOriginalProfile());
2124 Browser* browser = chrome::FindLastActiveWithProfile(profile);
2125 int startupIndex = TabStripModel::kNoTab;
2126 content::WebContents* startupContent = nullptr;
2127 if (browser && browser->tab_strip_model()->count() == 1) {
2128 // If there's only 1 tab and the tab is NTP, close this NTP tab and open all
2129 // startup urls in new tabs, because the omnibox will stay focused if we
2130 // load url in NTP tab.
2131 startupIndex = browser->tab_strip_model()->active_index();
2132 startupContent = browser->tab_strip_model()->GetActiveWebContents();
2133 } else if (!browser) {
2134 // if no browser window exists then create one with no tabs to be filled in.
2135 browser = Browser::Create(Browser::CreateParams(profile, true));
2136 browser->window()->Show();
2139 // Various methods to open URLs that we get in a native fashion. We use
2140 // StartupBrowserCreator here because on the other platforms, URLs to open
2141 // come through the ProcessSingleton, and it calls StartupBrowserCreator. It's
2142 // best to bottleneck the openings through that for uniform handling.
2143 base::CommandLine dummy(base::CommandLine::NO_PROGRAM);
2144 chrome::startup::IsFirstRun first_run =
2145 first_run::IsChromeFirstRun() ? chrome::startup::IsFirstRun::kYes
2146 : chrome::startup::IsFirstRun::kNo;
2147 StartupBrowserCreatorImpl launch(base::FilePath(), dummy, first_run);
2148 launch.OpenURLsInBrowser(browser, chrome::startup::IsProcessStartup::kNo,
2151 // This NTP check should be replaced once https://crbug.com/624410 is fixed.
2152 if (startupIndex != TabStripModel::kNoTab &&
2153 (startupContent->GetVisibleURL() == chrome::kChromeUINewTabURL ||
2154 startupContent->GetVisibleURL() == chrome::kChromeUINewTabPageURL)) {
2155 browser->tab_strip_model()->CloseWebContentsAt(startupIndex,
2156 TabCloseTypes::CLOSE_NONE);
2160 // Returns the profile to be used for new windows (or nullptr if it fails).
2161 Profile* GetSafeProfile(Profile* loaded_profile) {
2162 DCHECK(loaded_profile);
2164 [AppController.sharedController safeProfileForNewWindows:loaded_profile];
2167 // Called when the profile has been loaded for RunIn*ProfileSafely(). This
2168 // profile may not be safe to use for new windows (due to policies).
2169 void OnProfileLoaded(base::OnceCallback<void(Profile*)> callback,
2170 app_controller_mac::ProfileLoadFailureBehavior on_failure,
2171 Profile* loaded_profile) {
2172 Profile* safe_profile = GetSafeProfile(loaded_profile);
2173 if (!safe_profile) {
2174 switch (on_failure) {
2175 case app_controller_mac::kShowProfilePickerOnFailure:
2176 ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
2177 ProfilePicker::EntryPoint::kUnableToCreateBrowser));
2180 case app_controller_mac::kIgnoreOnFailure:
2184 std::move(callback).Run(safe_profile);
2189 namespace app_controller_mac {
2191 bool IsOpeningNewWindow() {
2192 return g_is_opening_new_window;
2195 void CreateGuestProfileIfNeeded() {
2196 g_browser_process->profile_manager()->CreateProfileAsync(
2197 ProfileManager::GetGuestProfilePath(),
2198 base::BindOnce(&UpdateProfileInUse));
2201 void EnterpriseStartupDialogClosed() {
2202 NSNotification* notify = [NSNotification
2203 notificationWithName:NSApplicationDidFinishLaunchingNotification
2205 [AppController.sharedController applicationDidFinishLaunching:notify];
2208 void RunInLastProfileSafely(base::OnceCallback<void(Profile*)> callback,
2209 ProfileLoadFailureBehavior on_failure) {
2211 if (Profile* profile = [AppController.sharedController lastProfileIfLoaded]) {
2212 OnProfileLoaded(std::move(callback), on_failure, profile);
2216 g_browser_process->profile_manager()->CreateProfileAsync(
2217 GetStartupProfilePathMac(),
2218 base::BindOnce(&OnProfileLoaded, std::move(callback), on_failure));
2221 void RunInProfileSafely(const base::FilePath& profile_dir,
2222 base::OnceCallback<void(Profile*)> callback,
2223 ProfileLoadFailureBehavior on_failure) {
2225 ProfileManager* profile_manager = g_browser_process->profile_manager();
2226 // `profile_manager` can be null in tests.
2227 if (!profile_manager) {
2228 OnProfileLoaded(std::move(callback), on_failure, nullptr);
2231 if (Profile* profile = profile_manager->GetProfileByPath(profile_dir)) {
2232 OnProfileLoaded(std::move(callback), on_failure, profile);
2235 // Pass the OnceCallback by reference because CreateProfileAsync() needs a
2236 // repeating callback. It will be called at most once.
2237 g_browser_process->profile_manager()->CreateProfileAsync(
2239 base::BindOnce(&OnProfileLoaded, std::move(callback), on_failure));
2243 void TabRestorer::RestoreMostRecent(Profile* profile) {
2244 RestoreByID(profile, SessionID::InvalidValue());
2248 void TabRestorer::RestoreByID(Profile* profile, SessionID session_id) {
2250 auto* service = TabRestoreServiceFactory::GetForProfile(profile);
2253 if (service->IsLoaded()) {
2254 DoRestoreTab(profile, session_id);
2256 // TabRestoreService isn't loaded. Tell it to load entries, and call
2257 // OpenWindowWithRestoredTabs() when it's done.
2258 std::ignore = new TabRestorer(profile, session_id);
2259 service->LoadTabsFromLastSession();
2264 void TabRestorer::DoRestoreTab(Profile* profile, SessionID session_id) {
2266 auto* service = TabRestoreServiceFactory::GetForProfile(profile);
2269 Browser* browser = chrome::FindTabbedBrowser(profile, false);
2270 BrowserLiveTabContext* context =
2271 browser ? browser->live_tab_context() : nullptr;
2272 if (session_id.is_valid()) {
2273 service->RestoreEntryById(context, session_id,
2274 WindowOpenDisposition::UNKNOWN);
2276 service->RestoreMostRecentEntry(context);
2280 TabRestorer::TabRestorer(Profile* profile, SessionID session_id)
2281 : profile_(profile), session_id_(session_id) {
2282 auto* service = TabRestoreServiceFactory::GetForProfile(profile);
2284 observation_.Observe(service);
2287 TabRestorer::~TabRestorer() = default;
2289 void TabRestorer::TabRestoreServiceDestroyed(
2290 sessions::TabRestoreService* service) {
2294 void TabRestorer::TabRestoreServiceLoaded(
2295 sessions::TabRestoreService* service) {
2296 observation_.Reset();
2297 DoRestoreTab(profile_, session_id_);
2301 } // namespace app_controller_mac