chromium/chrome/browser/chromeos/app_mode/kiosk_browser_session_unittest.cc

// Copyright 2019 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/chromeos/app_mode/kiosk_browser_session.h"

#include <memory>
#include <set>
#include <utility>
#include <vector>

#include "ash/constants/ash_switches.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/json/values_util.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/app_mode/kiosk_browser_window_handler.h"
#include "chrome/browser/chromeos/app_mode/kiosk_metrics_service.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/tabs/tab_activity_simulator.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/gfx/geometry/rect.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/session/session_types.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/test/ash_test_helper.h"
#include "ash/wm/overview/overview_controller.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/types/system_web_app_delegate_map.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/web_applications/external_install_options.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"  // nogncheck
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chromeos/ash/components/standalone_browser/feature_refs.h"
#include "components/policy/core/common/device_local_account_type.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(ENABLE_PLUGINS)
#include "chrome/browser/chromeos/app_mode/kiosk_session_plugin_handler_delegate.h"
#include "content/public/browser/plugin_service.h"
#endif  // BUILDFLAG(ENABLE_PLUGINS)

namespace chromeos {

namespace {

using ::chromeos::FakePowerManagerClient;

constexpr char kTestAppId[] = "aaaabbbbaaaabbbbaaaabbbbaaaabbbb";
constexpr char kTestWebAppName1[] = "test_web_app_name1";
constexpr char kTestWebAppName2[] = "test_web_app_name2";
constexpr char kTestUrl[] = "www.test.com";
constexpr base::TimeDelta kCloseBrowserTimeout = base::Seconds(2);

#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr char kTestWebAppUrl[] = "https://install.url";
#endif

#if BUILDFLAG(ENABLE_PLUGINS)
constexpr char16_t kPepperPluginName1[] = u"pepper_plugin_name1";
constexpr char16_t kPepperPluginName2[] = u"pepper_plugin_name2";
constexpr char16_t kBrowserPluginName[] = u"browser_plugin_name";
constexpr char kPepperPluginFilePath1[] = "/path/to/pepper_plugin1";
constexpr char kPepperPluginFilePath2[] = "/path/to/pepper_plugin2";
constexpr char kBrowserPluginFilePath[] = "/path/to/browser_plugin";
constexpr char kUnregisteredPluginFilePath[] = "/path/to/unregistered_plugin";
#endif  // BUILDFLAG(ENABLE_PLUGINS)

// This class constructs and owns the `Browser` object. It assumes that the
// `Browser` uses a `TestBrowserWindow`. The class adds a default tab to the
// newly constructed browser and handles the closing lifecycle by registering a
// close callback on the `TestBrowserWindow`.
class FakeBrowser {
 public:
  explicit FakeBrowser(Browser::CreateParams(params))
      : FakeBrowser(Browser::Create(params)) {}

  explicit FakeBrowser(Browser* browser) : browser_(browser) {
    if (!browser->is_type_picture_in_picture()) {
      // Add a tab to the browser to ensure that `CloseAllTabs()` works.
      // Note that tabs are not supported with PICTURE_IN_PICTURE windows.
      TabActivitySimulator().AddWebContentsAndNavigate(
          browser_->tab_strip_model(), GURL(kTestUrl));
    }
    static_cast<TestBrowserWindow*>(browser_->window())
        ->SetCloseCallback(base::BindOnce(&FakeBrowser::OnBrowserWindowClosed,
                                          weak_ptr_.GetWeakPtr()));
  }

  ~FakeBrowser() {
    if (browser_ && !browser_->tab_strip_model()->empty()) {
      // This is required to prevent a DCHECK crash in the destructor of
      // `Browser` if tabs remain open.
      browser_->tab_strip_model()->CloseAllTabs();
    }
  }

  [[nodiscard]] bool WaitForBrowserClose() { return closed_future_.Wait(); }
  bool IsClosed() { return closed_future_.IsReady(); }

  bool IsFullscreen() {
    return browser_->exclusive_access_manager()
        ->fullscreen_controller()
        ->IsFullscreenForBrowser();
  }

 private:
  void OnBrowserWindowClosed() {
    closed_future_.SetValue();
    // `TestBrowserWindow` does not destroy `Browser` when `Close()` is called,
    // but real browser window does. Call `RemoveBrowser` here to fake this
    // behavior.
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(&FakeBrowser::RemoveBrowser, weak_ptr_.GetWeakPtr()));
  }

  void RemoveBrowser() { browser_.reset(); }

  base::test::TestFuture<void> closed_future_;
  std::unique_ptr<Browser> browser_;
  base::WeakPtrFactory<FakeBrowser> weak_ptr_{this};
};

// A test browser window that can toggle fullscreen state.
class FullscreenTestBrowserWindow : public TestBrowserWindow,
                                    ExclusiveAccessContext {
 public:
  explicit FullscreenTestBrowserWindow(TestingProfile* profile,
                                       bool fullscreen = false)
      : fullscreen_(fullscreen), profile_(profile) {}

  FullscreenTestBrowserWindow(const FullscreenTestBrowserWindow&) = delete;
  FullscreenTestBrowserWindow& operator=(const FullscreenTestBrowserWindow&) =
      delete;

  ~FullscreenTestBrowserWindow() override = default;

  // TestBrowserWindow:
  bool ShouldHideUIForFullscreen() const override { return fullscreen_; }
  bool IsFullscreen() const override { return fullscreen_; }
  void EnterFullscreen(const GURL& url,
                       ExclusiveAccessBubbleType type,
                       int64_t display_id) override {
    fullscreen_ = true;
  }
  void ExitFullscreen() override { fullscreen_ = false; }
  bool IsToolbarShowing() const override { return false; }
  bool IsLocationBarVisible() const override { return true; }

  ExclusiveAccessContext* GetExclusiveAccessContext() override { return this; }

  // ExclusiveAccessContext:
  Profile* GetProfile() override { return profile_; }
  content::WebContents* GetWebContentsForExclusiveAccess() override {
    NOTIMPLEMENTED();
    return nullptr;
  }
  void UpdateExclusiveAccessBubble(
      const ExclusiveAccessBubbleParams& params,
      ExclusiveAccessBubbleHideCallback first_hide_callback) override {}
  bool IsExclusiveAccessBubbleDisplayed() const override { return false; }
  void OnExclusiveAccessUserInput() override {}
  bool CanUserExitFullscreen() const override { return true; }

 private:
  bool fullscreen_ = false;
  raw_ptr<TestingProfile> profile_;
};

std::unique_ptr<FakeBrowser> CreateBrowserWithFullscreenTestWindowForParams(
    Browser::CreateParams params,
    TestingProfile* profile,
    bool is_main_browser = false) {
  // The main browser window for the kiosk is always fullscreen in the
  // production.
  auto window = std::make_unique<FullscreenTestBrowserWindow>(
      profile, /*fullscreen=*/is_main_browser);
  params.window = window.get();
  // Self deleting.
  new TestBrowserWindowOwner(std::move(window));
  return std::make_unique<FakeBrowser>(params);
}

void EmulateDeviceReboot() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
  base::CommandLine::ForCurrentProcess()->AppendSwitch(
      ash::switches::kFirstExecAfterBoot);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
}

