chromium/chrome/app_shim/app_shim_controller.h

// 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_