chromium/chrome/browser/ash/crosapi/browser_action.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/crosapi/browser_action.h"

#include <optional>
#include <string_view>

#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/app_restore/full_restore_service.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/desk_template_ash.h"
#include "chrome/browser/ash/floating_workspace/floating_workspace_util.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "components/user_manager/user_manager.h"

namespace crosapi {

void BrowserAction::Cancel(crosapi::mojom::CreationResult reason) {
  DCHECK_NE(reason, mojom::CreationResult::kSuccess);
}

void BrowserAction::OnPerformed(BrowserManagerCallback on_performed,
                                mojom::CreationResult result) {
  const bool retry = result == mojom::CreationResult::kBrowserShutdown;
  std::move(on_performed).Run(retry);
}

// No-op action, used to start the browser without opening a window.
class NoOpAction final : public BrowserAction {
 public:
  NoOpAction() : BrowserAction(true) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {}
};

class NewWindowAction final : public BrowserAction {
 public:
  NewWindowAction(bool incognito,
                  bool should_trigger_session_restore,
                  int64_t target_display_id,
                  std::optional<uint64_t> profile_id = std::nullopt)
      : BrowserAction(true),
        incognito_(incognito),
        should_trigger_session_restore_(should_trigger_session_restore),
        target_display_id_(target_display_id),
        profile_id_(profile_id),
        weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kNewWindowMinVersion);
    if (incognito_) {
      Profile* profile = ProfileManager::GetPrimaryUserProfile();
      if (!profile || !IncognitoModePrefs::IsIncognitoAllowed(profile))
        return;
    }
    service.service->NewWindow(incognito_, should_trigger_session_restore_,
                               target_display_id_, profile_id_,
                               base::BindOnce(&NewWindowAction::OnPerformed,
                                              weak_ptr_factory_.GetWeakPtr(),
                                              std::move(on_performed)));
  }

 private:
  const bool incognito_;
  const bool should_trigger_session_restore_;
  const int64_t target_display_id_;
  const std::optional<uint64_t> profile_id_;
  base::WeakPtrFactory<NewWindowAction> weak_ptr_factory_;
};

class NewWindowForDetachingTabAction final : public BrowserAction {
 public:
  NewWindowForDetachingTabAction(std::u16string_view tab_id_str,
                                 std::u16string_view group_id_str,
                                 NewWindowForDetachingTabCallback callback)
      : BrowserAction(false),
        tab_id_str_(tab_id_str),
        group_id_str_(group_id_str),
        callback_(std::move(callback)),
        weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             mojom::BrowserService::kNewWindowForDetachingTabMinVersion);
    service.service->NewWindowForDetachingTab(
        tab_id_str_, group_id_str_,
        base::BindOnce(&NewWindowForDetachingTabAction::OnPerformed,
                       weak_ptr_factory_.GetWeakPtr(),
                       std::move(on_performed)));
  }

  void Cancel(crosapi::mojom::CreationResult reason) override {
    DCHECK_NE(reason, mojom::CreationResult::kSuccess);
    std::move(callback_).Run(reason, {});
  }

 private:
  const std::u16string tab_id_str_;
  const std::u16string group_id_str_;
  NewWindowForDetachingTabCallback callback_;
  base::WeakPtrFactory<NewWindowForDetachingTabAction> weak_ptr_factory_;

  void OnPerformed(BrowserManagerCallback on_performed,
                   mojom::CreationResult result,
                   const std::string& new_window) {
    const bool retry = result == mojom::CreationResult::kBrowserShutdown;
    if (!retry) {
      std::move(callback_).Run(result, new_window);
    }
    std::move(on_performed).Run(retry);
  }
};

class NewTabAction final : public BrowserAction {
 public:
  explicit NewTabAction(std::optional<uint64_t> profile_id = std::nullopt)
      : BrowserAction(true), profile_id_(profile_id), weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             mojom::BrowserService::kNewTabMinVersion);
    service.service->NewTab(profile_id_,
                            base::BindOnce(&NewTabAction::OnPerformed,
                                           weak_ptr_factory_.GetWeakPtr(),
                                           std::move(on_performed)));
  }

 private:
  std::optional<uint64_t> profile_id_;
  base::WeakPtrFactory<NewTabAction> weak_ptr_factory_;
};