struct KioskSessionRestartTestCase {
  std::string test_name;
  bool run_with_reboot = false;
};

struct KioskSessionPowerManagerRequestRestartTestCase {
  power_manager::RequestRestartReason power_manager_reason;
  KioskSessionRestartReason restart_reason;
};

void CheckSessionRestartReasonHistogramDependingOnRebootStatus(
    bool run_with_reboot,
    const KioskSessionRestartReason& reasonWithoutReboot,
    const KioskSessionRestartReason& reasonWithReboot,
    const base::HistogramTester* histogram) {
  if (run_with_reboot) {
    histogram->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
                                 reasonWithReboot, 1);
  } else {
    histogram->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
                                 reasonWithoutReboot, 1);
  }
  histogram->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 1);
}

#if BUILDFLAG(IS_CHROMEOS_ASH)

class SystemWebAppWaiter {
 public:
  explicit SystemWebAppWaiter(
      ash::SystemWebAppManager* system_web_app_manager) {
    system_web_app_manager->on_apps_synchronized().Post(
        FROM_HERE, base::BindLambdaForTesting([&]() {
          // Wait one execution loop for on_apps_synchronized() to be called on
          // all listeners.
          base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
              FROM_HERE, run_loop_.QuitClosure());
        }));
  }

  void Wait() { run_loop_.Run(); }

 private:
  base::RunLoop run_loop_;
};

std::unique_ptr<web_app::WebAppInstallInfo> GetWebAppInstallInfo(
    const GURL& url) {
  std::unique_ptr<web_app::WebAppInstallInfo> info =
      web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(url);
  info->scope = url.GetWithoutFilename();
  info->title = u"Web App";
  return info;
}

web_app::WebAppInstallInfoFactory GetAppWebAppInfoFactory() {
  static auto factory = base::BindRepeating(
      &GetWebAppInstallInfo, GURL(content::GetWebUIURL("system-app")));
  return factory;
}

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

}  // namespace

// TODO(b/271336749): split kiosk_browser_session_unittest.cc file into smaller
// test files.
template <typename KioskBrowserSessionParamType = KioskSessionRestartTestCase>
class KioskBrowserSessionBaseTest
    : public ::testing::TestWithParam<KioskBrowserSessionParamType> {
 public:
  KioskBrowserSessionBaseTest()
      : local_state_(std::make_unique<ScopedTestingLocalState>(
            TestingBrowserProcess::GetGlobal())),
        testing_profile_manager_(TestingBrowserProcess::GetGlobal(),
                                 local_state_.get()) {}

  KioskBrowserSessionBaseTest(const KioskBrowserSessionBaseTest&) = delete;
  KioskBrowserSessionBaseTest& operator=(const KioskBrowserSessionBaseTest&) =
      delete;

  static void SetUpTestSuite() {
    chromeos::PowerManagerClient::InitializeFake();
  }

  void SetUp() override {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
#if BUILDFLAG(IS_CHROMEOS_ASH)
    ash_test_helper_.SetUp(ash::AshTestHelper::InitParams());
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
    ASSERT_TRUE(testing_profile_manager_.SetUp());
    profile_ = testing_profile_manager_.CreateTestingProfile("test@user");
  }

  static void TearDownTestSuite() { chromeos::PowerManagerClient::Shutdown(); }

  TestingPrefServiceSimple* local_state() { return local_state_->Get(); }

  TestingProfile* profile() { return profile_; }

  base::HistogramTester* histogram() { return &histogram_; }

  base::test::TaskEnvironment* task_environment() { return &task_environment_; }

  std::unique_ptr<FakeBrowser> CreateBrowserWithTestWindow() {
    return CreateBrowserWithFullscreenTestWindowForParams(
        Browser::CreateParams(profile(), true), profile());
  }

  std::unique_ptr<FakeBrowser> CreateBrowserForWebApp(
      const std::string& web_app_name,
      std::optional<Browser::Type> browser_type = std::nullopt) {
    Browser::CreateParams params = Browser::CreateParams::CreateForAppPopup(
        /*app_name=*/web_app_name, /*trusted_source=*/true,
        /*window_bounds=*/gfx::Rect(), /*profile=*/profile(),
        /*user_gesture=*/true);
    if (browser_type.has_value()) {
      params.type = browser_type.value();
    }
    return CreateBrowserWithFullscreenTestWindowForParams(params, profile());
  }

  // Simulate starting a web kiosk session.
  void StartWebKioskSession(
      const std::string& web_app_name = kTestWebAppName1) {
    // Create the main kiosk browser window, which is normally auto-created when
    // a web kiosk session starts.
    web_kiosk_main_browser_ = CreateBrowserWithFullscreenTestWindowForParams(
        Browser::CreateParams::CreateForApp(
            /*app_name=*/web_app_name, /*trusted_source=*/true,
            /*window_bounds=*/gfx::Rect(), /*profile=*/profile(),
            /*user_gesture=*/true),
        profile(), /*is_main_browser=*/true);

    kiosk_browser_session_ = KioskBrowserSession::CreateForTesting(
        profile(), base::DoNothing(), local_state(), {crash_path().value()});
    kiosk_browser_session_->InitForWebKiosk(web_app_name);

    task_environment_.RunUntilIdle();
  }

  // Simulate starting a chrome app kiosk session.
  void StartChromeAppKioskSession() {
    kiosk_browser_session_ = std::make_unique<KioskBrowserSession>(
        profile(), base::DoNothing(), local_state());
    kiosk_browser_session_->InitForChromeAppKiosk(kTestAppId);
  }

  // Waits until `kiosk_browser_session_` handles creation of
  // `new_browser_window` and returns whether `new_browser_window` was asked to
  // close. In this case we will also ensure that `new_browser_window` was
  // automatically closed.
  bool DidSessionCloseNewWindow(FakeBrowser& new_browser) {
    // Wait until the new window is handled by `kiosk_browser_session_`.
    base::test::TestFuture<bool> is_handled;
    kiosk_browser_session_->SetOnHandleBrowserCallbackForTesting(
        is_handled.GetRepeatingCallback());
    bool is_closed_by_kiosk_session = is_handled.Get();

    if (is_closed_by_kiosk_session) {
      EXPECT_TRUE(new_browser.WaitForBrowserClose());
    }

    return is_closed_by_kiosk_session;
  }

  // Keeps ownership of the browser window while checking if the window is
  // closed automatically or not.
  bool DidSessionCloseNewWindow(std::unique_ptr<FakeBrowser> new_browser) {
    return DidSessionCloseNewWindow(*new_browser);
  }

  void CloseMainBrowser() {
    // Close the main browser window.
    web_kiosk_main_browser_.reset();
  }

  bool IsMainBrowserClosed() {
    return web_kiosk_main_browser_ == nullptr ||
           web_kiosk_main_browser_->IsClosed();
  }

  bool IsMainBrowserFullscreen() {
    return web_kiosk_main_browser_->IsFullscreen();
  }

  bool IsSessionShuttingDown() const {
    return kiosk_browser_session_->is_shutting_down();
  }

  void ResetKioskBrowserSession() { kiosk_browser_session_.reset(); }

  PrefService* GetPrefs() { return profile()->GetPrefs(); }

  KioskSessionPluginHandlerDelegate* GetPluginHandlerDelegate() {
    return kiosk_browser_session_->GetPluginHandlerDelegateForTesting();
  }

  base::FilePath crash_path() const { return temp_dir_.GetPath(); }

 private:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
  base::ScopedTempDir temp_dir_;
  std::unique_ptr<ScopedTestingLocalState> local_state_;
#if BUILDFLAG(IS_CHROMEOS_ASH)
  ash::AshTestHelper ash_test_helper_;
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  // `RenderViewHostTestEnabled` is required to make the navigation work that
  // happens in the tab added to `TestBrowserWindow` in `FakeBrowser`.
  content::RenderViewHostTestEnabler enabler_;
  TestingProfileManager testing_profile_manager_;
  raw_ptr<TestingProfile> profile_;
  // Main browser window created when launching a web kiosk app.
  // Could be nullptr if `StartWebKioskSession` function was not called.
  std::unique_ptr<FakeBrowser> web_kiosk_main_browser_;
  base::HistogramTester histogram_;
  std::unique_ptr<KioskBrowserSession> kiosk_browser_session_;
};

