| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_APPS_APP_SHIM_APP_SHIM_MANAGER_MAC_H_ |
| #define CHROME_BROWSER_APPS_APP_SHIM_APP_SHIM_MANAGER_MAC_H_ |
| |
| #include <Security/Security.h> |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "apps/app_lifetime_monitor.h" |
| #include "base/apple/scoped_cftyperef.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "chrome/browser/apps/app_shim/app_shim_host_bootstrap_mac.h" |
| #include "chrome/browser/apps/app_shim/app_shim_host_mac.h" |
| #include "chrome/browser/badging/badge_manager.h" |
| #include "chrome/browser/profiles/avatar_menu_observer.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager_observer.h" |
| #include "chrome/browser/profiles/profile_observer.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list_observer.h" |
| #include "chrome/common/mac/app_shim.mojom.h" |
| #include "chrome/services/mac_notifications/public/mojom/mac_notifications.mojom.h" |
| #include "components/webapps/common/web_app_id.h" |
| |
| class Profile; |
| class ProfileManager; |
| |
| namespace base { |
| class FilePath; |
| } // namespace base |
| |
| namespace content { |
| class BrowserContext; |
| } // namespace content |
| |
| namespace apps { |
| |
| // The passed in `callback` will be called when all launches for the next app |
| // shim launch have completed (all profiles the app will launch in, as well |
| // as possibly multiple windows within profiles). |
| void SetMacShimStartupDoneCallbackForTesting(base::OnceClosure callback); |
| |
| // Returns the callback set with SetMacShimStartupDoneCallbackForTesting; |
| base::OnceClosure TakeShimStartupDoneCallbackForTesting(); |
| |
| // This app shim handler that handles events for app shims that correspond to an |
| // extension. |
| class AppShimManager |
| : public AppShimHostBootstrap::Client, |
| public AppShimHost::Client, |
| public AppLifetimeMonitor::Observer, |
| public BrowserListObserver, |
| public AvatarMenuObserver, |
| public ProfileManagerObserver, |
| public ProfileObserver, |
| public mac_notifications::mojom::MacNotificationProvider, |
| public mac_notifications::mojom::MacNotificationActionHandler { |
| public: |
| class Delegate { |
| public: |
| virtual ~Delegate() = default; |
| |
| // Show all app windows (for non-PWA apps). Return true if there existed any |
| // windows. |
| virtual bool ShowAppWindows(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| |
| // Close all app windows (for non-PWA apps). |
| virtual void CloseAppWindows(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| |
| // Return true iff |app_id| corresponds to an app that is installed for |
| // |profile|. Note that |profile| may be nullptr (in which case it should |
| // always return false). |
| virtual bool AppIsInstalled(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| |
| // Return true iff the specified app can create an AppShimHost, which will |
| // keep the app shim process connected (as opposed to, e.g, a bookmark app |
| // that opens in a tab, which will immediately close). |
| virtual bool AppCanCreateHost(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| |
| // Return true if Cocoa windows for this app should be hosted in the app |
| // shim process. |
| virtual bool AppUsesRemoteCocoa(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| |
| // Return true if a single app shim is used for all profiles (as opposed to |
| // one shim per profile). |
| virtual bool AppIsMultiProfile(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| |
| // Open a dialog to enable the specified extension. Call |callback| after |
| // the dialog is executed. |
| virtual void EnableExtension(Profile* profile, |
| const std::string& extension_id, |
| base::OnceCallback<void()> callback) = 0; |
| |
| // Launch the app in Chrome. This will (often) create a new window. It is |
| // guaranteed that |app_id| is installed for |profile| when this method |
| // is called. |
| virtual void LaunchApp( |
| Profile* profile, |
| const webapps::AppId& app_id, |
| const std::vector<base::FilePath>& files, |
| const std::vector<GURL>& urls, |
| const GURL& override_url, |
| chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state, |
| base::OnceClosure launch_finished_callback) = 0; |
| |
| // Launch the shim process for an app. It is guaranteed that |app_id| is |
| // installed for |profile| when this method is called. |
| virtual void LaunchShim(Profile* profile, |
| const webapps::AppId& app_id, |
| web_app::LaunchShimUpdateBehavior update_behavior, |
| web_app::ShimLaunchMode launch_mode, |
| ShimLaunchedCallback launched_callback, |
| ShimTerminatedCallback terminated_callback) = 0; |
| |
| // Return true if any app windows are open. This is eventually invoked |
| // by MaybeTerminate. It does not apply to bookmark apps. |
| virtual bool HasNonBookmarkAppWindowsOpen() = 0; |
| |
| virtual std::vector<chrome::mojom::ApplicationDockMenuItemPtr> |
| GetAppShortcutsMenuItemInfos(Profile* profile, |
| const webapps::AppId& app_id) = 0; |
| }; |
| |
| // Helper function to get the instance on the browser process. This will be |
| // non-null except for tests. |
| static AppShimManager* Get(); |
| |
| explicit AppShimManager(std::unique_ptr<Delegate> delegate); |
| AppShimManager(const AppShimManager&) = delete; |
| AppShimManager& operator=(const AppShimManager&) = delete; |
| ~AppShimManager() override; |
| |
| // Get the host corresponding to a profile and app id, or null if there is |
| // none. |
| AppShimHost* FindHost(Profile* profile, const webapps::AppId& app_id); |
| |
| // If the specified |browser| should be using RemoteCocoa (because it is a |
| // bookmark app), then get or create an AppShimHost for it, and return |
| // it. If an AppShimHost had to be created (e.g, because the app process is |
| // still launching), create one, which will bind to the app process when it |
| // finishes launching. |
| AppShimHost* GetHostForRemoteCocoaBrowser(Browser* browser); |
| |
| // Returns true if the specified `browser` should be using RemoteCocoa. This |
| // is equivalent to `GetHostForRemoteCocoaBrowser` return a non-null value, |
| // except that this method does not cause an AppShimHost to be created. |
| bool BrowserUsesRemoteCocoa(Browser* browser); |
| |
| // Return true if any non-bookmark app windows open. |
| bool HasNonBookmarkAppWindowsOpen(); |
| |
| // Called when the launch of the app was cancelled by the user. For example, |
| // if the user clicks cancel during a protocol launch. |
| void OnAppLaunchCancelled(content::BrowserContext* context, |
| const std::string& app_id); |
| |
| void UpdateAppBadge( |
| Profile* profile, |
| const webapps::AppId& app_id, |
| const std::optional<badging::BadgeManager::BadgeValue>& badge); |
| |
| // Called to connect to a MacNotificationProvider instance in the app shim |
| // process for the given app_id. This is only supported for multi-profile |
| // app shims; but only legacy platform apps would use single-profile shims |
| // anyway. |
| // If there is no running app shim matching `app_id`, currently this method |
| // instead returns a remote connected to a dummy notification provider. In |
| // the future this will instead launch an app shim for `app_id` and connect |
| // to that. |
| mojo::Remote<mac_notifications::mojom::MacNotificationProvider> |
| LaunchNotificationProvider(const webapps::AppId& app_id); |
| |
| // Triggers an OS-level notification permission request prompt to be shown by |
| // the app shim corresponding to `app_id`. Returns the current state without |
| // showing a prompt if permission has already been granted and/or denied to |
| // the app shim. |
| using RequestNotificationPermissionCallback = |
| chrome::mojom::AppShim::RequestNotificationPermissionCallback; |
| void ShowNotificationPermissionRequest( |
| const webapps::AppId& app_id, |
| RequestNotificationPermissionCallback callback); |
| |
| // Causes ShowNotificationPermissionRequest() to immediately call its callback |
| // with the given `result`, rather than trying to request permission from the |
| // app shim. |
| void SetNotificationPermissionResponseForTesting( |
| mac_notifications::mojom::RequestPermissionResult result) { |
| notification_permission_result_for_testing_ = result; |
| } |
| |
| // Opens the given app in the given profile in response to the user picking |
| // said profile in the Profiles menu. |
| void LaunchAppInProfile(const webapps::AppId& app_id, |
| const base::FilePath& profile_path); |
| |
| // AppShimHostBootstrap::Client: |
| void OnShimProcessConnected( |
| std::unique_ptr<AppShimHostBootstrap> bootstrap) override; |
| |
| // AppShimHost::Client: |
| void OnShimLaunchRequested( |
| AppShimHost* host, |
| web_app::LaunchShimUpdateBehavior update_behavior, |
| web_app::ShimLaunchMode launch_mode, |
| base::OnceCallback<void(base::Process)> launched_callback, |
| base::OnceClosure terminated_callback) override; |
| void OnShimProcessDisconnected(AppShimHost* host) override; |
| void OnShimFocus(AppShimHost* host) override; |
| void OnShimReopen(AppShimHost* host) override; |
| void OnShimOpenedFiles(AppShimHost* host, |
| const std::vector<base::FilePath>& files) override; |
| void OnShimSelectedProfile(AppShimHost* host, |
| const base::FilePath& profile_path) override; |
| void OnShimOpenedAppSettings(AppShimHost* host) override; |
| void OnShimOpenedUrls(AppShimHost* host, |
| const std::vector<GURL>& urls) override; |
| void OnShimOpenAppWithOverrideUrl(AppShimHost* host, |
| const GURL& override_url) override; |
| void OnShimWillTerminate(AppShimHost* host) override; |
| void OnNotificationPermissionStatusChanged( |
| AppShimHost* host, |
| mac_notifications::mojom::PermissionStatus status) override; |
| |
| // AppLifetimeMonitor::Observer overrides: |
| void OnAppStart(content::BrowserContext* context, |
| const std::string& app_id) override; |
| void OnAppActivated(content::BrowserContext* context, |
| const std::string& app_id) override; |
| void OnAppDeactivated(content::BrowserContext* context, |
| const std::string& app_id) override; |
| void OnAppStop(content::BrowserContext* context, |
| const std::string& app_id) override; |
| |
| // ProfileManagerObserver overrides: |
| void OnProfileAdded(Profile* profile) override; |
| void OnProfileMarkedForPermanentDeletion(Profile* profile) override; |
| void OnProfileManagerDestroying() override; |
| |
| // BrowserListObserver overrides: |
| void OnBrowserAdded(Browser* browser) override; |
| void OnBrowserRemoved(Browser* browser) override; |
| void OnBrowserSetLastActive(Browser* browser) override; |
| |
| // ProfileObserver overrides: |
| void OnProfileWillBeDestroyed(Profile* profile) override; |
| |
| // AvatarMenuObserver: |
| void OnAvatarMenuChanged(AvatarMenu* menu) override; |
| |
| static base::apple::ScopedCFTypeRef<CFStringRef> |
| BuildAppShimRequirementStringFromFrameworkRequirementString(CFStringRef); |
| |
| class AppShimObserver { |
| public: |
| virtual void OnShimProcessConnected(base::ProcessId pid) {} |
| virtual void OnShimProcessConnectedAndAllLaunchesDone( |
| base::ProcessId pid, |
| chrome::mojom::AppShimLaunchResult result) {} |
| virtual void OnShimReopen(base::ProcessId pid) {} |
| virtual void OnShimOpenedURLs(base::ProcessId pid) {} |
| // If this is overridden to return false, the regular notification action |
| // code path is bypassed. |
| virtual bool OnNotificationAction( |
| mac_notifications::mojom::NotificationActionInfoPtr& info); |
| }; |
| void SetAppShimObserverForTesting(AppShimObserver* observer) { |
| app_shim_observer_ = observer; |
| } |
| |
| // Simulates a launch as triggered by an app shim for the specific `app_id`. |
| void LoadAndLaunchAppForTesting(const webapps::AppId& app_id); |
| |
| protected: |
| typedef std::set<Browser*> BrowserSet; |
| |
| // Virtual for tests. |
| virtual void IsAcceptablyCodeSigned( |
| audit_token_t audit_token, |
| base::OnceCallback<void(bool)> callback) const; |
| |
| // Return the profile for |path|, only if it is already loaded. |
| virtual Profile* ProfileForPath(const base::FilePath& path); |
| |
| // Return a profile to use for a background shim launch, virtual for tests. |
| virtual Profile* ProfileForBackgroundShimLaunch(const webapps::AppId& app_id); |
| |
| // Load a profile and call |callback| when completed or failed. |
| virtual void LoadProfileAsync(const base::FilePath& path, |
| base::OnceCallback<void(Profile*)> callback); |
| |
| // Wait for |profile|'s WebAppProvider registry to be started. |
| virtual void WaitForAppRegistryReadyAsync( |
| Profile* profile, |
| base::OnceCallback<void()> callback); |
| |
| // Return true if the specified path is for a valid profile that is also |
| // locked. |
| virtual bool IsProfileLockedForPath(const base::FilePath& path); |
| |
| // Create an AppShimHost for the specified parameters (intercept-able for |
| // tests). |
| virtual std::unique_ptr<AppShimHost> CreateHost( |
| AppShimHost::Client* client, |
| const base::FilePath& profile_path, |
| const webapps::AppId& app_id, |
| bool use_remote_cocoa); |
| |
| // Open the specified URL in a new Chrome window. This is the fallback when |
| // an app shim exists, but there is no profile or extension for it. If |
| // |profile_path| is specified, then that profile is preferred, otherwise, |
| // the last used profile is used. |
| virtual void OpenAppURLInBrowserWindow(const base::FilePath& profile_path, |
| const GURL& url); |
| |
| // Launch the user manager (in response to attempting to access a locked |
| // profile). |
| virtual void LaunchProfilePicker(); |
| |
| // Terminate Chrome if Chrome attempted to quit, but was prevented from |
| // quitting due to apps being open. |
| virtual void MaybeTerminate(); |
| |
| // Called when profile menu items may have changed. Rebuilds the profile |
| // menu item list and sends updated lists to all apps. |
| void UpdateAllProfileMenus(); |
| |
| // Update |profile_menu_items_| from |avatar_menu_|. Virtual for tests. |
| virtual void RebuildProfileMenuItemsFromAvatarMenu(); |
| |
| // The list of all profiles that might appear in the menu. |
| std::vector<chrome::mojom::ProfileMenuItemPtr> profile_menu_items_; |
| |
| private: |
| friend class ScopedAppShimKeepAlive; |
| |
| // The state for an individual app, and for the profile-scoped app info. |
| struct ProfileState; |
| struct AppState; |
| |
| // Close all app shims associated with the specified profile. |
| void CloseShimsForProfile(Profile* profile); |
| |
| // This is called by OnShimProcessConnected if the app shim was launched by |
| // Chrome, and should connect to an already-existing AppShimHost. |
| void OnShimProcessConnectedForRegisterOnly( |
| std::unique_ptr<AppShimHostBootstrap> bootstrap); |
| |
| // The function LoadAndLaunchApp will: |
| // - Find the appropriate profiles for which |app_id| should be launched. |
| // - Load the profiles and ensure the app is enabled (using |
| // LoadProfileAndApp), if needed. |
| // - Launch the app, if appropriate. |
| // The "if appropriate" above is defined as: |
| // - If `params.files` is non-empty, then will always launch the app |
| // - If `profile_path` is non-empty, then use that profile. |
| // - In the most recently used profile, otherwise |
| // - If `params.files` is empty, then launch the app only if: |
| // - If `profile_path` is non-empty, then launch if the app is not running |
| // in that profile. |
| // - Otherwise, launch the app only if it is not running any profile. |
| using LoadAndLaunchAppCallback = |
| base::OnceCallback<void(ProfileState* profile_state, |
| chrome::mojom::AppShimLaunchResult result)>; |
| struct LoadAndLaunchAppParams { |
| LoadAndLaunchAppParams(); |
| ~LoadAndLaunchAppParams(); |
| LoadAndLaunchAppParams(const LoadAndLaunchAppParams&); |
| LoadAndLaunchAppParams& operator=(const LoadAndLaunchAppParams&); |
| |
| // Return true if `files` or `urls` is non-empty. If so, then this launch |
| // will open exactly one window. |
| bool HasFilesOrURLs() const; |
| |
| webapps::AppId app_id; |
| std::vector<base::FilePath> files; |
| std::vector<GURL> urls; |
| GURL override_url; |
| chrome::mojom::AppShimLoginItemRestoreState login_item_restore_state = |
| chrome::mojom::AppShimLoginItemRestoreState::kNone; |
| }; |
| void LoadAndLaunchApp(const base::FilePath& profile_path, |
| const LoadAndLaunchAppParams& params, |
| LoadAndLaunchAppCallback launch_callback); |
| bool LoadAndLaunchApp_TryExistingProfileStates( |
| const base::FilePath& profile_path, |
| const LoadAndLaunchAppParams& params, |
| const std::map<base::FilePath, int>& profiles_with_handlers, |
| LoadAndLaunchAppCallback* launch_callback); |
| void LoadAndLaunchApp_OnProfilesAndAppReady( |
| const std::vector<base::FilePath>& profile_paths_to_launch, |
| bool first_profile_is_from_bootstrap, |
| const LoadAndLaunchAppParams& params, |
| LoadAndLaunchAppCallback launch_callback); |
| void LoadAndLaunchApp_LaunchIfAppropriate( |
| Profile* profile, |
| ProfileState* profile_state, |
| const LoadAndLaunchAppParams& params, |
| base::OnceClosure launch_finished_callback); |
| |
| // The final step of both paths for OnShimProcessConnected. This will connect |
| // |bootstrap| to |profile_state|'s AppShimHost, if possible. The value of |
| // |profile_state| is non-null if and only if |result| is success. |
| void OnShimProcessConnectedAndAllLaunchesDone( |
| std::unique_ptr<AppShimHostBootstrap> bootstrap, |
| ProfileState* profile_state, |
| chrome::mojom::AppShimLaunchResult result); |
| // The continuation of OnShimProcessConnectedAndAllLaunchesDone, called after |
| // the possibly asynchronous code signature validation is complete. |
| void OnShimProcessConnectedAndAllLaunchesDoneValidationDone( |
| std::unique_ptr<AppShimHostBootstrap> bootstrap, |
| base::WeakPtr<AppShimHost> host, |
| bool is_acceptably_signed); |
| |
| // Load the specified profile and extension, and run |callback| with |
| // the result. The callback's arguments may be nullptr on failure. |
| using LoadProfileAndAppCallback = base::OnceCallback<void(Profile*)>; |
| void LoadProfileAndApp(const base::FilePath& profile_path, |
| const webapps::AppId& app_id, |
| LoadProfileAndAppCallback callback); |
| void LoadProfileAndApp_OnProfileLoaded(const base::FilePath& profile_path, |
| const webapps::AppId& app_id, |
| LoadProfileAndAppCallback callback, |
| Profile* profile); |
| void LoadProfileAndApp_OnProfileAppRegistryReady( |
| const base::FilePath& profile_path, |
| const webapps::AppId& app_id, |
| LoadProfileAndAppCallback callback); |
| void LoadProfileAndApp_OnAppEnabled(const base::FilePath& profile_path, |
| const webapps::AppId& app_id, |
| LoadProfileAndAppCallback callback); |
| |
| // Update the profiles menu for the specified host. |
| void UpdateAppProfileMenu(AppState* app_state); |
| |
| // Update the application dock menu for the specified host. |
| void UpdateApplicationDockMenu(Profile* profile, ProfileState* profile_state); |
| |
| // Updates the badge for the specified host. |
| void UpdateApplicationBadge(ProfileState* profile_state); |
| |
| // Retrieve the ProfileState for a given (Profile, AppId) pair. If one |
| // does not exist, create one. |
| ProfileState* GetOrCreateProfileState(Profile* profile, |
| const webapps::AppId& app_id); |
| |
| // Launches a shim for `app_id` in background mode (i.e. without being shown |
| // in the Dock and other UI surfaces). Can call `callback` with nullptr if the |
| // `app_id` is invalid (for example not installed locally in any profile). If |
| // the launch itself fails, this will still call `callback` with a valid |
| // AppShimHost, but a mojo connection to the app shim will never be |
| // established (and any calls that were made to the remote app shim will be |
| // dropped). |
| void LaunchShimInBackgroundMode( |
| const webapps::AppId& app_id, |
| base::OnceCallback<void(AppShimHost*)> callback); |
| |
| // Returns a mapping of profile paths to how many of the files and urls passed |
| // in in `params` each profile can handle. |
| static std::map<base::FilePath, int> GetProfilesWithMatchingHandlers( |
| const LoadAndLaunchAppParams& params); |
| |
| // mac_notifications::mojom::MacNotificationProvider: |
| void BindNotificationService( |
| mojo::PendingReceiver<mac_notifications::mojom::MacNotificationService> |
| service, |
| mojo::PendingRemote< |
| mac_notifications::mojom::MacNotificationActionHandler> handler) |
| override; |
| |
| // mac_notifications::mojom::MacNotificationActionHandler: |
| void OnNotificationAction( |
| mac_notifications::mojom::NotificationActionInfoPtr info) override; |
| |
| std::unique_ptr<Delegate> delegate_; |
| |
| // Weak, reset during OnProfileManagerDestroying. |
| raw_ptr<ProfileManager> profile_manager_ = nullptr; |
| |
| // Map from extension id to the state for that app. |
| std::map<std::string, std::unique_ptr<AppState>> apps_; |
| |
| // The avatar menu instance used by all app shims. |
| std::unique_ptr<AvatarMenu> avatar_menu_; |
| |
| // Requests for MacNotificationProviders that can't be connected to the |
| // correct app shim process right away get added to this receiver set |
| // instead. This is needed because higher level notifications code currently |
| // always expects to get a connected MacNotificationProvider remote. |
| mojo::ReceiverSet<mac_notifications::mojom::MacNotificationProvider> |
| dummy_notification_provider_receivers_; |
| |
| // Notification actions from all app shims are routed through these receivers |
| // and this class to make sure notification actions can be handled even if the |
| // browser process has never tried to connect to the notification service |
| // in an app shim. |
| mojo::ReceiverSet<mac_notifications::mojom::MacNotificationActionHandler, |
| webapps::AppId> |
| notification_action_handler_receivers_; |
| |
| // This contains `AppShimHostBootstrap` instances, keyed by the `ReceiverId` |
| // for the corresponding `MacNotificationActionHandler` receiver in |
| // `notification_action_handler_receivers_`, for app shims that were launched |
| // by the OS to handle notification actions. |
| std::map<mojo::ReceiverId, std::unique_ptr<AppShimHostBootstrap>> |
| bootstraps_pending_notification_actions_; |
| |
| // Set in some tests to short-circuit ShowNotificationPermissionRequest. |
| std::optional<mac_notifications::mojom::RequestPermissionResult> |
| notification_permission_result_for_testing_; |
| |
| raw_ptr<AppShimObserver> app_shim_observer_ = nullptr; |
| |
| base::ScopedMultiSourceObservation<Profile, ProfileObserver> |
| profile_observation_{this}; |
| |
| base::WeakPtrFactory<AppShimManager> weak_factory_; |
| }; |
| |
| } // namespace apps |
| |
| #endif // CHROME_BROWSER_APPS_APP_SHIM_APP_SHIM_MANAGER_MAC_H_ |