class LaunchAction final : public BrowserAction {
 public:
  explicit LaunchAction(int64_t target_display_id,
                        std::optional<uint64_t> profile_id = std::nullopt)
      : BrowserAction(true),
        target_display_id_(target_display_id),
        profile_id_(profile_id),
        weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             mojom::BrowserService::kLaunchMinVersion);
    service.service->Launch(target_display_id_, profile_id_,
                            base::BindOnce(&LaunchAction::OnPerformed,
                                           weak_ptr_factory_.GetWeakPtr(),
                                           std::move(on_performed)));
  }

 private:
  int64_t target_display_id_;
  std::optional<uint64_t> profile_id_;
  base::WeakPtrFactory<LaunchAction> weak_ptr_factory_;
};

namespace {
crosapi::mojom::OpenUrlParams_SwitchToTabPathBehavior ConvertPathBehavior(
    NavigateParams::PathBehavior path_behavior) {
  switch (path_behavior) {
    case NavigateParams::RESPECT:
      return crosapi::mojom::OpenUrlParams_SwitchToTabPathBehavior::kRespect;
    case NavigateParams::IGNORE_AND_NAVIGATE:
      return crosapi::mojom::OpenUrlParams_SwitchToTabPathBehavior::kIgnore;
  }
}
}  // namespace

class OpenUrlAction final : public BrowserAction {
 public:
  OpenUrlAction(
      const GURL& url,
      crosapi::mojom::OpenUrlParams::WindowOpenDisposition disposition,
      crosapi::mojom::OpenUrlFrom from,
      NavigateParams::PathBehavior path_behavior)
      : BrowserAction(true),
        url_(url),
        disposition_(disposition),
        from_(from),
        path_behavior_(path_behavior),
        weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             mojom::BrowserService::kOpenUrlMinVersion);
    auto params = crosapi::mojom::OpenUrlParams::New();
    params->disposition = disposition_;
    params->from = from_;
    params->path_behavior = ConvertPathBehavior(path_behavior_);
    service.service->OpenUrl(url_, std::move(params),
                             base::BindOnce(&OpenUrlAction::OnPerformed,
                                            weak_ptr_factory_.GetWeakPtr(),
                                            std::move(on_performed)));
  }

 private:
  const GURL url_;
  const crosapi::mojom::OpenUrlParams::WindowOpenDisposition disposition_;
  const crosapi::mojom::OpenUrlFrom from_;
  const NavigateParams::PathBehavior path_behavior_;
  base::WeakPtrFactory<OpenUrlAction> weak_ptr_factory_;
};

class OpenCaptivePortalSigninAction final : public BrowserAction {
 public:
  explicit OpenCaptivePortalSigninAction(const GURL& url)
      : BrowserAction(true), url_(url), weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    if (service.interface_version <
        mojom::BrowserService::kOpenCaptivePortalSigninMinVersion) {
      LOG(ERROR) << "BrowserService does not support OpenCaptivePortalSignin";
      return;
    }
    service.service->OpenCaptivePortalSignin(
        url_, base::BindOnce(&OpenCaptivePortalSigninAction::OnPerformed,
                             weak_ptr_factory_.GetWeakPtr(),
                             std::move(on_performed)));
  }

 private:
  const GURL url_;
  base::WeakPtrFactory<OpenCaptivePortalSigninAction> weak_ptr_factory_;
};

class NewGuestWindowAction final : public BrowserAction {
 public:
  explicit NewGuestWindowAction(int64_t target_display_id)
      : BrowserAction(true),
        target_display_id_(target_display_id),
        weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kNewGuestWindowMinVersion);
    service.service->NewGuestWindow(
        target_display_id_, base::BindOnce(&NewGuestWindowAction::OnPerformed,
                                           weak_ptr_factory_.GetWeakPtr(),
                                           std::move(on_performed)));
  }

 private:
  const int64_t target_display_id_;
  base::WeakPtrFactory<NewGuestWindowAction> weak_ptr_factory_;
};

class HandleTabScrubbingAction final : public BrowserAction {
 public:
  HandleTabScrubbingAction(float x_offset, bool is_fling_scroll_event)
      : BrowserAction(false),
        x_offset_(x_offset),
        is_fling_scroll_event_(is_fling_scroll_event) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kHandleTabScrubbingMinVersion);
    service.service->HandleTabScrubbing(x_offset_, is_fling_scroll_event_);
  }

 private:
  const float x_offset_;
  const bool is_fling_scroll_event_;
};

