| // Copyright 2024 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/app_mode/test/kiosk_test_utils.h" |
| |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| #include "apps/test/app_window_waiter.h" |
| #include "ash/public/cpp/login_accelerators.h" |
| #include "ash/public/cpp/login_screen_test_api.h" |
| #include "ash/webui/settings/public/constants/routes.mojom.h" |
| #include "base/auto_reset.h" |
| #include "base/check.h" |
| #include "base/check_deref.h" |
| #include "base/check_op.h" |
| #include "base/functional/function_ref.h" |
| #include "base/notimplemented.h" |
| #include "base/scoped_multi_source_observation.h" |
| #include "base/test/test_future.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "chrome/browser/ash/app_mode/isolated_web_app/kiosk_iwa_manager.h" |
| #include "chrome/browser/ash/app_mode/kiosk_app.h" |
| #include "chrome/browser/ash/app_mode/kiosk_app_manager_base.h" |
| #include "chrome/browser/ash/app_mode/kiosk_app_manager_observer.h" |
| #include "chrome/browser/ash/app_mode/kiosk_app_types.h" |
| #include "chrome/browser/ash/app_mode/kiosk_chrome_app_manager.h" |
| #include "chrome/browser/ash/app_mode/kiosk_controller.h" |
| #include "chrome/browser/ash/app_mode/kiosk_system_session.h" |
| #include "chrome/browser/ash/app_mode/kiosk_test_helper.h" |
| #include "chrome/browser/ash/app_mode/web_app/web_kiosk_app_manager.h" |
| #include "chrome/browser/ash/login/test/oobe_screen_waiter.h" |
| #include "chrome/browser/chromeos/app_mode/kiosk_web_app_install_util.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/ash/login/login_display_host.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/settings_window_manager_chromeos.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/webui/ash/login/app_launch_splash_screen_handler.h" |
| #include "chrome/browser/ui/webui/ash/login/error_screen_handler.h" |
| #include "chrome/browser/web_applications/web_app_helpers.h" |
| #include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h" |
| #include "chromeos/ash/components/policy/device_local_account/device_local_account_type.h" |
| #include "components/policy/core/common/cloud/cloud_policy_constants.h" |
| #include "components/policy/core/common/cloud/test/policy_builder.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/extension.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/events/keycodes/keyboard_codes_posix.h" |
| #include "url/gurl.h" |
| |
| namespace ash::kiosk::test { |
| |
| namespace { |
| |
| const char kTestUrl[] = "https://www.test.com"; |
| |
| const extensions::Extension* FindInExtensionRegistry(Profile& profile, |
| std::string_view app_id) { |
| return extensions::ExtensionRegistry::Get(&profile)->GetInstalledExtension( |
| std::string(app_id)); |
| } |
| |
| // Helper to wait for `KioskSystemSession` to be initialized. This happens late |
| // during Kiosk launch. |
| class SessionInitializedWaiter : public KioskAppManagerObserver { |
| public: |
| SessionInitializedWaiter() { |
| observation_.AddObservation(KioskChromeAppManager::Get()); |
| observation_.AddObservation(WebKioskAppManager::Get()); |
| observation_.AddObservation(KioskIwaManager::Get()); |
| } |
| SessionInitializedWaiter(const SessionInitializedWaiter&) = delete; |
| SessionInitializedWaiter& operator=(const SessionInitializedWaiter&) = delete; |
| ~SessionInitializedWaiter() override = default; |
| |
| bool Wait() { return future_.Wait(); } |
| |
| private: |
| // KioskAppManagerObserver: |
| void OnKioskSessionInitialized() override { future_.SetValue(); } |
| |
| base::test::TestFuture<void> future_; |
| |
| base::ScopedMultiSourceObservation<KioskAppManagerBase, |
| KioskAppManagerObserver> |
| observation_{this}; |
| }; |
| |
| content::WebContents* GetActiveWebContents(const Browser& browser) { |
| return browser.tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| void AddWebContentsToBrowser(Browser& browser, Profile& profile) { |
| std::unique_ptr<content::WebContents> web_contents = |
| content::WebContents::Create( |
| content::WebContents::CreateParams(&profile)); |
| |
| browser.tab_strip_model()->AddWebContents(std::move(web_contents), -1, |
| ui::PAGE_TRANSITION_FIRST, |
| AddTabTypes::ADD_ACTIVE); |
| } |
| |
| void TriggerNavigation(content::WebContents* web_contents) { |
| web_contents->GetController().LoadURLWithParams( |
| content::NavigationController::LoadURLParams(GURL(kTestUrl))); |
| } |
| |
| } // namespace |
| |
| KioskApp AutoLaunchKioskApp() { |
| auto app = KioskController::Get().GetAutoLaunchApp(); |
| CHECK(app.has_value()); |
| return app.value(); |
| } |
| |
| KioskApp TheKioskApp() { |
| auto apps = KioskController::Get().GetApps(); |
| CHECK_EQ(apps.size(), 1ul); |
| return apps[0]; |
| } |
| |
| KioskApp TheKioskChromeApp() { |
| auto app = TheKioskApp(); |
| CHECK_EQ(app.id().type, KioskAppType::kChromeApp); |
| return app; |
| } |
| |
| KioskApp TheKioskWebApp() { |
| auto app = TheKioskApp(); |
| CHECK_EQ(app.id().type, KioskAppType::kWebApp); |
| return app; |
| } |
| |
| std::optional<KioskApp> GetAppByAccountId(std::string_view account_id) { |
| // We don't know which app type `account_id` refers to at this point. Create a |
| // device local account ID for each app type and see which one exists. |
| auto chrome_app_account_id = CreateDeviceLocalAccountId( |
| account_id, policy::DeviceLocalAccountType::kKioskApp); |
| auto web_app_account_id = CreateDeviceLocalAccountId( |
| account_id, policy::DeviceLocalAccountType::kWebKioskApp); |
| auto iwa_account_id = CreateDeviceLocalAccountId( |
| account_id, policy::DeviceLocalAccountType::kKioskIsolatedWebApp); |
| for (const auto& app : KioskController::Get().GetApps()) { |
| switch (app.id().type) { |
| case KioskAppType::kChromeApp: |
| if (app.id().account_id == chrome_app_account_id) { |
| return app; |
| } |
| break; |
| case KioskAppType::kWebApp: |
| if (app.id().account_id == web_app_account_id) { |
| return app; |
| } |
| break; |
| case KioskAppType::kIsolatedWebApp: |
| if (app.id().account_id == iwa_account_id) { |
| return app; |
| } |
| break; |
| case KioskAppType::kArcvmApp: |
| NOTIMPLEMENTED(); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| bool WaitKioskLaunched() { |
| if (KioskController::Get().GetKioskSystemSession() != nullptr) { |
| // Kiosk session already initialized, nothing to wait for. |
| return true; |
| } |
| return SessionInitializedWaiter().Wait(); |
| } |
| bool LaunchAppManually(const KioskApp& app) { |
| switch (app.id().type) { |
| case KioskAppType::kChromeApp: |
| return LoginScreenTestApi::LaunchApp(app.id().app_id.value()); |
| case KioskAppType::kWebApp: |
| case KioskAppType::kIsolatedWebApp: |
| case KioskAppType::kArcvmApp: |
| return LoginScreenTestApi::LaunchApp(app.id().account_id); |
| } |
| } |
| |
| bool LaunchAppManually(std::string_view account_id) { |
| auto app_maybe = GetAppByAccountId(account_id); |
| return app_maybe.has_value() ? LaunchAppManually(app_maybe.value()) : false; |
| } |
| |
| std::optional<base::AutoReset<bool>> BlockKioskLaunch() { |
| return {KioskTestHelper::BlockAppLaunch()}; |
| } |
| |
| bool IsChromeAppInstalled(Profile& profile, const KioskApp& app) { |
| CHECK_EQ(app.id().type, KioskAppType::kChromeApp); |
| return IsChromeAppInstalled(profile, app.id().app_id.value()); |
| } |
| |
| bool IsChromeAppInstalled(Profile& profile, std::string_view app_id) { |
| return FindInExtensionRegistry(profile, app_id) != nullptr; |
| } |
| |
| bool IsWebAppInstalled(Profile& profile, const KioskApp& app) { |
| CHECK_EQ(app.id().type, KioskAppType::kWebApp); |
| return IsWebAppInstalled(profile, app.url().value()); |
| } |
| |
| bool IsWebAppInstalled(Profile& profile, const GURL& install_url) { |
| auto [state, __] = chromeos::GetKioskWebAppInstallState(profile, install_url); |
| return chromeos::WebKioskInstallState::kInstalled == state; |
| } |
| |
| bool IsAppInstalled(Profile& profile, const KioskApp& app) { |
| switch (app.id().type) { |
| case KioskAppType::kChromeApp: |
| return IsChromeAppInstalled(profile, app.id().app_id.value()); |
| case KioskAppType::kWebApp: |
| return IsWebAppInstalled(profile, app.url().value()); |
| case KioskAppType::kIsolatedWebApp: |
| case KioskAppType::kArcvmApp: |
| // TODO(crbug.com/379633748): Support IWA in KioskMixin. |
| NOTIMPLEMENTED(); |
| return false; |
| } |
| } |
| |
| std::string InstalledChromeAppVersion(Profile& profile, const KioskApp& app) { |
| CHECK_EQ(app.id().type, KioskAppType::kChromeApp); |
| return InstalledChromeAppVersion(profile, app.id().app_id.value()); |
| } |
| |
| std::string InstalledChromeAppVersion(Profile& profile, |
| std::string_view app_id) { |
| auto& chrome_app = CHECK_DEREF(FindInExtensionRegistry(profile, app_id)); |
| return chrome_app.version().GetString(); |
| } |
| |
| std::string CachedChromeAppVersion(const KioskApp& app) { |
| return CachedChromeAppVersion(app.id().app_id.value()); |
| } |
| |
| std::string CachedChromeAppVersion(std::string_view app_id) { |
| auto& manager = CHECK_DEREF(KioskChromeAppManager::Get()); |
| auto [_, cached_crx_version] = manager.GetCachedCrx(app_id).value(); |
| return cached_crx_version; |
| } |
| |
| Profile& CurrentProfile() { |
| return CHECK_DEREF(ProfileManager::GetPrimaryUserProfile()); |
| } |
| |
| void WaitSplashScreen() { |
| OobeScreenWaiter(AppLaunchSplashScreenView::kScreenId).Wait(); |
| } |
| |
| void WaitNetworkScreen() { |
| OobeScreenWaiter(ErrorScreenView::kScreenId).Wait(); |
| } |
| |
| bool PressNetworkAccelerator() { |
| return LoginScreenTestApi::PressAccelerator( |
| ui::Accelerator(ui::VKEY_N, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)); |
| } |
| |
| bool PressBailoutAccelerator() { |
| return LoginDisplayHost::default_host()->HandleAccelerator( |
| LoginAcceleratorAction::kAppLaunchBailout); |
| } |
| |
| Browser* OpenA11ySettings(Profile& profile) { |
| auto& session = CHECK_DEREF(KioskController::Get().GetKioskSystemSession()); |
| auto& settings_manager = |
| CHECK_DEREF(chrome::SettingsWindowManager::GetInstance()); |
| |
| settings_manager.ShowOSSettings( |
| &profile, chromeos::settings::mojom::kManageAccessibilitySubpagePath); |
| |
| EXPECT_FALSE(DidKioskCloseNewWindow()); |
| |
| Browser& settings_browser = |
| CHECK_DEREF(session.GetSettingsBrowserForTesting()); |
| return &settings_browser; |
| } |
| |
| bool DidKioskCloseNewWindow() { |
| auto& session = CHECK_DEREF(KioskController::Get().GetKioskSystemSession()); |
| base::test::TestFuture<bool> new_window_closed; |
| session.SetOnHandleBrowserCallbackForTesting( |
| new_window_closed.GetRepeatingCallback()); |
| return new_window_closed.Take(); |
| } |
| |
| void CloseAppWindow(const KioskApp& app) { |
| switch (app.id().type) { |
| case KioskAppType::kChromeApp: { |
| auto& registry = |
| CHECK_DEREF(extensions::AppWindowRegistry::Get(&CurrentProfile())); |
| auto& chrome_app_window = CHECK_DEREF( |
| apps::AppWindowWaiter(®istry, app.id().app_id.value()).Wait()); |
| chrome_app_window.GetBaseWindow()->Close(); |
| break; |
| } |
| case KioskAppType::kWebApp: |
| case KioskAppType::kIsolatedWebApp: { |
| EXPECT_GE(BrowserList::GetInstance()->size(), 1u); |
| auto& web_app_browser = CHECK_DEREF(BrowserList::GetInstance()->get(0)); |
| web_app_browser.window()->Close(); |
| break; |
| } |
| case KioskAppType::kArcvmApp: |
| NOTIMPLEMENTED(); |
| break; |
| } |
| } |
| |
| void CachePolicy(const std::string& account_id, |
| base::FunctionRef<void(policy::UserPolicyBuilder&)> setup) { |
| policy::UserPolicyBuilder builder; |
| builder.policy_data().set_policy_type( |
| policy::dm_protocol::kChromePublicAccountPolicyType); |
| builder.policy_data().set_username(account_id); |
| builder.SetDefaultSigningKey(); |
| |
| setup(builder); |
| builder.Build(); |
| |
| const std::string policy_blob = builder.GetBlob(); |
| auto& manager = CHECK_DEREF(FakeSessionManagerClient::Get()); |
| manager.set_device_local_account_policy(account_id, policy_blob); |
| manager.device_local_account_policy(account_id); |
| } |
| |
| AccountId CreateDeviceLocalAccountId(std::string_view account_id, |
| policy::DeviceLocalAccountType type) { |
| return AccountId(AccountId::FromUserEmail( |
| policy::GenerateDeviceLocalAccountUserId(account_id, type))); |
| } |
| |
| Browser& CreateRegularBrowser(Profile& profile) { |
| Browser::CreateParams params(&profile, /*user_gesture=*/true); |
| Browser& browser = CHECK_DEREF(Browser::Create(params)); |
| browser.window()->Show(); |
| |
| AddWebContentsToBrowser(browser, profile); |
| TriggerNavigation(GetActiveWebContents(browser)); |
| |
| return browser; |
| } |
| |
| Browser& CreatePopupBrowser(Profile& profile, const std::string& app_name) { |
| Browser::CreateParams params = Browser::CreateParams::CreateForAppPopup( |
| app_name, |
| /*trusted_source=*/true, |
| /*window_bounds=*/gfx::Rect(), &profile, |
| /*user_gesture=*/true); |
| Browser& browser = CHECK_DEREF(Browser::Create(params)); |
| browser.window()->Show(); |
| |
| AddWebContentsToBrowser(browser, profile); |
| TriggerNavigation(GetActiveWebContents(browser)); |
| |
| return browser; |
| } |
| |
| } // namespace ash::kiosk::test |