// Copyright 2018 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_APP_SHIM_APP_SHIM_CONTROLLER_H_
#define CHROME_APP_SHIM_APP_SHIM_CONTROLLER_H_
#include <vector>
#import <AppKit/AppKit.h>
#include "base/files/file_path.h"
#include "chrome/common/mac/app_shim.mojom.h"
#include "chrome/services/mac_notifications/public/mojom/mac_notifications.mojom.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/system/isolated_connection.h"
#include "url/gurl.h"
namespace apps {
class MachBootstrapAcceptorTest;
}
namespace display {
class ScopedNativeScreen;
}
namespace mac_notifications {
class MacNotificationServiceUN;
}
@class AppShimDelegate;
@class ProfileMenuTarget;
@class ApplicationDockMenuTarget;
@protocol RenderWidgetHostViewMacDelegate;
// The AppShimController is responsible for launching and maintaining the
// connection with the main Chrome process, and generally controls the lifetime
// of the app shim process.
class AppShimController
: public chrome::mojom::AppShim,
public mac_notifications::mojom::MacNotificationProvider {
public:
struct Params {
Params();
Params(const Params& other);
~Params();
// The full path of the user data dir.
base::FilePath user_data_dir;
// The relative path of the profile.
base::FilePath profile_dir;
std::string app_id;
std::u16string app_name;
GURL app_url;
// Task runner for the IO thread, only used to guarantee no race conditions
// when swapping out FeatureList instances in FinalizeFeatureState();
scoped_refptr<base::SequencedTaskRunner> io_thread_runner;
};
explicit AppShimController(const Params& params);
AppShimController(const AppShimController&) = delete;
AppShimController& operator=(const AppShimController&) = delete;
~AppShimController() override;
// Called early in process startup to temporarily initialize base::FeatureList
// and field trial state with a best guess of what the state should be. This
// gets state from the command line and/or a file in user_data_dir.
// FeatureList and field trials are later re-initialized in
// OnShimConnectedResponse, once communication with the correct chrome
// instance has been established.
static void PreInitFeatureState(const base::CommandLine& command_line);
// Called by OnShimConnectedResponse to finish setting up FeatureList and
// field trials for this process.
static void FinalizeFeatureState(
const variations::VariationsCommandLine& feature_state,
const scoped_refptr<base::SequencedTaskRunner>& io_thread_runner);
chrome::mojom::AppShimHost* host() const { return host_.get(); }
// Called by AppShimDelegate in response to receiving the notification
// -[NSApplicationDelegate applicationDidFinishLaunching:]. This kicks off
// the initialization process (connecting to Chrome, etc).
// `was_notification_action_launch` is set to true if this app shim was
// launched by the OS in response to the user interacting with a
// notification.
void OnAppFinishedLaunching(bool launched_by_notification_action);
// Called by AppShimDelegate in response a file being opened. If this occurs
// before OnDidFinishLaunching, then the argument is the files that triggered
// the launch of the app.
void OpenFiles(const std::vector<base::FilePath>& files);
// Called when a profile is selected from the profiles NSMenu.
void ProfileMenuItemSelected(uint32_t index);
// Called when a item is selected from the application dock menu.
void CommandFromDock(uint32_t index);
// Called when an item is selected from the application menu while no windows
// are shown.
void CommandDispatch(int command_id);
// Called by AppShimDelegate in response to an URL being opened. If this
// occurs before OnDidFinishLaunching, then the argument is the files that
// triggered the launch of the app.
void OpenUrls(const std::vector<GURL>& urls);
NSMenu* GetApplicationDockMenu();
// Called when the app is about to terminate.
void ApplicationWillTerminate();
// Returns the current MacNotificationService instances as a
// MacNotificationServiceUN, or nullptr if no notification service has been
// created yet, or if it is of the wrong type.
mac_notifications::MacNotificationServiceUN* notification_service_un();
private:
friend class TestShimClient;
friend class apps::MachBootstrapAcceptorTest;
// The state of initialization.
enum class InitState {
// Waiting for OnAppFinishedLaunching to be called.
kWaitingForAppToFinishLaunch,
// Waiting for PollForChromeReady to connect to the browser process.
kWaitingForChromeReady,
// Has sent OnShimConnected to the browser process, waiting for the
// response.
kHasSentOnShimConnected,
// Has received the OnShimConnected response from the browser,
// initialization is now complete.
kHasReceivedOnShimConnectedResponse,
};
// Init step 1 after OnAppFinishedLaunching. Find a running instance of Chrome
// to connect to, or launch Chrome if none is found. Returns true if a
// running instance was found and polling for readiness is possible.
bool FindOrLaunchChrome();
// Init step 2: Poll for the mach server exposed by Chrome's AppShimListener
// to be initialized. Once it has, proceed to SendBootstrapOnShimConnected.
// If |time_until_timeout| runs out, quit the app shim.
void PollForChromeReady(const base::TimeDelta& time_until_timeout);
// Init step 3: Reinterpret |endpoint| as a mojom::AppShimHostBootstrap, and
// send the OnShimConnected message.
void SendBootstrapOnShimConnected(mojo::PlatformChannelEndpoint endpoint);
// Init step 4 (the last): The browser response to OnShimConnected. On
// success, this will initialize |shim_receiver_|, through which |this| will
// be controlled by the browser. On failure, the app will quit.
void OnShimConnectedResponse(
chrome::mojom::AppShimLaunchResult result,
variations::VariationsCommandLine feature_state,
mojo::PendingReceiver<chrome::mojom::AppShim> app_shim_receiver);
// Builds main menu bar items.
void SetUpMenu();
void ChannelError(uint32_t custom_reason, const std::string& description);
void BootstrapChannelError(uint32_t custom_reason,
const std::string& description);
// chrome::mojom::AppShim implementation.
void CreateRemoteCocoaApplication(
mojo::PendingAssociatedReceiver<remote_cocoa::mojom::Application>
receiver) override;
void CreateCommandDispatcherForWidget(uint64_t widget_id) override;
void SetBadgeLabel(const std::string& badge_label) override;
void SetUserAttention(
chrome::mojom::AppShimAttentionType attention_type) override;
void UpdateProfileMenu(std::vector<chrome::mojom::ProfileMenuItemPtr>
profile_menu_items) override;
void UpdateApplicationDockMenu(
std::vector<chrome::mojom::ApplicationDockMenuItemPtr> dock_menu_items)
override;
void BindNotificationProvider(
mojo::PendingReceiver<mac_notifications::mojom::MacNotificationProvider>
provider) override;
void RequestNotificationPermission(
RequestNotificationPermissionCallback callback) override;
void BindChildHistogramFetcherFactory(
mojo::PendingReceiver<metrics::mojom::ChildHistogramFetcherFactory>
receiver) override;
// mac_notifications::mojom::MacNotificationProvider implementation.
void BindNotificationService(
mojo::PendingReceiver<mac_notifications::mojom::MacNotificationService>
service,
mojo::PendingRemote<
mac_notifications::mojom::MacNotificationActionHandler> handler)
override;
// Called when a change in the system notification permission status has been
// detected.
void NotificationPermissionStatusChanged(
mac_notifications::mojom::PermissionStatus status);
bool WebAppIsAdHocSigned() const;
// Helper function to set up a connection to the AppShimListener at the given
// Mach endpoint name.
static mojo::PlatformChannelEndpoint ConnectToBrowser(
const mojo::NamedPlatformChannel::ServerName& server_name);
// Helper function to search for the Chrome instance holding
// chrome::kSingletonLockFilename in the specified |user_data_dir|.
static NSRunningApplication* FindChromeFromSingletonLock(
const base::FilePath& user_data_dir);
static void CreateRenderWidgetHostNSView(
uint64_t view_id,
mojo::ScopedInterfaceEndpointHandle host_handle,
mojo::ScopedInterfaceEndpointHandle view_request_handle);
static NSObject<RenderWidgetHostViewMacDelegate>* GetDelegateForHost(
uint64_t view_id);
const Params params_;
// Populated by OpenFiles if it was called before OnAppFinishedLaunching
// was called.
std::vector<base::FilePath> launch_files_;
// Populated by OpenUrls if it was called before OnAppFinishedLaunching
// was called.
std::vector<GURL> launch_urls_;
// Populated by OnAppfinishedLaunching to indicate if this app was launched
// as a result of a notification action.
bool launched_by_notification_action_ = false;
// This is the Chrome process that this app is committed to connecting to.
// The app will quit if this process is terminated before the mojo connection
// is established.
// This process is determined by either:
// - The pid specified to the app's command line (if it exists).
// - The pid specified in the chrome::kSingletonLockFilename file.
NSRunningApplication* __strong chrome_to_connect_to_;
// The Chrome process that was launched by this app in FindOrLaunchChrome.
// Note that the app is not compelled to connect to this process (consider the
// case where multiple apps launch at the same time, and all launch their own
// Chrome -- only one will grab the chrome::kSingletonLockFilename, and all
// apps should connect to that).
NSRunningApplication* __strong chrome_launched_by_app_;
mojo::IsolatedConnection bootstrap_mojo_connection_;
mojo::Remote<chrome::mojom::AppShimHostBootstrap> host_bootstrap_;
mojo::Receiver<chrome::mojom::AppShim> shim_receiver_{this};
mojo::Remote<chrome::mojom::AppShimHost> host_;
mojo::PendingReceiver<chrome::mojom::AppShimHost> host_receiver_;
mojo::Receiver<mac_notifications::mojom::MacNotificationProvider>
notifications_receiver_{this};
AppShimDelegate* __strong delegate_;
InitState init_state_ = InitState::kWaitingForAppToFinishLaunch;
// The target for NSMenuItems in the profile menu.
ProfileMenuTarget* __strong profile_menu_target_;
// The target for NSMenuItems in the application dock menu.
ApplicationDockMenuTarget* __strong application_dock_menu_target_;
// The screen object used in the app sim.
std::unique_ptr<display::ScopedNativeScreen> screen_;
// The items in the profile menu.
std::vector<chrome::mojom::ProfileMenuItemPtr> profile_menu_items_;
// The items in the application dock menu.
std::vector<chrome::mojom::ApplicationDockMenuItemPtr> dock_menu_items_;
// MacNotificationService implementation used by Chrome to display
// notifications in this app shim process.
std::unique_ptr<mac_notifications::mojom::MacNotificationService>
notification_service_;
// Remote and receiver used for passing notification actions to the browser
// process. The receiver end is passed to the browser process when connection
// is established, while the remote end is passed to
// `MacNotificationServiceUN` when it is constructed.
mojo::PendingRemote<mac_notifications::mojom::MacNotificationActionHandler>
notification_action_handler_remote_;
mojo::PendingReceiver<mac_notifications::mojom::MacNotificationActionHandler>
notification_action_handler_receiver_ =
notification_action_handler_remote_.InitWithNewPipeAndPassReceiver();
NSInteger attention_request_id_ = 0;
};
#endif // CHROME_APP_SHIM_APP_SHIM_CONTROLLER_H_