using KioskBrowserSessionTest = KioskBrowserSessionBaseTest<>;
using KioskBrowserSessionRestartReasonTest =
    KioskBrowserSessionBaseTest<KioskSessionRestartTestCase>;

TEST_F(KioskBrowserSessionTest, WebKioskTracksBrowserCreation) {
  local_state()->SetDict(
      prefs::kKioskMetrics,
      base::Value::Dict().Set(kKioskSessionStartTime,
                              base::TimeToValue(base::Time::Now())));

  StartWebKioskSession();
  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kWebStarted, 1);
  histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);

  EXPECT_TRUE(DidSessionCloseNewWindow(CreateBrowserWithTestWindow()));

  // The main browser window still exists, the kiosk session should not
  // shutdown.
  EXPECT_FALSE(IsSessionShuttingDown());
  // Opening a new browser should not be counted as a new session.
  histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);

  CloseMainBrowser();
  EXPECT_TRUE(IsSessionShuttingDown());

  const base::Value::Dict& dict = local_state()->GetDict(prefs::kKioskMetrics);
  const base::Value::List* sessions_list =
      dict.FindList(kKioskSessionLastDayList);
  ASSERT_TRUE(sessions_list);
  EXPECT_EQ(1u, sessions_list->size());

  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kStopped, 1);
  EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());

  histogram()->ExpectTotalCount(kKioskSessionDurationNormalHistogram, 1);
  histogram()->ExpectTotalCount(kKioskSessionDurationInDaysNormalHistogram, 0);
}

TEST_F(KioskBrowserSessionTest, ChromeAppKioskSessionState) {
  StartChromeAppKioskSession();
  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kStarted, 1);
  histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);
}

TEST_F(KioskBrowserSessionTest, ChromeAppKioskTracksBrowserCreation) {
  StartChromeAppKioskSession();

  EXPECT_TRUE(DidSessionCloseNewWindow(CreateBrowserWithTestWindow()));
  // Closing the browser should not shutdown the ChromeApp kiosk session.
  EXPECT_FALSE(IsSessionShuttingDown());
  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kClosedRegularBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);

  const base::Value::Dict& dict = local_state()->GetDict(prefs::kKioskMetrics);
  const base::Value::List* sessions_list =
      dict.FindList(kKioskSessionLastDayList);
  ASSERT_TRUE(sessions_list);
  EXPECT_EQ(1u, sessions_list->size());

  // Emulate exiting kiosk session.
  ResetKioskBrowserSession();

  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kStopped, 1);
  EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());

  histogram()->ExpectTotalCount(kKioskSessionDurationNormalHistogram, 1);
  histogram()->ExpectTotalCount(kKioskSessionDurationInDaysNormalHistogram, 0);
}

TEST_F(KioskBrowserSessionTest, ChromeAppKioskShouldClosePreexistingBrowsers) {
  std::unique_ptr<FakeBrowser> preexisting_browser =
      CreateBrowserWithTestWindow();

  StartChromeAppKioskSession();

  ASSERT_TRUE(preexisting_browser->WaitForBrowserClose());
}

TEST_F(KioskBrowserSessionTest, WebKioskShouldClosePreexistingBrowsers) {
  std::unique_ptr<FakeBrowser> preexisting_browser =
      CreateBrowserWithTestWindow();

  StartWebKioskSession();

  ASSERT_TRUE(preexisting_browser->WaitForBrowserClose());
  EXPECT_FALSE(IsMainBrowserClosed());
}

// Check that sessions list in local_state contains only sessions within the
// last 24h.
TEST_F(KioskBrowserSessionTest, WebKioskLastDaySessions) {
  // Setup local_state with 5 more kiosk sessions happened prior to the current
  // one: {now, 2,3,4,5 days ago}
  {
    auto session_list =
        base::Value::List().Append(base::TimeToValue(base::Time::Now()));

    const size_t kMaxDays = 4;
    for (size_t i = 0; i < kMaxDays; i++) {
      session_list.Append(
          base::TimeToValue(base::Time::Now() - base::Days(i + 2)));
    }

    local_state()->SetDict(
        prefs::kKioskMetrics,
        base::Value::Dict()
            .Set(kKioskSessionLastDayList, std::move(session_list))
            // Emulates previous session crashes.
            .Set(kKioskSessionStartTime,
                 base::TimeToValue(base::Time::Now() -
                                   2 * kKioskSessionDurationHistogramLimit)));
  }

#if BUILDFLAG(IS_CHROMEOS_ASH)
  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
      ash::switches::kLoginUser, "fake-user");
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

  base::FilePath crash_file;
  ASSERT_TRUE(base::CreateTemporaryFileInDir(crash_path(), &crash_file));

  StartWebKioskSession();

