chromium/chrome/browser/lacros/app_mode/kiosk_session_service_browsertest.cc

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

#include <memory>

#include "base/auto_reset.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/test/test_future.h"
#include "chrome/browser/chromeos/app_mode/kiosk_browser_session.h"
#include "chrome/browser/extensions/extension_special_storage_policy.h"
#include "chrome/browser/lacros/app_mode/kiosk_session_service_lacros.h"
#include "chrome/browser/lacros/app_mode/web_kiosk_installer_lacros.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/test/test_browser_closed_waiter.h"
#include "chrome/browser/ui/web_applications/web_app_launch_utils.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/crosapi/mojom/kiosk_session_service.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "chromeos/startup/browser_init_params.h"
#include "content/public/test/browser_test.h"
#include "ui/base/base_window.h"
#include "ui/base/window_open_disposition.h"
#include "ui/display/screen.h"

using crosapi::mojom::BrowserInitParams;
using crosapi::mojom::BrowserInitParamsPtr;
using crosapi::mojom::CreationResult;
using crosapi::mojom::KioskSessionService;
using crosapi::mojom::SessionType;

namespace {

const char kWebAppUrl[] = "https://www.google.com/";

// Runs the provided callback when the web kiosk is initialized.
class KioskWebSessionInitializedWaiter
    : public KioskSessionServiceLacros::Observer {
 public:
  explicit KioskWebSessionInitializedWaiter(
      base::OnceClosure on_kiosk_web_session_initialized)
      : on_kiosk_web_session_initialized_(
            std::move(on_kiosk_web_session_initialized)) {
    kiosk_session_observation_.Observe(KioskSessionServiceLacros::Get());
  }

  ~KioskWebSessionInitializedWaiter() override = default;

  // KioskSessionServiceLacros::Observer:
  void KioskWebSessionInitialized() override {
    std::move(on_kiosk_web_session_initialized_).Run();
  }

 private:
  base::OnceCallback<void()> on_kiosk_web_session_initialized_;
  base::ScopedObservation<KioskSessionServiceLacros,
                          KioskSessionServiceLacros::Observer>
      kiosk_session_observation_{this};
};

Profile& GetProfile() {
  return CHECK_DEREF(ProfileManager::GetPrimaryUserProfile());
}

void SetBrowserInitParamsForWebKiosk() {
  BrowserInitParamsPtr init_params =
      chromeos::BrowserInitParams::GetForTests()->Clone();
  init_params->session_type = SessionType::kWebKioskSession;
  init_params->device_settings = crosapi::mojom::DeviceSettings::New();
  chromeos::BrowserInitParams::SetInitParamsForTests(std::move(init_params));
}

void CreateKioskAppWindowAndWaitInitialized() {
  base::test::TestFuture<void> kiosk_initialized;
  KioskWebSessionInitializedWaiter waiter(kiosk_initialized.GetCallback());
  Browser::CreateParams params = web_app::CreateParamsForApp(
      kWebAppUrl,
      /*is_popup*/ true,
      /*trusted_source=*/true, /*window_bounds=*/gfx::Rect(), &GetProfile(),
      /*user_gesture=*/true);
  web_app::CreateWebAppWindowMaybeWithHomeTab(kWebAppUrl, params);
  EXPECT_TRUE(kiosk_initialized.Wait());
  EXPECT_NE(
      KioskSessionServiceLacros::Get()->GetKioskBrowserSessionForTesting(),
      nullptr);
}

[[nodiscard]] bool WaitUntilBrowserClosed(Browser* browser) {
  TestBrowserClosedWaiter waiter(browser);
  return waiter.WaitUntilClosed();
}

bool DidSessionCloseNewWindow() {
  chromeos::KioskBrowserSession* session =
      KioskSessionServiceLacros::Get()->GetKioskBrowserSessionForTesting();
  base::test::TestFuture<bool> future;
  session->SetOnHandleBrowserCallbackForTesting(future.GetRepeatingCallback());
  return future.Take();
}

Browser* CreateBrowserWithWindowOfType(WindowOpenDisposition window_type) {
  Browser::CreateParams params = web_app::CreateParamsForApp(
      kWebAppUrl, window_type == WindowOpenDisposition::NEW_POPUP,
      /*trusted_source=*/true, /*window_bounds=*/gfx::Rect(), &GetProfile(),
      /*user_gesture=*/true);
  Browser* browser =
      web_app::CreateWebAppWindowMaybeWithHomeTab(kWebAppUrl, params);
  browser->window()->Show();
  return browser;
}

}  // namespace

class WebKioskSessionServiceBrowserTest : public InProcessBrowserTest {
 protected:
  WebKioskSessionServiceBrowserTest() = default;
  ~WebKioskSessionServiceBrowserTest() override = default;