class NewFullscreenWindowAction final : public BrowserAction {
 public:
  NewFullscreenWindowAction(const GURL& url,
                            int64_t target_display_id,
                            NewFullscreenWindowCallback callback)
      : BrowserAction(true),
        url_(url),
        target_display_id_(target_display_id),
        callback_(std::move(callback)),
        weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kNewFullscreenWindowMinVersion);
    service.service->NewFullscreenWindow(
        url_, target_display_id_,
        base::BindOnce(&NewFullscreenWindowAction::OnPerformed,
                       weak_ptr_factory_.GetWeakPtr(),
                       std::move(on_performed)));
  }

  void Cancel(crosapi::mojom::CreationResult reason) override {
    DCHECK_NE(reason, mojom::CreationResult::kSuccess);
    std::move(callback_).Run(reason);
  }

 private:
  const GURL url_;
  const int64_t target_display_id_;
  NewFullscreenWindowCallback callback_;
  base::WeakPtrFactory<NewFullscreenWindowAction> weak_ptr_factory_;

  void OnPerformed(BrowserManagerCallback on_performed,
                   mojom::CreationResult result) {
    const bool retry = result == mojom::CreationResult::kBrowserShutdown;
    if (!retry) {
      std::move(callback_).Run(result);
    }
    std::move(on_performed).Run(retry);
  }
};

class RestoreTabAction final : public BrowserAction {
 public:
  RestoreTabAction() : BrowserAction(true), weak_ptr_factory_(this) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kRestoreTabMinVersion);
    service.service->RestoreTab(base::BindOnce(&RestoreTabAction::OnPerformed,
                                               weak_ptr_factory_.GetWeakPtr(),
                                               std::move(on_performed)));
  }

 private:
  base::WeakPtrFactory<RestoreTabAction> weak_ptr_factory_;
};

class OpenForFullRestoreAction final : public BrowserAction {
 public:
  explicit OpenForFullRestoreAction(bool skip_crash_restore)
      : BrowserAction(true), skip_crash_restore_(skip_crash_restore) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kOpenForFullRestoreMinVersion);
    service.service->OpenForFullRestore(skip_crash_restore_);
  }

 private:
  const bool skip_crash_restore_;
};

class CreateBrowserWithRestoredDataAction final : public BrowserAction {
 public:
  CreateBrowserWithRestoredDataAction(
      const std::vector<GURL>& urls,
      const gfx::Rect& bounds,
      const std::vector<tab_groups::TabGroupInfo>& tab_group_infos,
      ui::WindowShowState show_state,
      int32_t active_tab_index,
      int32_t first_non_pinned_tab_index,
      std::string_view app_name,
      int32_t restore_window_id,
      uint64_t lacros_profile_id)
      : BrowserAction(true),
        urls_(urls),
        bounds_(bounds),
        tab_group_infos_(tab_group_infos),
        show_state_(show_state),
        active_tab_index_(active_tab_index),
        first_non_pinned_tab_index_(first_non_pinned_tab_index),
        app_name_(app_name),
        restore_window_id_(restore_window_id),
        lacros_profile_id_(lacros_profile_id) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    crosapi::mojom::DeskTemplateStatePtr additional_state =
        crosapi::mojom::DeskTemplateState::New(
            urls_, active_tab_index_, app_name_, restore_window_id_,
            first_non_pinned_tab_index_, tab_group_infos_, lacros_profile_id_);
    crosapi::CrosapiManager::Get()
        ->crosapi_ash()
        ->desk_template_ash()
        ->CreateBrowserWithRestoredData(bounds_, show_state_,
                                        std::move(additional_state));
  }

 private:
  const std::vector<GURL> urls_;
  const gfx::Rect bounds_;
  const std::vector<tab_groups::TabGroupInfo> tab_group_infos_;
  const ui::WindowShowState show_state_;
  const int32_t active_tab_index_;
  const int32_t first_non_pinned_tab_index_;
  const std::string app_name_;
  const int32_t restore_window_id_;
  const uint64_t lacros_profile_id_;
};

class OpenProfileManagerAction final : public BrowserAction {
 public:
  OpenProfileManagerAction() : BrowserAction(true) {}