#if BUILDFLAG(IS_CHROMEOS_ASH)
  // `kRestored` is only reported for Ash.
  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kRestored, 1);
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kCrashed, 1);
  histogram()->ExpectTotalCount(kKioskSessionDurationCrashedHistogram, 1);
  histogram()->ExpectTotalCount(kKioskSessionDurationInDaysCrashedHistogram, 1);
  histogram()->ExpectTotalCount(kKioskSessionCountPerDayHistogram, 1);

  CloseMainBrowser();
  EXPECT_TRUE(IsSessionShuttingDown());

  const base::Value::Dict& dict = local_state()->GetDict(prefs::kKioskMetrics);
  const base::Value::List* sessions_list =
      dict.FindList(kKioskSessionLastDayList);
  ASSERT_TRUE(sessions_list);
  // There should be only two kiosk sessions on the list:
  // the one that happened right before the current one and the current one.
  EXPECT_EQ(2u, sessions_list->size());
  for (const auto& time : *sessions_list) {
    EXPECT_LE(base::Time::Now() - base::ValueToTime(time).value(),
              base::Days(1));
  }

  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kStopped, 1);
  EXPECT_EQ(3u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
  histogram()->ExpectTotalCount(kKioskSessionDurationNormalHistogram, 1);
  histogram()->ExpectTotalCount(kKioskSessionDurationInDaysNormalHistogram, 0);
}

TEST_F(KioskBrowserSessionTest, DoNotOpenSecondBrowserInWebKiosk) {
  StartWebKioskSession(kTestWebAppName1);

  EXPECT_TRUE(
      DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName1)));
}

TEST_F(KioskBrowserSessionTest, DoNotCrashIfBrowserClosedSuccessfully) {
  StartWebKioskSession(kTestWebAppName1);

  auto browser = CreateBrowserForWebApp(kTestWebAppName1);

  task_environment()->FastForwardBy(kCloseBrowserTimeout);
}

TEST_F(KioskBrowserSessionTest, OpenSecondBrowserInWebKioskIfAllowed) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession(kTestWebAppName1);

  EXPECT_FALSE(
      DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName1)));
}

TEST_F(KioskBrowserSessionTest, EnsureSecondBrowserIsFullscreenInWebKiosk) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession(kTestWebAppName1);
  EXPECT_TRUE(IsMainBrowserFullscreen());

  std::unique_ptr<FakeBrowser> second_browser =
      CreateBrowserForWebApp(kTestWebAppName1);
  DidSessionCloseNewWindow(*second_browser);

  EXPECT_TRUE(second_browser->IsFullscreen());
}

TEST_F(KioskBrowserSessionTest,
       DoNotOpenSecondBrowserInWebKioskIfTypeIsNotAppPopup) {
  const std::vector<Browser::Type> not_app_popup_browser_types = {
      Browser::Type::TYPE_NORMAL,
      Browser::Type::TYPE_POPUP,
      Browser::Type::TYPE_APP,
      Browser::Type::TYPE_DEVTOOLS,
#if BUILDFLAG(IS_CHROMEOS_ASH)
      Browser::Type::TYPE_CUSTOM_TAB,
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
      Browser::Type::TYPE_PICTURE_IN_PICTURE,
  };

  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession(kTestWebAppName1);

  for (auto browser_type : not_app_popup_browser_types) {
    EXPECT_TRUE(DidSessionCloseNewWindow(
        CreateBrowserForWebApp(kTestWebAppName1, browser_type)));
  }
}

TEST_F(KioskBrowserSessionTest,
       DoNotOpenSecondBrowserInWebKioskWithEmptyWebAppName) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession();

  EXPECT_TRUE(DidSessionCloseNewWindow(CreateBrowserWithTestWindow()));
}

TEST_F(KioskBrowserSessionTest,
       DoNotOpenSecondBrowserInWebKioskWithDifferentWebAppName) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession(kTestWebAppName1);

  EXPECT_TRUE(
      DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName2)));
}

TEST_F(KioskBrowserSessionTest, DoNotOpenSecondBrowserInChromeAppKiosk) {
  // This flag allows opening new windows only for the web kiosk session. For
  // chrome app kiosk we still should block all new browsers.
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartChromeAppKioskSession();

  EXPECT_TRUE(
      DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName2)));
}

TEST_F(KioskBrowserSessionTest, NewOpenedRegularBrowserMetrics) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession(kTestWebAppName1);

  DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName1));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kOpenedRegularBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

TEST_F(KioskBrowserSessionTest, NewClosedRegularBrowserMetrics) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, false);
  StartWebKioskSession(kTestWebAppName1);

  DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName1));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kClosedRegularBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

TEST_F(KioskBrowserSessionTest,
       DoNotExitWebKioskSessionWhenSecondBrowserIsOpened) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession();

  auto second_browser = CreateBrowserForWebApp(kTestWebAppName1);
  EXPECT_FALSE(DidSessionCloseNewWindow(*second_browser));

  CloseMainBrowser();
  EXPECT_FALSE(IsSessionShuttingDown());

  second_browser.reset();
  // Exit kiosk session when the last browser is closed.
  EXPECT_TRUE(IsSessionShuttingDown());
}

TEST_F(KioskBrowserSessionTest, InitialBrowserShouldBeHandledAsRegularBrowser) {
  GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  StartWebKioskSession();

  auto second_browser = CreateBrowserForWebApp(kTestWebAppName1);
  EXPECT_FALSE(DidSessionCloseNewWindow(*second_browser));

  second_browser.reset();
  EXPECT_FALSE(IsSessionShuttingDown());

  CloseMainBrowser();
  // Exit kiosk session when the last browser is closed.
  EXPECT_TRUE(IsSessionShuttingDown());
}

TEST_P(KioskBrowserSessionRestartReasonTest, StoppedMetric) {
  const KioskSessionRestartTestCase& test_config = GetParam();
  StartWebKioskSession();
  // Emulate exiting the kiosk session.
  CloseMainBrowser();
  EXPECT_TRUE(IsSessionShuttingDown());
  if (test_config.run_with_reboot) {
    EmulateDeviceReboot();
  }
  histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 0);

  StartWebKioskSession();

  if (test_config.run_with_reboot) {
    histogram()->ExpectBucketCount(
        kKioskSessionRestartReasonHistogram,
        KioskSessionRestartReason::kStoppedWithReboot, 1);
  } else {
    histogram()->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
                                   KioskSessionRestartReason::kStopped, 1);
  }
  histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 1);
}