  void SetUp() override {
    // `SetBrowserInitParamsForWebKiosk` must be called before
    // `InProcessBrowserTest::SetUp` to configure `KioskSessionServiceLacros`
    // correctly.
    SetBrowserInitParamsForWebKiosk();
    InProcessBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    InProcessBrowserTest::SetUpOnMainThread();

    attempt_user_exit_reset_ =
        KioskSessionServiceLacros::Get()->SetAttemptUserExitCallbackForTesting(
            base::DoNothing());

    installer_ = std::make_unique<WebKioskInstallerLacros>(GetProfile());
    InstallWebKiosk(kWebAppUrl);
  }

  void TearDownOnMainThread() override {
    attempt_user_exit_reset_.reset();
    InProcessBrowserTest::TearDownOnMainThread();
  }

  std::optional<webapps::AppId> InstallWebKiosk(const std::string& url) {
    base::test::TestFuture<crosapi::mojom::WebKioskInstallState,
                           const std::optional<webapps::AppId>&>
        install_state;
    installer_->GetWebKioskInstallState(GURL(url), install_state.GetCallback());
    if (install_state.Get<0>() ==
        crosapi::mojom::WebKioskInstallState::kInstalled) {
      return install_state.Get<1>();
    }

    base::test::TestFuture<const std::optional<webapps::AppId>&> install_result;
    installer_->InstallWebKiosk(GURL(url), install_result.GetCallback());
    return install_result.Get();
  }

 private:
  std::unique_ptr<WebKioskInstallerLacros> installer_;
  std::unique_ptr<base::AutoReset<base::OnceClosure>> attempt_user_exit_reset_;
};

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest,
                       BrowserKioskSessionIsCreated) {
  EXPECT_EQ(
      KioskSessionServiceLacros::Get()->GetKioskBrowserSessionForTesting(),
      nullptr);

  CreateKioskAppWindowAndWaitInitialized();
}

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest, VerifyInstallUrl) {
  CreateKioskAppWindowAndWaitInitialized();

  EXPECT_EQ(KioskSessionServiceLacros::Get()->GetInstallURL(),
            GURL(kWebAppUrl));
}

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest,
                       ClosingAllWindowsTriggersAttemptUserExitCall) {
  // Closing all browser windows should trigger `AttemptUserExit`.
  base::test::TestFuture<void> did_attempt_user_exit;
  auto auto_reset =
      KioskSessionServiceLacros::Get()->SetAttemptUserExitCallbackForTesting(
          did_attempt_user_exit.GetCallback());

  CreateKioskAppWindowAndWaitInitialized();
  CloseAllBrowsers();

  EXPECT_TRUE(did_attempt_user_exit.Wait());
  // Verify that all windows have been closed.
  EXPECT_TRUE(BrowserList::GetInstance()->empty());
}

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest,
                       KioskOriginShouldGetUnlimitedStorage) {
  CreateKioskAppWindowAndWaitInitialized();

  // Verify the origin of the install URL has been granted unlimited storage
  EXPECT_TRUE(
      GetProfile().GetExtensionSpecialStoragePolicy()->IsStorageUnlimited(
          GURL(kWebAppUrl)));
}

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest,
                       AnyNewBrowsersInKioskNotAllowedByDefault) {
  EXPECT_FALSE(
      GetProfile().GetPrefs()->GetBoolean(prefs::kNewWindowsInKioskAllowed));
  CreateKioskAppWindowAndWaitInitialized();

  Browser* popup_browser =
      CreateBrowserWithWindowOfType(WindowOpenDisposition::NEW_POPUP);
  EXPECT_TRUE(WaitUntilBrowserClosed(popup_browser));
  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);

  Browser* const new_browser =
      CreateBrowserWithWindowOfType(WindowOpenDisposition::NEW_WINDOW);
  EXPECT_TRUE(WaitUntilBrowserClosed(new_browser));
  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
}

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest,
                       NewPopupBrowserInKioskAllowedByPolicy) {
  GetProfile().GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  CreateKioskAppWindowAndWaitInitialized();

  CreateBrowserWithWindowOfType(WindowOpenDisposition::NEW_POPUP);

  EXPECT_FALSE(DidSessionCloseNewWindow());
  EXPECT_EQ(BrowserList::GetInstance()->size(), 2u);
}

IN_PROC_BROWSER_TEST_F(WebKioskSessionServiceBrowserTest,
                       NewRegularBrowserInKioskNotAllowedEvenByPolicy) {
  GetProfile().GetPrefs()->SetBoolean(prefs::kNewWindowsInKioskAllowed, true);
  CreateKioskAppWindowAndWaitInitialized();

  // `kNewWindowsInKioskAllowed` policy allows to open only popup windows. All
  // other windows will be closed.
  Browser* const new_browser =
      CreateBrowserWithWindowOfType(WindowOpenDisposition::NEW_WINDOW);

  EXPECT_TRUE(WaitUntilBrowserClosed(new_browser));
  EXPECT_EQ(BrowserList::GetInstance()->size(), 1u);
}