  void Perform(const VersionedBrowserService& service,
               BrowserManagerCallback on_performed) override {
    CHECK_GE(service.interface_version,
             crosapi::mojom::BrowserService::kOpenProfileManagerMinVersion);
    service.service->OpenProfileManager();
  }
};

// static
std::unique_ptr<BrowserAction> BrowserAction::NewWindow(
    bool incognito,
    bool should_trigger_session_restore,
    int64_t target_display_id,
    std::optional<uint64_t> profile_id) {
  return std::make_unique<NewWindowAction>(
      incognito, should_trigger_session_restore, target_display_id, profile_id);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::NewTab(
    std::optional<uint64_t> profile_id) {
  return std::make_unique<NewTabAction>(profile_id);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::Launch(
    int64_t target_display_id,
    std::optional<uint64_t> profile_id) {
  return std::make_unique<LaunchAction>(target_display_id, profile_id);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::NewWindowForDetachingTab(
    std::u16string_view tab_id_str,
    std::u16string_view group_id_str,
    NewWindowForDetachingTabCallback callback) {
  return std::make_unique<NewWindowForDetachingTabAction>(
      tab_id_str, group_id_str, std::move(callback));
}

// static
std::unique_ptr<BrowserAction> BrowserAction::NewGuestWindow(
    int64_t target_display_id) {
  return std::make_unique<NewGuestWindowAction>(target_display_id);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::NewFullscreenWindow(
    const GURL& url,
    int64_t target_display_id,
    NewFullscreenWindowCallback callback) {
  return std::make_unique<NewFullscreenWindowAction>(url, target_display_id,
                                                     std::move(callback));
}

// static
std::unique_ptr<BrowserAction> BrowserAction::OpenUrl(
    const GURL& url,
    crosapi::mojom::OpenUrlParams::WindowOpenDisposition disposition,
    crosapi::mojom::OpenUrlFrom from,
    NavigateParams::PathBehavior path_behavior) {
  return std::make_unique<OpenUrlAction>(url, disposition, from, path_behavior);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::OpenCaptivePortalSignin(
    const GURL& url) {
  return std::make_unique<OpenCaptivePortalSigninAction>(url);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::OpenForFullRestore(
    bool skip_crash_restore) {
  return std::make_unique<OpenForFullRestoreAction>(skip_crash_restore);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::RestoreTab() {
  return std::make_unique<RestoreTabAction>();
}

// static
std::unique_ptr<BrowserAction> BrowserAction::HandleTabScrubbing(
    float x_offset,
    bool is_fling_scroll_event) {
  return std::make_unique<HandleTabScrubbingAction>(x_offset,
                                                    is_fling_scroll_event);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::CreateBrowserWithRestoredData(
    const std::vector<GURL>& urls,
    const gfx::Rect& bounds,
    const std::vector<tab_groups::TabGroupInfo>& tab_groups,
    ui::WindowShowState show_state,
    int32_t active_tab_index,
    int32_t first_non_pinned_tab_index,
    std::string_view app_name,
    int32_t restore_window_id,
    uint64_t lacros_profile_id) {
  return std::make_unique<CreateBrowserWithRestoredDataAction>(
      urls, bounds, tab_groups, show_state, active_tab_index,
      first_non_pinned_tab_index, app_name, restore_window_id,
      lacros_profile_id);
}

// static
std::unique_ptr<BrowserAction> BrowserAction::OpenProfileManager() {
  return std::make_unique<OpenProfileManagerAction>();
}

// No window will be opened in the following circumstances:
// 1. Lacros-chrome is initialized in the Kiosk session.
// 2. Full restore is responsible for restoring/launching Lacros.
// 3. Floating Workspace Service is responsible for restoring/launching lacros.
// static
std::unique_ptr<BrowserAction> BrowserAction::GetActionForSessionStart() {
  if (user_manager::UserManager::Get()->IsLoggedInAsGuest()) {
    return std::make_unique<NewWindowAction>(
        /*incognito=*/false, /*should_trigger_session_restore=*/false, -1);
  }
  if (chromeos::IsKioskSession() ||
      ash::floating_workspace_util::ShouldHandleRestartRestore() ||
      ash::full_restore::MaybeCreateFullRestoreServiceForLacros()) {
    return std::make_unique<NoOpAction>();
  }
  return std::make_unique<NewWindowAction>(
      /*incognito=*/false, /*should_trigger_session_restore=*/true, -1);
}


}  // namespace crosapi