TEST_P(KioskBrowserSessionRestartReasonTest, CrashMetric) {
  const KioskSessionRestartTestCase& test_config = GetParam();
  // Setup `kKioskSessionStartTime` and add a file to the crash directory to
  // emulate previous kiosk session crash.
  local_state()->SetDict(
      prefs::kKioskMetrics,
      base::Value::Dict().Set(
          kKioskSessionStartTime,
          base::TimeToValue(base::Time::Now() - base::Hours(1))));
  base::FilePath crash_file;
  ASSERT_TRUE(base::CreateTemporaryFileInDir(crash_path(), &crash_file));
  if (test_config.run_with_reboot) {
    EmulateDeviceReboot();
  }

  StartWebKioskSession();

  CheckSessionRestartReasonHistogramDependingOnRebootStatus(
      test_config.run_with_reboot, KioskSessionRestartReason::kCrashed,
      KioskSessionRestartReason::kCrashedWithReboot, histogram());
}

TEST_P(KioskBrowserSessionRestartReasonTest, LocalStateWasNotSavedMetric) {
  const KioskSessionRestartTestCase& test_config = GetParam();
  // Setup `kKioskSessionStartTime` to emulate previous kiosk session stopped
  // correctly, but because of race condition, `kKioskSessionStartTime` was not
  // cleaned.
  local_state()->SetDict(
      prefs::kKioskMetrics,
      base::Value::Dict().Set(
          kKioskSessionStartTime,
          base::TimeToValue(base::Time::Now() - base::Hours(1))));
  if (test_config.run_with_reboot) {
    EmulateDeviceReboot();
  }

  StartWebKioskSession();

  CheckSessionRestartReasonHistogramDependingOnRebootStatus(
      test_config.run_with_reboot,
      KioskSessionRestartReason::kLocalStateWasNotSaved,
      KioskSessionRestartReason::kLocalStateWasNotSavedWithReboot, histogram());
}

#if BUILDFLAG(ENABLE_PLUGINS)
TEST_P(KioskBrowserSessionRestartReasonTest, PluginCrashedMetric) {
  const KioskSessionRestartTestCase& test_config = GetParam();
  StartWebKioskSession();

  KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();
  delegate->OnPluginCrashed(base::FilePath(kBrowserPluginFilePath));

  // Emulate exiting the kiosk session.
  CloseMainBrowser();
  EXPECT_TRUE(IsSessionShuttingDown());
  if (test_config.run_with_reboot) {
    EmulateDeviceReboot();
  }
  histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 0);

  StartWebKioskSession();

  CheckSessionRestartReasonHistogramDependingOnRebootStatus(
      test_config.run_with_reboot, KioskSessionRestartReason::kPluginCrashed,
      KioskSessionRestartReason::kPluginCrashedWithReboot, histogram());
}

TEST_P(KioskBrowserSessionRestartReasonTest, PluginHungMetric) {
  const KioskSessionRestartTestCase& test_config = GetParam();
  // Create a fake power manager client.
  // FakePowerManagerClient client;
  StartWebKioskSession();

  KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();
  delegate->OnPluginHung(std::set<int>());

  // Emulate exiting the kiosk session.
  CloseMainBrowser();
  EXPECT_TRUE(IsSessionShuttingDown());
  if (test_config.run_with_reboot) {
    EmulateDeviceReboot();
  }
  histogram()->ExpectTotalCount(kKioskSessionRestartReasonHistogram, 0);

  StartWebKioskSession();

  CheckSessionRestartReasonHistogramDependingOnRebootStatus(
      test_config.run_with_reboot, KioskSessionRestartReason::kPluginHung,
      KioskSessionRestartReason::kPluginHungWithReboot, histogram());
}
#endif  // BUILDFLAG(ENABLE_PLUGINS)

// Reboot-related restart reasons are only reported in Ash.
INSTANTIATE_TEST_SUITE_P(
    KioskBrowserSessionRestartReasons,
    KioskBrowserSessionRestartReasonTest,
    testing::ValuesIn<KioskSessionRestartTestCase>({
#if BUILDFLAG(IS_CHROMEOS_ASH)
        {/*test_name=*/"WithReboot", /*run_with_reboot=*/true},
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
        {/*test_name=*/"WithoutReboot", /*run_with_reboot=*/false},
    }),
    [](const testing::TestParamInfo<
        KioskBrowserSessionRestartReasonTest::ParamType>& info) {
      return info.param.test_name;
    });

TEST_F(KioskBrowserSessionRestartReasonTest, PowerManagerRequestRestart) {
  std::vector<KioskSessionPowerManagerRequestRestartTestCase> test_cases = {
      {/*power_manager_reason=*/power_manager::RequestRestartReason::
           REQUEST_RESTART_SCHEDULED_REBOOT_POLICY,
       /*restart_reason=*/KioskSessionRestartReason::kRebootPolicy},
      {/*power_manager_reason=*/power_manager::RequestRestartReason::
           REQUEST_RESTART_REMOTE_ACTION_REBOOT,
       /*restart_reason=*/KioskSessionRestartReason::kRemoteActionReboot},
      {/*power_manager_reason=*/power_manager::RequestRestartReason::
           REQUEST_RESTART_API,
       /*restart_reason=*/KioskSessionRestartReason::kRestartApi}};

  for (auto test_case : test_cases) {
    StartWebKioskSession();
    chromeos::FakePowerManagerClient::Get()->RequestRestart(
        test_case.power_manager_reason, "test reboot description");
    // Emulate exiting the kiosk session.
    CloseMainBrowser();
    EXPECT_TRUE(IsSessionShuttingDown());

    StartWebKioskSession();

    histogram()->ExpectBucketCount(kKioskSessionRestartReasonHistogram,
                                   test_case.restart_reason, 1);
  }
}

// Kiosk type agnostic test class. Runs all tests for web and chrome app kiosks.
class KioskBrowserSessionTroubleshootingTest
    : public KioskBrowserSessionBaseTest<bool> {
 public:
  void SetUpKioskSession() {
    if (is_web_kiosk()) {
      StartWebKioskSession();
    } else {
      StartChromeAppKioskSession();
    }
  }

  void UpdateTroubleshootingToolsPolicy(bool enable) {
    GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, enable);
  }

  std::unique_ptr<FakeBrowser> CreateDevToolsBrowserWithTestWindow() {
    auto params = Browser::CreateParams::CreateForDevTools(profile());

    auto test_window = std::make_unique<TestBrowserWindow>();
    params.window = test_window.get();
    // Self deleting.
    new TestBrowserWindowOwner(std::move(test_window));

    return std::make_unique<FakeBrowser>(params);
  }

  std::unique_ptr<FakeBrowser> CreateRegularBrowserWithTestWindow() {
    return CreateBrowserWithTestWindowAndType(Browser::TYPE_NORMAL);
  }

  std::unique_ptr<FakeBrowser> CreateBrowserWithTestWindowAndType(
      Browser::Type type) {
    Browser::CreateParams params(profile(), /*user_gesture=*/true);
    params.type = type;
    return std::make_unique<FakeBrowser>(
        CreateBrowserWithTestWindowForParams(params).release());
  }

 private:
  bool is_web_kiosk() const { return GetParam(); }
};

