| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/web_applications/system_web_app_manager_browsertest.h" |
| |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/apps/app_service/app_launch_params.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy.h" |
| #include "chrome/browser/apps/app_service/app_service_proxy_factory.h" |
| #include "chrome/browser/apps/app_service/browser_app_launcher.h" |
| #include "chrome/browser/native_file_system/native_file_system_permission_request_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/web_applications/system_web_app_ui_utils.h" |
| #include "chrome/browser/web_applications/components/app_registrar.h" |
| #include "chrome/browser/web_applications/components/web_app_constants.h" |
| #include "chrome/browser/web_applications/components/web_app_helpers.h" |
| #include "chrome/browser/web_applications/test/test_system_web_app_installation.h" |
| #include "chrome/browser/web_applications/test/test_web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_tab_helper.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/permissions/permission_util.h" |
| #include "components/services/app_service/public/cpp/app_registry_cache.h" |
| #include "components/services/app_service/public/cpp/app_update.h" |
| #include "components/services/app_service/public/cpp/intent_util.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui.h" |
| #include "content/public/browser/web_ui_controller_factory.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/mock_navigation_handle.h" |
| #include "content/public/test/test_launcher.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "ui/display/types/display_constants.h" |
| #include "url/gurl.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "chrome/browser/apps/app_service/app_icon_factory.h" |
| #include "chrome/browser/chromeos/file_manager/file_manager_test_util.h" |
| #include "chrome/browser/chromeos/policy/system_features_disable_list_policy_handler.h" |
| #include "chrome/browser/ui/app_list/app_list_client_impl.h" |
| #include "chrome/browser/ui/app_list/app_list_model_updater.h" |
| #include "chrome/browser/ui/app_list/test/chrome_app_list_test_support.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #endif |
| |
| namespace { |
| |
| // Helper to call AppServiceProxyFactory::GetForProfile(). |
| apps::AppServiceProxy* GetAppServiceProxy(Profile* profile) { |
| // Crash if there is no AppService support for |profile|. GetForProfile() will |
| // DumpWithoutCrashing, which will not fail a test. No codepath should trigger |
| // that in normal operation. |
| DCHECK( |
| apps::AppServiceProxyFactory::IsAppServiceAvailableForProfile(profile)); |
| return apps::AppServiceProxyFactory::GetForProfile(profile); |
| } |
| |
| } // namespace |
| |
| namespace web_app { |
| |
| SystemWebAppManagerBrowserTestBase::SystemWebAppManagerBrowserTestBase( |
| bool install_mock) {} |
| |
| SystemWebAppManagerBrowserTestBase::~SystemWebAppManagerBrowserTestBase() = |
| default; |
| |
| SystemWebAppManager& SystemWebAppManagerBrowserTestBase::GetManager() { |
| return WebAppProvider::Get(browser()->profile())->system_web_app_manager(); |
| } |
| |
| SystemAppType SystemWebAppManagerBrowserTestBase::GetMockAppType() { |
| CHECK(maybe_installation_); |
| return maybe_installation_->GetType(); |
| } |
| |
| void SystemWebAppManagerBrowserTestBase::WaitForTestSystemAppInstall() { |
| // Wait for the System Web Apps to install. |
| if (maybe_installation_) { |
| maybe_installation_->WaitForAppInstall(); |
| } else { |
| GetManager().InstallSystemAppsForTesting(); |
| } |
| |
| // Ensure apps are registered with the |AppService| and populated in |
| // |AppListModel|. Redirect to the profile that has an AppService that can be |
| // flushed. This logic differs from WebAppProviderFactory::GetContextToUse(). |
| apps::AppServiceProxyFactory::GetForProfileRedirectInIncognito( |
| browser()->profile()) |
| ->FlushMojoCallsForTesting(); |
| } |
| |
| apps::AppLaunchParams SystemWebAppManagerBrowserTestBase::LaunchParamsForApp( |
| SystemAppType system_app_type) { |
| base::Optional<AppId> app_id = |
| GetManager().GetAppIdForSystemApp(system_app_type); |
| |
| CHECK(app_id.has_value()); |
| return apps::AppLaunchParams( |
| *app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow, |
| WindowOpenDisposition::CURRENT_TAB, |
| apps::mojom::AppLaunchSource::kSourceAppLauncher); |
| } |
| |
| content::WebContents* SystemWebAppManagerBrowserTestBase::LaunchApp( |
| apps::AppLaunchParams&& params, |
| bool wait_for_load, |
| Browser** out_browser) { |
| content::TestNavigationObserver navigation_observer(GetStartUrl(params)); |
| navigation_observer.StartWatchingNewWebContents(); |
| |
| content::WebContents* web_contents = |
| GetAppServiceProxy(browser()->profile()) |
| ->BrowserAppLauncher() |
| ->LaunchAppWithParams(std::move(params)); |
| |
| if (wait_for_load) |
| navigation_observer.Wait(); |
| |
| if (out_browser) |
| *out_browser = chrome::FindBrowserWithWebContents(web_contents); |
| |
| return web_contents; |
| } |
| |
| content::WebContents* SystemWebAppManagerBrowserTestBase::LaunchApp( |
| apps::AppLaunchParams&& params, |
| Browser** browser) { |
| return LaunchApp(std::move(params), /* wait_for_load */ true, browser); |
| } |
| |
| content::WebContents* SystemWebAppManagerBrowserTestBase::LaunchApp( |
| SystemAppType type, |
| Browser** browser) { |
| return LaunchApp(LaunchParamsForApp(type), browser); |
| } |
| |
| content::WebContents* |
| SystemWebAppManagerBrowserTestBase::LaunchAppWithoutWaiting( |
| apps::AppLaunchParams&& params, |
| Browser** browser) { |
| return LaunchApp(std::move(params), /* wait_for_load */ false, browser); |
| } |
| |
| content::WebContents* |
| SystemWebAppManagerBrowserTestBase::LaunchAppWithoutWaiting( |
| web_app::SystemAppType type, |
| Browser** browser) { |
| return LaunchAppWithoutWaiting(LaunchParamsForApp(type), browser); |
| } |
| |
| GURL SystemWebAppManagerBrowserTestBase::GetStartUrl( |
| const apps::AppLaunchParams& params) { |
| return params.override_url.is_valid() |
| ? params.override_url |
| : WebAppProvider::Get(browser()->profile()) |
| ->registrar() |
| .GetAppStartUrl(params.app_id); |
| } |
| |
| SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest( |
| bool install_mock) |
| : SystemWebAppManagerBrowserTestBase(install_mock) { |
| if (install_mock) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpStandaloneSingleWindowApp( |
| install_from_web_app_info()); |
| } |
| } |
| |
| void SystemWebAppManagerBrowserTest::SetUpCommandLine( |
| base::CommandLine* command_line) { |
| SystemWebAppManagerBrowserTestBase::SetUpCommandLine(command_line); |
| if (profile_type() == TestProfileType::kGuest) { |
| ConfigureCommandLineForGuestMode(command_line); |
| } else if (profile_type() == TestProfileType::kIncognito) { |
| command_line->AppendSwitch(::switches::kIncognito); |
| } |
| } |
| |
| // Test that System Apps install correctly with a manifest. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerWebAppInfoBrowserTest, Install) { |
| WaitForTestSystemAppInstall(); |
| |
| // Don't wait for page load because we want to verify AppController identifies |
| // the System Web App before when the app loads. |
| Browser* app_browser; |
| LaunchAppWithoutWaiting(GetMockAppType(), &app_browser); |
| |
| AppId app_id = app_browser->app_controller()->GetAppId(); |
| EXPECT_EQ(GetManager().GetAppIdForSystemApp(GetMockAppType()), app_id); |
| EXPECT_TRUE(GetManager().IsSystemWebApp(app_id)); |
| |
| Profile* profile = app_browser->profile(); |
| AppRegistrar& registrar = |
| WebAppProviderBase::GetProviderBase(profile)->registrar(); |
| |
| EXPECT_EQ("Test System App", registrar.GetAppShortName(app_id)); |
| EXPECT_EQ(SkColorSetRGB(0, 0xFF, 0), registrar.GetAppThemeColor(app_id)); |
| EXPECT_TRUE(registrar.HasExternalAppWithInstallSource( |
| app_id, web_app::ExternalInstallSource::kSystemInstalled)); |
| EXPECT_EQ( |
| registrar.FindAppWithUrlInScope(content::GetWebUIURL("test-system-app/")), |
| app_id); |
| |
| // OS Integration only relevant for Chrome OS. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(apps::mojom::OptionalBool::kTrue, update.ShowInLauncher()); |
| EXPECT_EQ(apps::mojom::OptionalBool::kTrue, update.ShowInSearch()); |
| EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInManagement()); |
| EXPECT_EQ(apps::mojom::Readiness::kReady, update.Readiness()); |
| }); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| std::string SystemWebAppManagerTestParamsToString( |
| const ::testing::TestParamInfo<SystemWebAppManagerTestParams>& param_info) { |
| std::string output; |
| |
| if (std::get<0>(param_info.param) == InstallationType::kWebAppInfoInstall) { |
| output.append("_WebAppInfoInstall"); |
| } |
| switch (std::get<1>(param_info.param)) { |
| case TestProfileType::kRegular: |
| break; |
| case TestProfileType::kIncognito: |
| output.append("_Incognito"); |
| break; |
| case TestProfileType::kGuest: |
| output.append("_Guest"); |
| break; |
| } |
| // The framework doesn't accept a blank param |
| if (output.empty()) { |
| output = "_Default"; |
| } |
| return output; |
| } |
| |
| // Check the toolbar is not shown for system web apps for pages on the chrome:// |
| // scheme but is shown off the chrome:// scheme. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerWebAppInfoBrowserTest, |
| ToolbarVisibilityForSystemWebApp) { |
| WaitForTestSystemAppInstall(); |
| |
| // Don't wait for page load because we want to verify the toolbar is hidden |
| // when the window first opens. |
| Browser* app_browser; |
| LaunchAppWithoutWaiting(GetMockAppType(), &app_browser); |
| |
| // In scope, the toolbar should not be visible. |
| EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar()); |
| |
| // Because the first part of the url is on a different origin (settings vs. |
| // foo) a toolbar would normally be shown. However, because settings is a |
| // SystemWebApp and foo is served via chrome:// it is okay not to show the |
| // toolbar. |
| GURL out_of_scope_chrome_page(content::kChromeUIScheme + |
| std::string("://foo")); |
| content::NavigateToURLBlockUntilNavigationsComplete( |
| app_browser->tab_strip_model()->GetActiveWebContents(), |
| out_of_scope_chrome_page, 1); |
| EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar()); |
| |
| // Even though the url is secure it is not being served over chrome:// so a |
| // toolbar should be shown. |
| GURL off_scheme_page("https://example.com"); |
| content::NavigateToURLBlockUntilNavigationsComplete( |
| app_browser->tab_strip_model()->GetActiveWebContents(), off_scheme_page, |
| 1); |
| EXPECT_TRUE(app_browser->app_controller()->ShouldShowCustomTabBar()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerWebAppInfoBrowserTest, |
| LaunchMetricsWork) { |
| WaitForTestSystemAppInstall(); |
| |
| base::HistogramTester histograms; |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.launch_source = apps::mojom::LaunchSource::kFromAppListGrid; |
| |
| content::TestNavigationObserver navigation_observer( |
| maybe_installation_->GetAppUrl()); |
| navigation_observer.StartWatchingNewWebContents(); |
| |
| LaunchSystemWebApp(browser()->profile(), GetMockAppType(), |
| maybe_installation_->GetAppUrl(), std::move(params)); |
| |
| navigation_observer.Wait(); |
| histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromAppListGrid", 1); |
| histograms.ExpectUniqueSample("Apps.DefaultAppLaunch.FromAppListGrid", 39, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerWebAppInfoBrowserTest, |
| LaunchMetricsWorkFromAppProxy) { |
| WaitForTestSystemAppInstall(); |
| |
| base::HistogramTester histograms; |
| content::TestNavigationObserver navigation_observer( |
| maybe_installation_->GetAppUrl()); |
| navigation_observer.StartWatchingNewWebContents(); |
| |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| |
| proxy->Launch(GetManager().GetAppIdForSystemApp(GetMockAppType()).value(), |
| ui::EventFlags::EF_NONE, |
| apps::mojom::LaunchSource::kFromAppListGrid, |
| display::kDefaultDisplayId); |
| navigation_observer.Wait(); |
| |
| histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromAppListGrid", 1); |
| histograms.ExpectUniqueSample("Apps.DefaultAppLaunch.FromAppListGrid", 39, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerWebAppInfoBrowserTest, |
| LaunchMetricsWorkWithIntent) { |
| WaitForTestSystemAppInstall(); |
| |
| base::HistogramTester histograms; |
| content::TestNavigationObserver navigation_observer( |
| maybe_installation_->GetAppUrl()); |
| navigation_observer.StartWatchingNewWebContents(); |
| |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| auto intent = apps::mojom::Intent::New(); |
| intent->action = apps_util::kIntentActionView; |
| intent->mime_type = "text/plain"; |
| |
| proxy->LaunchAppWithIntent( |
| GetManager().GetAppIdForSystemApp(GetMockAppType()).value(), |
| ui::EventFlags::EF_NONE, std::move(intent), |
| apps::mojom::LaunchSource::kFromAppListGrid, display::kDefaultDisplayId); |
| navigation_observer.Wait(); |
| |
| histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromAppListGrid", 1); |
| histograms.ExpectUniqueSample("Apps.DefaultAppLaunch.FromAppListGrid", 39, 1); |
| } |
| |
| // The helper methods in this class uses ExecuteScriptXXX instead of ExecJs and |
| // EvalJs because of some quirks surrounding origin trials and content security |
| // policies. |
| class SystemWebAppManagerFileHandlingBrowserTestBase |
| : public SystemWebAppManagerBrowserTestBase, |
| public ::testing::WithParamInterface<SystemWebAppManagerTestParams> { |
| public: |
| using IncludeLaunchDirectory = |
| TestSystemWebAppInstallation::IncludeLaunchDirectory; |
| |
| explicit SystemWebAppManagerFileHandlingBrowserTestBase( |
| IncludeLaunchDirectory include_launch_directory) |
| : SystemWebAppManagerBrowserTestBase(/*install_mock=*/false) { |
| scoped_feature_blink_api_.InitWithFeatures( |
| {blink::features::kFileHandlingAPI}, {}); |
| |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppThatReceivesLaunchFiles( |
| include_launch_directory, |
| std::get<0>(GetParam()) == InstallationType::kWebAppInfoInstall); |
| } |
| |
| content::WebContents* LaunchApp( |
| const std::vector<base::FilePath> launch_files, |
| bool wait_for_load = true) { |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| params.launch_files = launch_files; |
| |
| return SystemWebAppManagerBrowserTestBase::LaunchApp(std::move(params)); |
| } |
| |
| content::WebContents* LaunchAppWithoutWaiting( |
| const std::vector<base::FilePath> launch_files) { |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| params.launch_files = launch_files; |
| |
| return SystemWebAppManagerBrowserTestBase::LaunchAppWithoutWaiting( |
| std::move(params)); |
| } |
| |
| // Must be called before WaitAndExposeLaunchParamsToWindow. This sets up the |
| // promise used to wait for launchParam callback. |
| bool PrepareToReceiveLaunchParams(content::WebContents* web_contents) { |
| return content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise = new Promise(resolve => {" |
| " window.resolveLaunchParamsPromise = resolve;" |
| "});" |
| "launchQueue.setConsumer(launchParams => {" |
| " window.resolveLaunchParamsPromise(launchParams);" |
| " window.resolveLaunchParamsPromise = null;" |
| "});"); |
| } |
| |
| // Must be called after PrepareToReceiveLaunchParams. This method waits for |
| // launchParams being received, the stores it to a |js_property_name| on JS |
| // window object. |
| bool WaitAndExposeLaunchParamsToWindow( |
| content::WebContents* web_contents, |
| const std::string js_property_name = "launchParams") { |
| bool launch_params_received; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| content::JsReplace("window.launchParamsPromise.then(launchParams => {" |
| " window[$1] = launchParams;" |
| " domAutomationController.send(true);" |
| "});", |
| js_property_name), |
| &launch_params_received)); |
| return launch_params_received; |
| } |
| |
| std::string GetJsStatementValueAsString(content::WebContents* web_contents, |
| std::string js_statement) { |
| std::string str; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, "domAutomationController.send( " + js_statement + ");", |
| &str)); |
| return str; |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_web_app_provider_type_; |
| base::test::ScopedFeatureList scoped_feature_blink_api_; |
| }; |
| |
| class SystemWebAppManagerLaunchFilesBrowserTest |
| : public SystemWebAppManagerFileHandlingBrowserTestBase { |
| public: |
| SystemWebAppManagerLaunchFilesBrowserTest() |
| : SystemWebAppManagerFileHandlingBrowserTestBase( |
| IncludeLaunchDirectory::kNo) {} |
| }; |
| |
| // Check launch files are passed to application. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchFilesBrowserTest, |
| LaunchFilesForSystemWebApp) { |
| WaitForTestSystemAppInstall(); |
| |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir temp_directory; |
| ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); |
| base::FilePath temp_file_path; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(), |
| &temp_file_path)); |
| |
| // First launch. |
| content::WebContents* web_contents = LaunchApp({temp_file_path}); |
| |
| // Check the App is launched with the correct launch file. |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams1")); |
| EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(), |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams1.files[0].name")); |
| |
| // Second launch. |
| base::FilePath temp_file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(), |
| &temp_file_path2)); |
| |
| // The second launch reuses the opened application. It should pass the |
| // launchParams to the opened page, and return the same content::WebContents*. |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_EQ(web_contents, LaunchAppWithoutWaiting({temp_file_path2})); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams2")); |
| |
| // Second launch_files are correct. |
| EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(), |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams2.files[0].name")); |
| } |
| |
| // The helper methods in this class uses ExecuteScriptXXX instead of ExecJs and |
| // EvalJs because of some quirks surrounding origin trials and content security |
| // policies. |
| class SystemWebAppManagerLaunchDirectoryBrowserTest |
| : public SystemWebAppManagerFileHandlingBrowserTestBase { |
| public: |
| SystemWebAppManagerLaunchDirectoryBrowserTest() |
| : SystemWebAppManagerFileHandlingBrowserTestBase( |
| IncludeLaunchDirectory::kYes) {} |
| |
| // Returns the content of |file_handle_or_promise| file handle. |
| std::string ReadContentFromJsFileHandle( |
| content::WebContents* web_contents, |
| const std::string& file_handle_or_promise) { |
| std::string js_file_content; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "Promise.resolve(" + file_handle_or_promise + ")" + |
| ".then(async (fileHandle) => {" |
| " const file = await fileHandle.getFile();" |
| " const content = await file.text();" |
| " window.domAutomationController.send(content);" |
| "});", |
| &js_file_content)); |
| return js_file_content; |
| } |
| |
| // Writes |content_to_write| to |file_handle_or_promise| file handle. Returns |
| // whether JavaScript execution finishes. |
| bool WriteContentToJsFileHandle(content::WebContents* web_contents, |
| const std::string& file_handle_or_promise, |
| const std::string& content_to_write) { |
| bool file_written; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| content::JsReplace( |
| "Promise.resolve(" + file_handle_or_promise + ")" + |
| ".then(async (fileHandle) => {" |
| " const writable = await fileHandle.createWritable();" |
| " await writable.write($1);" |
| " await writable.close();" |
| " window.domAutomationController.send(true);" |
| "});", |
| content_to_write), |
| &file_written)); |
| return file_written; |
| } |
| |
| // Remove file by |file_name| from |dir_handle_or_promise| directory handle. |
| // Returns whether JavaScript execution finishes. |
| bool RemoveFileFromJsDirectoryHandle(content::WebContents* web_contents, |
| const std::string& dir_handle_or_promise, |
| const std::string& file_name) { |
| bool file_removed; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| content::JsReplace("Promise.resolve(" + dir_handle_or_promise + ")" + |
| ".then(async (dir_handle) => {" |
| " await dir_handle.removeEntry($1);" |
| " domAutomationController.send(true);" |
| "});", |
| file_name), |
| &file_removed)); |
| return file_removed; |
| } |
| |
| std::string ReadFileContent(const base::FilePath& path) { |
| std::string content; |
| EXPECT_TRUE(base::ReadFileToString(path, &content)); |
| return content; |
| } |
| |
| // Launch the App with |base_dir| and a file inside this directory, then test |
| // SWA can 1) read and write to the launch file; 2) read and write to other |
| // files inside the launch directory; 3) read and write to the launch |
| // directory (i.e. list and delete files). |
| void TestPermissionsForLaunchDirectory(const base::FilePath& base_dir) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| // Create the launch file, which stores 4 characters "test". |
| base::FilePath launch_file_path; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(base_dir, &launch_file_path)); |
| ASSERT_TRUE(base::WriteFile(launch_file_path, "test")); |
| |
| // Launch the App. |
| content::WebContents* web_contents = LaunchApp({launch_file_path}); |
| |
| // Launch directories and files passed to system web apps should |
| // automatically be granted write permission. Users should not get |
| // permission prompts. So we auto deny them (if they show up). |
| NativeFileSystemPermissionRequestManager::FromWebContents(web_contents) |
| ->set_auto_response_for_test(permissions::PermissionAction::DENIED); |
| |
| // Wait for launchParams. |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents)); |
| |
| // Check we can read and write to the launch file. |
| std::string launch_file_js_handle = "window.launchParams.files[1]"; |
| EXPECT_EQ("test", |
| ReadContentFromJsFileHandle(web_contents, launch_file_js_handle)); |
| EXPECT_TRUE(WriteContentToJsFileHandle(web_contents, launch_file_js_handle, |
| "js_written")); |
| EXPECT_EQ("js_written", ReadFileContent(launch_file_path)); |
| |
| // Check we can read and write to a different file inside the directory. |
| // Note, this also checks we can read the launch directory, using |
| // directory_handle.getFileHandle(). |
| base::FilePath non_launch_file_path; |
| ASSERT_TRUE( |
| base::CreateTemporaryFileInDir(base_dir, &non_launch_file_path)); |
| ASSERT_TRUE(base::WriteFile(non_launch_file_path, "test2")); |
| |
| std::string non_launch_file_js_handle = |
| content::JsReplace("window.launchParams.files[0].getFileHandle($1)", |
| non_launch_file_path.BaseName().AsUTF8Unsafe()); |
| EXPECT_EQ("test2", ReadContentFromJsFileHandle(web_contents, |
| non_launch_file_js_handle)); |
| EXPECT_TRUE(WriteContentToJsFileHandle( |
| web_contents, non_launch_file_js_handle, "js_written2")); |
| EXPECT_EQ("js_written2", ReadFileContent(non_launch_file_path)); |
| |
| // Check the launch file can be deleted. |
| std::string launch_dir_js_handle = "window.launchParams.files[0]"; |
| EXPECT_TRUE(RemoveFileFromJsDirectoryHandle( |
| web_contents, launch_dir_js_handle, |
| launch_file_path.BaseName().AsUTF8Unsafe())); |
| EXPECT_FALSE(base::PathExists(launch_file_path)); |
| |
| // Check the non-launch file can be deleted. |
| EXPECT_TRUE(RemoveFileFromJsDirectoryHandle( |
| web_contents, launch_dir_js_handle, |
| non_launch_file_path.BaseName().AsUTF8Unsafe())); |
| EXPECT_FALSE(base::PathExists(non_launch_file_path)); |
| |
| // Check a file can be created. |
| std::string new_file_js_handle = content::JsReplace( |
| "window.launchParams.files[0].getFileHandle($1, {create:true})", |
| "new_file"); |
| EXPECT_TRUE(WriteContentToJsFileHandle(web_contents, new_file_js_handle, |
| "js_new_file")); |
| EXPECT_EQ("js_new_file", ReadFileContent(base_dir.AppendASCII("new_file"))); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchDirectoryBrowserTest, |
| LaunchDirectoryForSystemWebApp) { |
| WaitForTestSystemAppInstall(); |
| |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir temp_directory; |
| ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); |
| base::FilePath temp_file_path; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(), |
| &temp_file_path)); |
| |
| // First launch. |
| content::WebContents* web_contents = LaunchApp({temp_file_path}); |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams1")); |
| |
| // Check launch directory and launch files are correct. |
| EXPECT_EQ("directory", |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams1.files[0].kind")); |
| EXPECT_EQ(temp_directory.GetPath().BaseName().AsUTF8Unsafe(), |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams1.files[0].name")); |
| EXPECT_EQ("file", GetJsStatementValueAsString( |
| web_contents, "window.launchParams1.files[1].kind")); |
| EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(), |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams1.files[1].name")); |
| |
| // Second launch. |
| base::ScopedTempDir temp_directory2; |
| ASSERT_TRUE(temp_directory2.CreateUniqueTempDir()); |
| base::FilePath temp_file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory2.GetPath(), |
| &temp_file_path2)); |
| |
| // The second launch reuses the opened application. It should pass the |
| // launchParams to the opened page, and return the same content::WebContents*. |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_EQ(web_contents, LaunchAppWithoutWaiting({temp_file_path2})); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams2")); |
| |
| // Check the second launch directory and launch files are correct. |
| EXPECT_EQ("directory", |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams2.files[0].kind")); |
| EXPECT_EQ(temp_directory2.GetPath().BaseName().AsUTF8Unsafe(), |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams2.files[0].name")); |
| EXPECT_EQ("file", GetJsStatementValueAsString( |
| web_contents, "window.launchParams2.files[1].kind")); |
| EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(), |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams2.files[1].name")); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchDirectoryBrowserTest, |
| ReadWritePermissions_OrdinaryDirectory) { |
| WaitForTestSystemAppInstall(); |
| |
| // Test for ordinary directory. |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir temp_directory; |
| ASSERT_TRUE(temp_directory.CreateUniqueTempDir()); |
| TestPermissionsForLaunchDirectory(temp_directory.GetPath()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchDirectoryBrowserTest, |
| ReadWritePermissions_SensitiveDirectory) { |
| WaitForTestSystemAppInstall(); |
| |
| // Test for sensitive directory (which are otherwise blocked by |
| // NativeFileSystem API). It is safe to use |chrome::DIR_DEFAULT_DOWNLOADS|, |
| // because InProcBrowserTest fixture sets up different download directory for |
| // each test cases. |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::FilePath sensitive_dir; |
| ASSERT_TRUE( |
| base::PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &sensitive_dir)); |
| ASSERT_TRUE(base::DirectoryExists(sensitive_dir)); |
| TestPermissionsForLaunchDirectory(sensitive_dir); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Base class for testing File Handling and Native File System with Chrome OS |
| // File System Provider features. |
| class SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest |
| : public SystemWebAppManagerLaunchDirectoryBrowserTest { |
| public: |
| bool CheckFileIsGif(content::WebContents* web_contents, |
| const std::string& file_handle_or_promise) { |
| bool is_gif_signature; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "Promise.resolve(" + file_handle_or_promise + ")" + |
| ".then(async file => {" |
| " const arrayBuf = await file.arrayBuffer();" |
| " const bytes = new Uint8Array(arrayBuf.slice(0, 3));" |
| " const isGifSignature = bytes[0] === 0x47 /* G */" |
| " && bytes[1] === 0x49 /* I */ " |
| " && bytes[2] === 0x46; /* F */" |
| " domAutomationController.send(isGifSignature);" |
| "});", |
| &is_gif_signature)); |
| return is_gif_signature; |
| } |
| |
| bool CheckFileIsPng(content::WebContents* web_contents, |
| const std::string& file_handle_or_promise) { |
| bool is_png_signature; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "Promise.resolve(" + file_handle_or_promise + ")" + |
| ".then(async file => {" |
| " const arrayBuf = await file.arrayBuffer();" |
| " const bytes = new Uint8Array(arrayBuf.slice(0, 4));" |
| " const isPngSignature = bytes[0] === 0x89 /* 0x89 */" |
| " && bytes[1] === 0x50 /* P */" |
| " && bytes[2] === 0x4E /* N */" |
| " && bytes[3] === 0x47; /* G */" |
| " domAutomationController.send(isPngSignature);" |
| "});", |
| &is_png_signature)); |
| return is_png_signature; |
| } |
| |
| // Returns whether the file is written. |
| bool CheckCanWriteFile(content::WebContents* web_contents, |
| const std::string& file_handle_or_promise) { |
| bool file_written; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "Promise.resolve(" + file_handle_or_promise + ")" + |
| ".then(async fileHandle => {" |
| " try {" |
| " const writable = await fileHandle.createWritable();" |
| " await writable.write('test');" |
| " await writable.close();" |
| " domAutomationController.send(true);" |
| " } catch(err) {" |
| " console.error('write failed: ' + err.message);" |
| " domAutomationController.send(false);" |
| " }" |
| "});", |
| &file_written)); |
| return file_written; |
| } |
| |
| void InstallTestFileSystemProvider(Profile* profile) { |
| volume_ = file_manager::test::InstallFileSystemProviderChromeApp(profile); |
| } |
| |
| base::FilePath GetFileSystemProviderFilePath(const std::string& file_name) { |
| return volume_->mount_path().AppendASCII(file_name); |
| } |
| |
| private: |
| base::WeakPtr<file_manager::Volume> volume_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P( |
| SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest, |
| LaunchFromFileSystemProvider_ReadFiles) { |
| Profile* profile = browser()->profile(); |
| |
| WaitForTestSystemAppInstall(); |
| InstallTestFileSystemProvider(profile); |
| |
| // Launch from FileSystemProvider path. |
| const char kTestGifFile[] = "readwrite.gif"; |
| const char kTestPngFile[] = "readonly.png"; |
| const base::FilePath launch_file = |
| GetFileSystemProviderFilePath(kTestGifFile); |
| |
| content::WebContents* web_contents = LaunchApp({launch_file}); |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams")); |
| |
| // Check the launch file is the one we expect, and we can read the file. |
| EXPECT_EQ(kTestGifFile, |
| GetJsStatementValueAsString(web_contents, |
| "window.launchParams.files[1].name")); |
| EXPECT_TRUE( |
| CheckFileIsGif(web_contents, "window.launchParams.files[1].getFile()")); |
| |
| // Check we can list the directory. |
| std::string file_names; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "(async function() {" |
| " let fileNames = [];" |
| " const files = await window.launchParams.files[0].keys();" |
| " for await (const name of files)" |
| " fileNames.push(name);" |
| " domAutomationController.send(fileNames.sort().join(';'));" |
| "})();", |
| &file_names)); |
| EXPECT_EQ(base::StrCat({kTestPngFile, ";", kTestGifFile}), file_names); |
| |
| // Verify we can read a file (other than launch file) inside the directory. |
| EXPECT_TRUE(CheckFileIsPng( |
| web_contents, |
| content::JsReplace("window.launchParams.files[0].getFileHandle($1).then(" |
| " fileHandle => fileHandle.getFile())", |
| kTestPngFile))); |
| } |
| |
| // Test that the Native File System implementation doesn't cause a crash when |
| // writing to readonly files. |
| IN_PROC_BROWSER_TEST_P( |
| SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest, |
| LaunchFromFileSystemProvider_WriteFileFails) { |
| Profile* profile = browser()->profile(); |
| |
| WaitForTestSystemAppInstall(); |
| InstallTestFileSystemProvider(profile); |
| |
| content::WebContents* web_contents = |
| LaunchApp({GetFileSystemProviderFilePath("readonly.png")}); |
| |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams")); |
| |
| // Try to write the file. |
| EXPECT_FALSE(CheckCanWriteFile(web_contents, "window.launchParams.files[1]")); |
| |
| // Do a no-op JavaScript to check the page is still operational. If the page |
| // crashed, the following call will fail. |
| EXPECT_TRUE(content::ExecuteScript(web_contents, "(function() {})();")); |
| } |
| |
| // Test that the Native File System implementation doesn't cause a crash when |
| // deleting readonly files. |
| IN_PROC_BROWSER_TEST_P( |
| SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest, |
| LaunchFromFileSystemProvider_DeleteFileFails) { |
| Profile* profile = browser()->profile(); |
| |
| WaitForTestSystemAppInstall(); |
| InstallTestFileSystemProvider(profile); |
| |
| content::WebContents* web_contents = |
| LaunchApp({GetFileSystemProviderFilePath("readonly.png")}); |
| |
| EXPECT_TRUE(PrepareToReceiveLaunchParams(web_contents)); |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams")); |
| |
| // Try to delete the file. |
| bool file_deleted; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| content::JsReplace("window.launchParams.files[0].removeEntry($1)" |
| ".then(" |
| " _ => domAutomationController.send(true)," |
| " error => domAutomationController.send(false)" |
| ");", |
| "readonly.png"), |
| &file_deleted)); |
| EXPECT_FALSE(file_deleted); |
| |
| // Do a no-op JavaScript to check the page is still operational. If the page |
| // crashed, the following call will fail. |
| EXPECT_TRUE(content::ExecuteScript(web_contents, "(function() {})();")); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| class SystemWebAppManagerFileHandlingOriginTrialsBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerFileHandlingOriginTrialsBrowserTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithEnabledOriginTrials( |
| OriginTrialsMap({{GetOrigin(GURL("chrome://test-system-app/")), |
| {"NativeFileSystem2", "FileHandling"}}}), |
| install_from_web_app_info()); |
| } |
| |
| ~SystemWebAppManagerFileHandlingOriginTrialsBrowserTest() override = default; |
| |
| content::WebContents* LaunchWithTestFiles() { |
| // Create temporary directory and files. |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir temp_directory; |
| CHECK(temp_directory.CreateUniqueTempDir()); |
| base::FilePath temp_file_path; |
| CHECK(base::CreateTemporaryFileInDir(temp_directory.GetPath(), |
| &temp_file_path)); |
| |
| // Launch the App. |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| params.launch_files = {temp_file_path}; |
| |
| return SystemWebAppManagerBrowserTestBase::LaunchApp(std::move(params)); |
| } |
| |
| bool WaitForLaunchParam(content::WebContents* web_contents) { |
| bool promise_resolved = false; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "launchQueue.setConsumer(launchParams => {" |
| " domAutomationController.send(true);" |
| "});", |
| &promise_resolved)); |
| return promise_resolved; |
| } |
| |
| private: |
| url::Origin GetOrigin(const GURL& url) { return url::Origin::Create(url); } |
| }; |
| |
| // Test that file handling works when the App is first installed. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerFileHandlingOriginTrialsBrowserTest, |
| PRE_FileHandlingWorks) { |
| WaitForTestSystemAppInstall(); |
| |
| content::WebContents* web_contents = LaunchWithTestFiles(); |
| EXPECT_TRUE(WaitForLaunchParam(web_contents)); |
| } |
| |
| // Test that file handling works when after a version upgrade. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerFileHandlingOriginTrialsBrowserTest, |
| FileHandlingWorks) { |
| WaitForTestSystemAppInstall(); |
| |
| content::WebContents* web_contents = LaunchWithTestFiles(); |
| EXPECT_TRUE(WaitForLaunchParam(web_contents)); |
| } |
| |
| class SystemWebAppManagerNotShownInLauncherTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerNotShownInLauncherTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppNotShownInLauncher( |
| install_from_web_app_info()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInLauncherTest, |
| NotShownInLauncher) { |
| WaitForTestSystemAppInstall(); |
| |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| // OS Integration only relevant for Chrome OS. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInLauncher()); |
| }); |
| // The |AppList| should have all apps visible in the launcher, apps get |
| // removed from the |AppList| when they are hidden. |
| AppListClientImpl* client = AppListClientImpl::GetInstance(); |
| ASSERT_TRUE(client); |
| AppListModelUpdater* model_updater = test::GetModelUpdater(client); |
| const ChromeAppListItem* mock_app = model_updater->FindItem(app_id); |
| // |mock_app| shouldn't be found in |AppList| because it should be hidden in |
| // launcher. |
| EXPECT_FALSE(mock_app); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| class SystemWebAppManagerNotShownInSearchTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerNotShownInSearchTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppNotShownInSearch( |
| install_from_web_app_info()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInSearchTest, |
| NotShownInSearch) { |
| WaitForTestSystemAppInstall(); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| // OS Integration only relevant for Chrome OS. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInSearch()); |
| }); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| class SystemWebAppManagerAdditionalSearchTermsTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerAdditionalSearchTermsTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithAdditionalSearchTerms( |
| install_from_web_app_info()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAdditionalSearchTermsTest, |
| AdditionalSearchTerms) { |
| WaitForTestSystemAppInstall(); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| // AdditionalSearchTerms is flaky on Windows as it's a Chrome OS feature. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(std::vector<std::string>({"Security"}), |
| update.AdditionalSearchTerms()); |
| }); |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| } |
| |
| // Tests that SWA are correctly uninstalled across restarts. |
| class SystemWebAppManagerUninstallBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerUninstallBrowserTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| if (content::IsPreTest()) { |
| // Use an app with FileHandling enabled since it will perform extra setup |
| // steps. |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithEnabledOriginTrials( |
| OriginTrialsMap( |
| {{url::Origin::Create(GURL("chrome://test-system-app/")), |
| {"NativeFileSystem2", "FileHandling"}}}), |
| install_from_web_app_info()); |
| } else { |
| maybe_installation_ = TestSystemWebAppInstallation::SetUpWithoutApps(); |
| } |
| } |
| ~SystemWebAppManagerUninstallBrowserTest() override = default; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerUninstallBrowserTest, PRE_Uninstall) { |
| WaitForTestSystemAppInstall(); |
| EXPECT_TRUE(GetManager().GetAppIdForSystemApp(GetMockAppType()).has_value()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerUninstallBrowserTest, Uninstall) { |
| WaitForTestSystemAppInstall(); |
| EXPECT_TRUE(GetManager().GetAppIds().empty()); |
| } |
| |
| // We only have concrete System Web Apps on Chrome OS. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Test that all registered System Apps can be re-installed. |
| class SystemWebAppManagerUpgradeBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerUpgradeBrowserTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| features_.InitAndEnableFeature(features::kEnableAllSystemWebApps); |
| } |
| ~SystemWebAppManagerUpgradeBrowserTest() override = default; |
| |
| // Don't use WaitForTestSystemAppInstall in this test, because it artificially |
| // resets the OnAppsSynchronized signal, and starts a new synchronize request. |
| void WaitForSystemAppsSynchronized() { |
| base::RunLoop run_loop; |
| WebAppProvider::Get(browser()->profile()) |
| ->system_web_app_manager() |
| .on_apps_synchronized() |
| .Post(FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList features_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerUpgradeBrowserTest, PRE_Upgrade) { |
| WaitForSystemAppsSynchronized(); |
| EXPECT_GE(GetManager().GetRegisteredSystemAppsForTesting().size(), |
| GetManager().GetAppIds().size()); |
| } |
| |
| // Test is consistently failing: crbug/1162044 |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerUpgradeBrowserTest, |
| DISABLED_Upgrade) { |
| WaitForSystemAppsSynchronized(); |
| const auto& app_ids = GetManager().GetAppIds(); |
| |
| EXPECT_EQ(GetManager().GetRegisteredSystemAppsForTesting().size(), |
| app_ids.size()); |
| |
| for (const auto& app_id : app_ids) { |
| const auto type = GetManager().GetSystemAppTypeForAppId(app_id).value(); |
| |
| // We don't launch Terminal in browsertest, because it requires resources |
| // that are only available in Chrome OS images. |
| if (type == SystemAppType::TERMINAL) |
| continue; |
| |
| // Launch other System Apps normally, and check the app's launch_url loads. |
| EXPECT_TRUE(LaunchApp(type)); |
| } |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| // Tests that SWA-specific data is correctly migrated to Web Apps without |
| // Extensions. |
| class SystemWebAppManagerMigrationTest |
| : public SystemWebAppManagerBrowserTestBase { |
| public: |
| SystemWebAppManagerMigrationTest() |
| : SystemWebAppManagerBrowserTestBase(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithAdditionalSearchTerms(false); |
| maybe_installation_->set_update_policy( |
| SystemWebAppManager::UpdatePolicy::kOnVersionChange); |
| |
| if (content::IsPreTest()) { |
| scoped_feature_list_.InitAndDisableFeature( |
| features::kDesktopPWAsWithoutExtensions); |
| } else { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kDesktopPWAsWithoutExtensions); |
| } |
| } |
| ~SystemWebAppManagerMigrationTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // These tests use the App Service which is only enabled on Chrome OS. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #define MAYBE_PRE_ExtraDataIsMigrated PRE_ExtraDataIsMigrated |
| #else |
| #define MAYBE_PRE_ExtraDataIsMigrated DISABLED_PRE_ExtraDataIsMigrated |
| #endif |
| IN_PROC_BROWSER_TEST_F(SystemWebAppManagerMigrationTest, |
| MAYBE_PRE_ExtraDataIsMigrated) { |
| WaitForTestSystemAppInstall(); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| const bool app_found = proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(std::vector<std::string>({"Security"}), |
| update.AdditionalSearchTerms()); |
| EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInManagement()); |
| }); |
| ASSERT_TRUE(app_found); |
| } |
| |
| // These tests use the App Service which is only enabled on Chrome OS. |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #define MAYBE_ExtraDataIsMigrated ExtraDataIsMigrated |
| #else |
| #define MAYBE_ExtraDataIsMigrated DISABLED_ExtraDataIsMigrated |
| #endif |
| IN_PROC_BROWSER_TEST_F(SystemWebAppManagerMigrationTest, |
| MAYBE_ExtraDataIsMigrated) { |
| WaitForTestSystemAppInstall(); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| const bool app_found = proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(std::vector<std::string>({"Security"}), |
| update.AdditionalSearchTerms()); |
| EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInManagement()); |
| }); |
| ASSERT_TRUE(app_found); |
| } |
| |
| class SystemWebAppManagerChromeUntrustedTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerChromeUntrustedTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = TestSystemWebAppInstallation::SetUpChromeUntrustedApp( |
| install_from_web_app_info()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerChromeUntrustedTest, Install) { |
| WaitForTestSystemAppInstall(); |
| |
| // Don't wait for page load because we want to verify AppController identifies |
| // the System Web App before the app loads. |
| Browser* app_browser; |
| LaunchAppWithoutWaiting(GetMockAppType(), &app_browser); |
| |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| EXPECT_EQ(app_id, app_browser->app_controller()->GetAppId()); |
| EXPECT_TRUE(GetManager().IsSystemWebApp(app_id)); |
| |
| Profile* profile = app_browser->profile(); |
| AppRegistrar& registrar = |
| WebAppProviderBase::GetProviderBase(profile)->registrar(); |
| |
| EXPECT_EQ("Test System App", registrar.GetAppShortName(app_id)); |
| EXPECT_EQ(SkColorSetRGB(0, 0xFF, 0), registrar.GetAppThemeColor(app_id)); |
| EXPECT_TRUE(registrar.HasExternalAppWithInstallSource( |
| app_id, web_app::ExternalInstallSource::kSystemInstalled)); |
| EXPECT_EQ(registrar.FindAppWithUrlInScope( |
| GURL("chrome-untrusted://test-system-app/")), |
| app_id); |
| } |
| |
| class SystemWebAppManagerOriginTrialsBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerOriginTrialsBrowserTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithEnabledOriginTrials( |
| OriginTrialsMap({{GetOrigin(main_url_), main_url_trials_}, |
| {GetOrigin(trial_url_), trial_url_trials_}}), |
| install_from_web_app_info()); |
| } |
| |
| ~SystemWebAppManagerOriginTrialsBrowserTest() override = default; |
| |
| protected: |
| class MockNavigationHandle : public content::MockNavigationHandle { |
| public: |
| explicit MockNavigationHandle(const GURL& url) |
| : content::MockNavigationHandle(url, nullptr) {} |
| bool IsInMainFrame() override { return is_in_main_frame_; } |
| |
| void set_is_in_main_frame(bool is_in_main_frame) { |
| is_in_main_frame_ = is_in_main_frame; |
| } |
| |
| private: |
| bool is_in_main_frame_; |
| }; |
| |
| std::unique_ptr<content::WebContents> CreateTestWebContents() { |
| content::WebContents::CreateParams create_params(browser()->profile()); |
| return content::WebContents::Create(create_params); |
| } |
| |
| const std::vector<std::string> main_url_trials_ = {"Frobulate"}; |
| const std::vector<std::string> trial_url_trials_ = {"FrobulateNavigation"}; |
| |
| const GURL main_url_ = GURL("chrome://test-system-app/pwa.html"); |
| const GURL trial_url_ = GURL("chrome://test-subframe/title2.html"); |
| const GURL notrial_url_ = GURL("chrome://notrial-subframe/title3.html"); |
| |
| private: |
| url::Origin GetOrigin(const GURL& url) { return url::Origin::Create(url); } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerOriginTrialsBrowserTest, |
| ForceEnabledOriginTrials_FirstNavigationIntoPage) { |
| WaitForTestSystemAppInstall(); |
| |
| std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents(); |
| WebAppTabHelper tab_helper(web_contents.get()); |
| |
| // Simulate when first navigating into app's launch url. |
| { |
| MockNavigationHandle mock_nav_handle(main_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials(main_url_trials_)); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| ASSERT_EQ(maybe_installation_->GetAppId(), tab_helper.GetAppId()); |
| } |
| |
| // Simulate loading app's embedded child-frame that has origin trials. |
| { |
| MockNavigationHandle mock_nav_handle(trial_url_); |
| mock_nav_handle.set_is_in_main_frame(false); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials(trial_url_trials_)); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| } |
| |
| // Simulate loading app's embedded child-frame that has no origin trial. |
| { |
| MockNavigationHandle mock_nav_handle(notrial_url_); |
| mock_nav_handle.set_is_in_main_frame(false); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials).Times(0); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerOriginTrialsBrowserTest, |
| ForceEnabledOriginTrials_IntraDocumentNavigation) { |
| WaitForTestSystemAppInstall(); |
| |
| std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents(); |
| WebAppTabHelper tab_helper(web_contents.get()); |
| |
| // Simulate when first navigating into app's launch url. |
| { |
| MockNavigationHandle mock_nav_handle(main_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials(main_url_trials_)); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| ASSERT_EQ(maybe_installation_->GetAppId(), tab_helper.GetAppId()); |
| } |
| |
| // Simulate same-document navigation. |
| { |
| MockNavigationHandle mock_nav_handle(main_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(true); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials).Times(0); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| } |
| } |
| |
| // This test checks origin trials are correctly enabled for navigations on the |
| // main frame, this test checks: |
| // - The app's main page |main_url_| has OT. |
| // - The iframe page |trial_url_| has OT, only if it is embedded by the app. |
| // - When navigating from a cross-origin page to the app's main page, the main |
| // page has OT. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerOriginTrialsBrowserTest, |
| ForceEnabledOriginTrials_Navigation) { |
| WaitForTestSystemAppInstall(); |
| |
| std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents(); |
| WebAppTabHelper tab_helper(web_contents.get()); |
| |
| // Simulate when first navigating into app's launch url. |
| { |
| MockNavigationHandle mock_nav_handle(main_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials(main_url_trials_)); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| ASSERT_EQ(maybe_installation_->GetAppId(), tab_helper.GetAppId()); |
| } |
| |
| // Simulate navigating to a different site without origin trials. |
| { |
| MockNavigationHandle mock_nav_handle(notrial_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials).Times(0); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| ASSERT_EQ("", tab_helper.GetAppId()); |
| } |
| |
| // Simulatenavigating back to a SWA with origin trials. |
| { |
| MockNavigationHandle mock_nav_handle(main_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials(main_url_trials_)); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| ASSERT_EQ(maybe_installation_->GetAppId(), tab_helper.GetAppId()); |
| } |
| |
| // Simulate navigating the main frame to a url embedded by SWA. This url has |
| // origin trials when embedded by SWA. However, when this url is loaded in the |
| // main frame, it should not get origin trials. |
| { |
| MockNavigationHandle mock_nav_handle(trial_url_); |
| mock_nav_handle.set_is_in_main_frame(true); |
| mock_nav_handle.set_is_same_document(false); |
| EXPECT_CALL(mock_nav_handle, ForceEnableOriginTrials).Times(0); |
| tab_helper.ReadyToCommitNavigation(&mock_nav_handle); |
| ASSERT_EQ("", tab_helper.GetAppId()); |
| } |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| class SystemWebAppManagerAppSuspensionBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerAppSuspensionBrowserTest() |
| : SystemWebAppManagerBrowserTest(false) {} |
| |
| apps::mojom::Readiness GetAppReadiness(const AppId& app_id) { |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| apps::mojom::Readiness readiness; |
| bool app_found = proxy->AppRegistryCache().ForOneApp( |
| app_id, [&readiness](const apps::AppUpdate& update) { |
| readiness = update.Readiness(); |
| }); |
| CHECK(app_found); |
| return readiness; |
| } |
| |
| apps::mojom::IconKeyPtr GetAppIconKey(const AppId& app_id) { |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| apps::mojom::IconKeyPtr icon_key; |
| bool app_found = proxy->AppRegistryCache().ForOneApp( |
| app_id, [&icon_key](const apps::AppUpdate& update) { |
| icon_key = update.IconKey(); |
| }); |
| CHECK(app_found); |
| return icon_key; |
| } |
| }; |
| |
| // Tests that System Apps can be suspended when the policy is set before the app |
| // is installed. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAppSuspensionBrowserTest, |
| AppSuspendedBeforeInstall) { |
| ASSERT_FALSE( |
| GetManager().GetAppIdForSystemApp(SystemAppType::SETTINGS).has_value()); |
| { |
| ListPrefUpdate update(TestingBrowserProcess::GetGlobal()->local_state(), |
| policy::policy_prefs::kSystemFeaturesDisableList); |
| base::ListValue* list = update.Get(); |
| list->Append(policy::SystemFeature::kOsSettings); |
| } |
| WaitForTestSystemAppInstall(); |
| base::Optional<AppId> settings_id = |
| GetManager().GetAppIdForSystemApp(SystemAppType::SETTINGS); |
| DCHECK(settings_id.has_value()); |
| |
| EXPECT_EQ(apps::mojom::Readiness::kDisabledByPolicy, |
| GetAppReadiness(*settings_id)); |
| EXPECT_TRUE(apps::IconEffects::kBlocked & |
| GetAppIconKey(*settings_id)->icon_effects); |
| |
| { |
| ListPrefUpdate update(TestingBrowserProcess::GetGlobal()->local_state(), |
| policy::policy_prefs::kSystemFeaturesDisableList); |
| base::ListValue* list = update.Get(); |
| list->Clear(); |
| } |
| GetAppServiceProxy(browser()->profile())->FlushMojoCallsForTesting(); |
| EXPECT_EQ(apps::mojom::Readiness::kReady, GetAppReadiness(*settings_id)); |
| EXPECT_FALSE(apps::IconEffects::kBlocked & |
| GetAppIconKey(*settings_id)->icon_effects); |
| } |
| |
| // Tests that System Apps can be suspended when the policy is set after the app |
| // is installed. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAppSuspensionBrowserTest, |
| AppSuspendedAfterInstall) { |
| WaitForTestSystemAppInstall(); |
| base::Optional<AppId> settings_id = |
| GetManager().GetAppIdForSystemApp(SystemAppType::SETTINGS); |
| DCHECK(settings_id.has_value()); |
| EXPECT_EQ(apps::mojom::Readiness::kReady, GetAppReadiness(*settings_id)); |
| |
| { |
| ListPrefUpdate update(TestingBrowserProcess::GetGlobal()->local_state(), |
| policy::policy_prefs::kSystemFeaturesDisableList); |
| base::ListValue* list = update.Get(); |
| list->Append(policy::SystemFeature::kOsSettings); |
| } |
| |
| apps::AppServiceProxy* proxy = GetAppServiceProxy(browser()->profile()); |
| proxy->FlushMojoCallsForTesting(); |
| EXPECT_EQ(apps::mojom::Readiness::kDisabledByPolicy, |
| GetAppReadiness(*settings_id)); |
| EXPECT_TRUE(apps::IconEffects::kBlocked & |
| GetAppIconKey(*settings_id)->icon_effects); |
| |
| { |
| ListPrefUpdate update(TestingBrowserProcess::GetGlobal()->local_state(), |
| policy::policy_prefs::kSystemFeaturesDisableList); |
| base::ListValue* list = update.Get(); |
| list->Clear(); |
| } |
| proxy->FlushMojoCallsForTesting(); |
| EXPECT_EQ(apps::mojom::Readiness::kReady, GetAppReadiness(*settings_id)); |
| EXPECT_FALSE(apps::IconEffects::kBlocked & |
| GetAppIconKey(*settings_id)->icon_effects); |
| } |
| // This feature will only work when DesktopPWAsWithoutExtensions launches. |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerAppSuspensionBrowserTest); |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerWebAppInfoBrowserTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerLaunchFilesBrowserTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerLaunchDirectoryBrowserTest); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest); |
| #endif |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerNotShownInLauncherTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerNotShownInSearchTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerAdditionalSearchTermsTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerChromeUntrustedTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerOriginTrialsBrowserTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerFileHandlingOriginTrialsBrowserTest); |
| |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerUninstallBrowserTest); |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_ALL_INSTALL_TYPES_P( |
| SystemWebAppManagerUpgradeBrowserTest); |
| #endif |
| |
| } // namespace web_app |