TEST_P(KioskBrowserSessionTroubleshootingTest,
       EnableTroubleshootingToolsDuringSession) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  // Kiosk session should shoutdown only if policy is changed from enable to
  // disable.
  EXPECT_FALSE(IsSessionShuttingDown());

  UpdateTroubleshootingToolsPolicy(/*enable=*/false);
  EXPECT_TRUE(IsSessionShuttingDown());
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       EnableTroubleshootingToolsBeforeSessionStarted) {
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);
  SetUpKioskSession();

  UpdateTroubleshootingToolsPolicy(/*enable=*/false);
  EXPECT_TRUE(IsSessionShuttingDown());
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       MainBrowserShutdownAfterKioskTroubleshootingToolsDisabled) {
  GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, true);

  SetUpKioskSession();

  GetPrefs()->SetBoolean(prefs::kKioskTroubleshootingToolsEnabled, false);

  EXPECT_TRUE(IsSessionShuttingDown());

  CloseMainBrowser();

  EXPECT_TRUE(IsSessionShuttingDown());
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       OpenDevToolsEnabledTroubleshootingTools) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  EXPECT_FALSE(DidSessionCloseNewWindow(CreateDevToolsBrowserWithTestWindow()));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kOpenedDevToolsBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       CloseTroubleshootingToolsByDefault) {
  SetUpKioskSession();

  // Kiosk troubleshooting tools are disabled by default.
  EXPECT_TRUE(DidSessionCloseNewWindow(CreateDevToolsBrowserWithTestWindow()));
  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kClosedRegularBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);

  EXPECT_TRUE(DidSessionCloseNewWindow(CreateRegularBrowserWithTestWindow()));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kClosedRegularBrowser,
                                 2);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 2);
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       OpenDevToolsDisableTroubleshootingToolsDuringSession) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  // Kiosk session should shoutdown only if policy is changed from enable to
  // disable.
  EXPECT_FALSE(IsSessionShuttingDown());
  EXPECT_FALSE(DidSessionCloseNewWindow(CreateDevToolsBrowserWithTestWindow()));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kOpenedDevToolsBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);

  UpdateTroubleshootingToolsPolicy(/*enable=*/false);
  EXPECT_TRUE(IsSessionShuttingDown());
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       OpenNewWindowEnabledTroubleshootingTools) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  std::unique_ptr<FakeBrowser> normal_browser =
      CreateRegularBrowserWithTestWindow();
  EXPECT_FALSE(DidSessionCloseNewWindow(*normal_browser));

  histogram()->ExpectBucketCount(
      kKioskNewBrowserWindowHistogram,
      KioskBrowserWindowType::kOpenedTroubleshootingNormalBrowser, 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       CloseNewWindowDisabledTroubleshootingTools) {
  UpdateTroubleshootingToolsPolicy(/*enable=*/false);
  SetUpKioskSession();

  EXPECT_TRUE(DidSessionCloseNewWindow(CreateRegularBrowserWithTestWindow()));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kClosedRegularBrowser,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

TEST_P(KioskBrowserSessionTroubleshootingTest,
       OnlyAllowRegularBrowserAndDevToolsAsTroubleshootingBrowsers) {
  const std::vector<Browser::Type> should_be_closed_browser_types = {
      Browser::Type::TYPE_POPUP,        Browser::Type::TYPE_APP,
      Browser::Type::TYPE_APP_POPUP,
#if BUILDFLAG(IS_CHROMEOS_ASH)
      Browser::Type::TYPE_CUSTOM_TAB,
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
      Browser::TYPE_PICTURE_IN_PICTURE,
  };
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  for (Browser::Type type : should_be_closed_browser_types) {
    EXPECT_TRUE(
        DidSessionCloseNewWindow(CreateBrowserWithTestWindowAndType(type)));
  }

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kClosedRegularBrowser,
                                 should_be_closed_browser_types.size());
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram,
                                should_be_closed_browser_types.size());
}

INSTANTIATE_TEST_SUITE_P(KioskBrowserSessionTroubleshootingTools,
                         KioskBrowserSessionTroubleshootingTest,
                         ::testing::Bool());

#if BUILDFLAG(IS_CHROMEOS_ASH)
class FakeNewWindowDelegate : public ash::TestNewWindowDelegate {
 public:
  FakeNewWindowDelegate() = default;
  ~FakeNewWindowDelegate() override = default;

  void NewWindow(bool incognito, bool should_trigger_session_restore) override {
    new_window_called_ = true;
  }

  void NewTab() override { new_tab_called_ = true; }

  void ShowTaskManager() override { task_manager_called_ = true; }

  void OpenFeedbackPage(FeedbackSource source,
                        const std::string& description_template) override {
    open_feedback_page_called_ = true;
  }

  bool is_new_window_called() const { return new_window_called_; }
  bool is_new_tab_called() const { return new_tab_called_; }
  bool is_task_manager_called() const { return task_manager_called_; }
  bool is_open_feedback_page_called() const {
    return open_feedback_page_called_;
  }

 private:
  bool new_window_called_ = false;
  bool new_tab_called_ = false;
  bool task_manager_called_ = false;
  bool open_feedback_page_called_ = false;
};

// Tests actions after pressing troubleshooting shortcuts. Runs all tests for
// web and chrome app kiosks.
class KioskBrowserSessionTroubleshootingShortcutsTest
    : public KioskBrowserSessionTroubleshootingTest {
 public:
  static bool ProcessInController(const ui::Accelerator& accelerator) {
    return ash::Shell::Get()->accelerator_controller()->Process(accelerator);
  }

  void SetUp() override {
    auto new_window_delegate = std::make_unique<FakeNewWindowDelegate>();
    fake_new_window_delegate_ = new_window_delegate.get();
    test_new_window_delegate_provider_ =
        std::make_unique<ash::TestNewWindowDelegateProvider>(
            std::move(new_window_delegate));

    KioskBrowserSessionTroubleshootingTest::SetUp();

    ash::SessionInfo info;
    info.is_running_in_app_mode = true;
    info.state = session_manager::SessionState::ACTIVE;
    ash::Shell::Get()->session_controller()->SetSessionInfo(info);
  }

  bool IsOverviewToggled() const {
    ash::OverviewController* overview_controller =
        ash::Shell::Get()->overview_controller();
    return overview_controller->InOverviewSession();
  }

  bool IsNewWindowCalled() const {
    return fake_new_window_delegate_->is_new_window_called();
  }

  bool IsNewTabCalled() const {
    return fake_new_window_delegate_->is_new_tab_called();
  }

  bool IsTaskManagerCalled() const {
    return fake_new_window_delegate_->is_task_manager_called();
  }

  bool IsOpenFeedbackPageCalled() const {
    return fake_new_window_delegate_->is_open_feedback_page_called();
  }

 protected:
  ui::Accelerator new_window_accelerator =
      ui::Accelerator(ui::VKEY_N, ui::EF_CONTROL_DOWN);
  ui::Accelerator task_manager_accelerator =
      ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN);
  ui::Accelerator open_feedback_page_accelerator =
      ui::Accelerator(ui::VKEY_I, ui::EF_SHIFT_DOWN | ui::EF_ALT_DOWN);
  ui::Accelerator toggle_overview_accelerator =
      ui::Accelerator(ui::VKEY_MEDIA_LAUNCH_APP1, ui::EF_NONE);

 private:
  raw_ptr<FakeNewWindowDelegate, DanglingUntriaged> fake_new_window_delegate_;
  std::unique_ptr<ash::TestNewWindowDelegateProvider>
      test_new_window_delegate_provider_;
};

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       NewWindowShortcutEnabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  ProcessInController(new_window_accelerator);
  EXPECT_TRUE(IsNewWindowCalled());
}

// Just confirm that other shortcuts (e.g. new tab) do not work.
TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       NewTabShortcutIsNoAction) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  ProcessInController(ui::Accelerator(ui::VKEY_T, ui::EF_CONTROL_DOWN));
  EXPECT_FALSE(IsNewTabCalled());
  EXPECT_FALSE(IsNewWindowCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       NewWindowShortcutNoActionByDefault) {
  SetUpKioskSession();

  ProcessInController(new_window_accelerator);
  EXPECT_FALSE(IsNewWindowCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       NewWindowShortcutNoActionIfPolicyDisabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/false);

  ProcessInController(new_window_accelerator);
  EXPECT_FALSE(IsNewWindowCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       TaskManagerShortcutEnabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  ProcessInController(task_manager_accelerator);
  EXPECT_TRUE(IsTaskManagerCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       TaskManagerShortcutNoActionByDefault) {
  SetUpKioskSession();

  ProcessInController(task_manager_accelerator);
  EXPECT_FALSE(IsTaskManagerCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       TaskManagerShortcutNoActionIfPolicyDisabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/false);

  ProcessInController(task_manager_accelerator);
  EXPECT_FALSE(IsTaskManagerCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       OpenFeedbackPageShortcutEnabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  ProcessInController(open_feedback_page_accelerator);
  EXPECT_TRUE(IsOpenFeedbackPageCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       OpenFeedbackPageShortcutNoActionByDefault) {
  SetUpKioskSession();

  ProcessInController(open_feedback_page_accelerator);
  EXPECT_FALSE(IsOpenFeedbackPageCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       OpenFeedbackPageShortcutNoActionIfPolicyDisabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/false);

  ProcessInController(open_feedback_page_accelerator);
  EXPECT_FALSE(IsOpenFeedbackPageCalled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       ToggleOverviewShortcutEnabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/true);

  ProcessInController(toggle_overview_accelerator);
  EXPECT_TRUE(IsOverviewToggled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       ToggleOverviewShortcutNoActionByDefault) {
  SetUpKioskSession();

  ProcessInController(toggle_overview_accelerator);
  EXPECT_FALSE(IsOverviewToggled());
}

TEST_P(KioskBrowserSessionTroubleshootingShortcutsTest,
       ToggleOverviewShortcutNoActionIfPolicyDisabled) {
  SetUpKioskSession();
  UpdateTroubleshootingToolsPolicy(/*enable=*/false);

  ProcessInController(toggle_overview_accelerator);
  EXPECT_FALSE(IsOverviewToggled());
}

INSTANTIATE_TEST_SUITE_P(KioskBrowserSessionTroubleshootingShortcuts,
                         KioskBrowserSessionTroubleshootingShortcutsTest,
                         ::testing::Bool());

// Kiosk type agnostic test class. Runs all tests for web and chrome app kiosks.
// Test only the case when system web apps are enabled in the Kiosk session.
// If system web apps are disabled in the Kiosk session, the system web app
// browser cannot be created.
class KioskBrowserSessionSwaTest : public KioskBrowserSessionBaseTest<bool> {
 public:
  void SetUp() override {
    scoped_feature_list_.InitAndEnableFeature(
        ash::features::kKioskEnableSystemWebApps);
    KioskBrowserSessionBaseTest<bool>::SetUp();
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
  }
  void SetUpKioskSession() {
    if (is_web_kiosk()) {
      StartWebKioskSession();
    } else {
      StartChromeAppKioskSession();
    }
  }

  void StartAndWaitForAppsToSynchronize() {
    system_web_app_manager()->ResetForTesting();
    SystemWebAppWaiter waiter(system_web_app_manager());
    system_web_app_manager()->Start();
    waiter.Wait();
  }

  std::unique_ptr<FakeBrowser> CreateSwaTestWindow() {
    auto app_id = system_web_app_manager()->GetAppIdForSystemApp(
        ash::SystemWebAppType::SETTINGS);
    CHECK(app_id.has_value());

    auto params = Browser::CreateParams::CreateForApp(
        web_app::GenerateApplicationNameFromAppId(app_id.value()),
        /*trusted_source=*/true,
        /*window_bounds=*/gfx::Rect(), profile(),
        /*user_gesture=*/true);

    auto test_window = std::make_unique<TestBrowserWindow>();
    params.window = test_window.get();
    // Self deleting.
    new TestBrowserWindowOwner(std::move(test_window));

    return std::make_unique<FakeBrowser>(params);
  }

  ash::TestSystemWebAppManager* system_web_app_manager() {
    return ash::TestSystemWebAppManager::Get(profile());
    ;
  }

 private:
  bool is_web_kiosk() const { return GetParam(); }

  base::test::ScopedFeatureList scoped_feature_list_;
};

TEST_P(KioskBrowserSessionSwaTest, OpenSystemWebApp) {
  SetUpKioskSession();

  ash::SystemWebAppDelegateMap system_apps;
  system_apps.emplace(
      ash::SystemWebAppType::SETTINGS,
      std::make_unique<ash::UnittestingSystemAppDelegate>(
          ash::SystemWebAppType::SETTINGS, "OSSettings",
          GURL(content::GetWebUIURL("system-app")), GetAppWebAppInfoFactory()));
  CHECK(system_web_app_manager());
  system_web_app_manager()->SetSystemAppsForTesting(std::move(system_apps));
  StartAndWaitForAppsToSynchronize();

  EXPECT_FALSE(DidSessionCloseNewWindow(CreateSwaTestWindow()));

  histogram()->ExpectBucketCount(kKioskNewBrowserWindowHistogram,
                                 KioskBrowserWindowType::kOpenedSystemWebApp,
                                 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

// Test only the case when system web apps are enabled in the Kiosk session.
// If system web apps are disabled in the Kiosk session, the system web app
// browser cannot be created.
INSTANTIATE_TEST_SUITE_P(KioskBrowserSessionSwa,
                         KioskBrowserSessionSwaTest,
                         ::testing::Bool());

#endif  //  BUILDFLAG(IS_CHROMEOS_ASH)

#if BUILDFLAG(ENABLE_PLUGINS)
TEST_F(KioskBrowserSessionTest, ShouldHandlePlugin) {
  // Create an out-of-process pepper plugin.
  content::WebPluginInfo info1;
  info1.name = kPepperPluginName1;
  info1.path = base::FilePath(kPepperPluginFilePath1);
  info1.type = content::WebPluginInfo::PLUGIN_TYPE_PEPPER_OUT_OF_PROCESS;

  // Create an in-of-process pepper plugin.
  content::WebPluginInfo info2;
  info2.name = kPepperPluginName2;
  info2.path = base::FilePath(kPepperPluginFilePath2);
  info2.type = content::WebPluginInfo::PLUGIN_TYPE_PEPPER_IN_PROCESS;

  // Create an in-of-process browser (non-pepper) plugin.
  content::WebPluginInfo info3;
  info3.name = kBrowserPluginName;
  info3.path = base::FilePath(kBrowserPluginFilePath);
  info3.type = content::WebPluginInfo::PLUGIN_TYPE_BROWSER_PLUGIN;

  // Register two pepper plugins.
  content::PluginService* service = content::PluginService::GetInstance();
  service->RegisterInternalPlugin(info1, true);
  service->RegisterInternalPlugin(info2, true);
  service->RegisterInternalPlugin(info3, true);
  service->Init();
  service->RefreshPlugins();

  // Force plugins to load and wait for completion.
  base::RunLoop run_loop;
  service->GetPlugins(base::BindOnce(
      [](base::OnceClosure callback,
         const std::vector<content::WebPluginInfo>& ignore) {
        std::move(callback).Run();
      },
      run_loop.QuitClosure()));
  run_loop.Run();

  KioskBrowserSession session(profile());
  KioskSessionPluginHandlerDelegate* delegate =
      session.GetPluginHandlerDelegateForTesting();

  // The app session should handle two pepper plugins.
  EXPECT_TRUE(
      delegate->ShouldHandlePlugin(base::FilePath(kPepperPluginFilePath1)));
  EXPECT_TRUE(
      delegate->ShouldHandlePlugin(base::FilePath(kPepperPluginFilePath2)));

  // The app session should not handle the browser plugin.
  EXPECT_FALSE(
      delegate->ShouldHandlePlugin(base::FilePath(kBrowserPluginFilePath)));

  // The app session should not handle the unregistered plugin.
  EXPECT_FALSE(delegate->ShouldHandlePlugin(
      base::FilePath(kUnregisteredPluginFilePath)));
}

TEST_F(KioskBrowserSessionTest, OnPluginCrashed) {
  StartWebKioskSession();
  KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();

  // Verified the number of restart calls.
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 0);
  delegate->OnPluginCrashed(base::FilePath(kBrowserPluginFilePath));
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);

  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kPluginCrashed, 1);
  EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
}

TEST_F(KioskBrowserSessionTest, OnPluginHung) {
  StartWebKioskSession();
  KioskSessionPluginHandlerDelegate* delegate = GetPluginHandlerDelegate();

  // Only verify if this method can be called without error.
  delegate->OnPluginHung(std::set<int>());
  histogram()->ExpectBucketCount(kKioskSessionStateHistogram,
                                 KioskSessionState::kPluginHung, 1);
  EXPECT_EQ(2u, histogram()->GetAllSamples(kKioskSessionStateHistogram).size());
}
#endif  // BUILDFLAG(ENABLE_PLUGINS)

// TODO(b/325648738): add KioskBrowserSessionDeathTest to check kiosk session
// crash when unexpected browser is not closed.

#if BUILDFLAG(IS_CHROMEOS_ASH)

class KioskBrowserSessionAshWithLacrosEnabledTest
    : public KioskBrowserSessionTest {
 public:
  void SetUp() override {
    scoped_feature_list_.InitWithFeatures(
        ash::standalone_browser::GetFeatureRefs(), {});
    scoped_command_line_.GetProcessCommandLine()->AppendSwitch(
        ash::switches::kEnableLacrosForTesting);
    KioskBrowserSessionTest::SetUp();
    LoginKioskUser(CreateKioskAppId());
  }

  void TearDown() override { CloseMainBrowser(); }

 private:
  ash::KioskAppId CreateKioskAppId() {
    std::string email = policy::GenerateDeviceLocalAccountUserId(
        kTestWebAppUrl, policy::DeviceLocalAccountType::kWebKioskApp);
    AccountId account_id(AccountId::FromUserEmail(email));
    return ash::KioskAppId::ForWebApp(account_id);
  }

  void LoginKioskUser(ash::KioskAppId kiosk_app_id) {
    fake_user_manager_->AddWebKioskAppUser(kiosk_app_id.account_id);
    fake_user_manager_->LoginUser(kiosk_app_id.account_id);
  }

  base::test::ScopedFeatureList scoped_feature_list_;
  base::test::ScopedCommandLine scoped_command_line_;
  user_manager::TypedScopedUserManager<ash::FakeChromeUserManager>
      fake_user_manager_{std::make_unique<ash::FakeChromeUserManager>()};
};

TEST_F(KioskBrowserSessionAshWithLacrosEnabledTest,
       AshBrowserShouldGetClosedIfLacrosIsEnabled) {
  StartWebKioskSession(kTestWebAppName1);

  DidSessionCloseNewWindow(CreateBrowserForWebApp(kTestWebAppName1));

  histogram()->ExpectBucketCount(
      kKioskNewBrowserWindowHistogram,
      KioskBrowserWindowType::kClosedAshBrowserWithLacrosEnabled, 1);
  histogram()->ExpectTotalCount(kKioskNewBrowserWindowHistogram, 1);
}

#endif
}  // namespace chromeos