| // 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 "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/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 "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 "url/gurl.h" |
| |
| #if defined(OS_CHROMEOS) |
| #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 web_app { |
| |
| SystemWebAppManagerBrowserTestBase::SystemWebAppManagerBrowserTestBase( |
| bool install_mock) { |
| scoped_feature_list_.InitWithFeatures({features::kSystemWebApps}, {}); |
| if (install_mock) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpStandaloneSingleWindowApp(); |
| } |
| } |
| |
| SystemWebAppManagerBrowserTestBase::~SystemWebAppManagerBrowserTestBase() = |
| default; |
| |
| SystemWebAppManager& SystemWebAppManagerBrowserTestBase::GetManager() { |
| return WebAppProvider::Get(browser()->profile())->system_web_app_manager(); |
| } |
| |
| SystemAppType SystemWebAppManagerBrowserTestBase::GetMockAppType() { |
| DCHECK(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|. |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()); |
| proxy->FlushMojoCallsForTesting(); |
| } |
| |
| content::WebContents* |
| SystemWebAppManagerBrowserTestBase::WaitForSystemAppInstallAndLoad( |
| SystemAppType system_app_type) { |
| WaitForTestSystemAppInstall(); |
| apps::AppLaunchParams params = LaunchParamsForApp(system_app_type); |
| content::WebContents* web_contents = LaunchApp(params); |
| EXPECT_TRUE(WaitForLoadStop(web_contents)); |
| return web_contents; |
| } |
| |
| Browser* SystemWebAppManagerBrowserTestBase::WaitForSystemAppInstallAndLaunch( |
| SystemAppType system_app_type) { |
| WaitForTestSystemAppInstall(); |
| apps::AppLaunchParams params = LaunchParamsForApp(system_app_type); |
| content::WebContents* web_contents = LaunchApp(params); |
| Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
| EXPECT_EQ(web_app::GetAppIdFromApplicationName(browser->app_name()), |
| params.app_id); |
| return browser; |
| } |
| |
| apps::AppLaunchParams SystemWebAppManagerBrowserTestBase::LaunchParamsForApp( |
| SystemAppType system_app_type) { |
| base::Optional<AppId> app_id = |
| GetManager().GetAppIdForSystemApp(system_app_type); |
| DCHECK(app_id.has_value()); |
| return apps::AppLaunchParams( |
| *app_id, apps::mojom::LaunchContainer::kLaunchContainerWindow, |
| WindowOpenDisposition::CURRENT_TAB, |
| apps::mojom::AppLaunchSource::kSourceTest); |
| } |
| |
| content::WebContents* SystemWebAppManagerBrowserTestBase::LaunchApp( |
| const apps::AppLaunchParams& params) { |
| return apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| } |
| |
| SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest( |
| bool install_mock) |
| : SystemWebAppManagerBrowserTestBase(install_mock) { |
| if (GetParam() == ProviderType::kWebApps) { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kDesktopPWAsWithoutExtensions); |
| } else if (GetParam() == ProviderType::kBookmarkApps) { |
| scoped_feature_list_.InitAndDisableFeature( |
| features::kDesktopPWAsWithoutExtensions); |
| } |
| } |
| |
| // Test that System Apps install correctly with a manifest. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest, Install) { |
| Browser* app_browser = WaitForSystemAppInstallAndLaunch(GetMockAppType()); |
| |
| 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); |
| |
| if (!base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions)) { |
| const extensions::Extension* extension = |
| extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension( |
| app_id); |
| EXPECT_TRUE(extension->from_bookmark()); |
| EXPECT_EQ(extensions::Manifest::EXTERNAL_COMPONENT, extension->location()); |
| } |
| |
| // OS Integration only relevant for Chrome OS. |
| #if defined(OS_CHROMEOS) |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(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 // defined(OS_CHROMEOS) |
| } |
| |
| // 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(SystemWebAppManagerBrowserTest, |
| ToolbarVisibilityForSystemWebApp) { |
| Browser* app_browser = WaitForSystemAppInstallAndLaunch(GetMockAppType()); |
| // 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()); |
| } |
| |
| class SystemWebAppManagerFileHandlingBrowserTestBase |
| : public SystemWebAppManagerBrowserTestBase, |
| public testing::WithParamInterface<bool> { |
| public: |
| using IncludeLaunchDirectory = |
| TestSystemWebAppInstallation::IncludeLaunchDirectory; |
| explicit SystemWebAppManagerFileHandlingBrowserTestBase( |
| IncludeLaunchDirectory include_launch_directory) |
| : SystemWebAppManagerBrowserTestBase(/*install_mock=*/false) { |
| bool enable_desktop_pwas_without_extensions = GetParam(); |
| |
| scoped_feature_web_app_provider_type_.InitWithFeatureState( |
| features::kDesktopPWAsWithoutExtensions, |
| enable_desktop_pwas_without_extensions); |
| scoped_feature_blink_api_.InitWithFeatures( |
| {blink::features::kNativeFileSystemAPI, |
| blink::features::kFileHandlingAPI}, |
| {}); |
| |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppThatReceivesLaunchFiles( |
| include_launch_directory); |
| } |
| |
| 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. |
| // Note: This test uses ExecuteScriptXXX instead of ExecJs and EvalJs because of |
| // some quirks surrounding origin trials and content security policies. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchFilesBrowserTest, |
| LaunchFilesForSystemWebApp) { |
| WaitForTestSystemAppInstall(); |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| |
| 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)); |
| |
| const GURL& launch_url = WebAppProvider::Get(browser()->profile()) |
| ->registrar() |
| .GetAppLaunchURL(params.app_id); |
| |
| // First launch. |
| params.launch_files = {temp_file_path}; |
| content::TestNavigationObserver navigation_observer(launch_url); |
| navigation_observer.StartWatchingNewWebContents(); |
| content::WebContents* web_contents = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| navigation_observer.Wait(); |
| |
| // Set up a Promise that resolves to launchParams, when launchQueue's consumer |
| // callback is called. |
| EXPECT_TRUE(content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise = new Promise(resolve => {" |
| " window.resolveLaunchParamsPromise = resolve;" |
| "});" |
| "launchQueue.setConsumer(launchParams => {" |
| " window.resolveLaunchParamsPromise(launchParams);" |
| "});")); |
| |
| // Check launch files are correct. |
| std::string file_name; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "window.launchParamsPromise.then(" |
| " launchParams => " |
| " domAutomationController.send(launchParams.files[0].name));", |
| &file_name)); |
| EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(), file_name); |
| |
| // Reset the Promise to get second launchParams. |
| EXPECT_TRUE(content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise = new Promise(resolve => {" |
| " window.resolveLaunchParamsPromise = resolve;" |
| "});")); |
| |
| // Second Launch. |
| base::FilePath temp_file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_directory.GetPath(), |
| &temp_file_path2)); |
| params.launch_files = {temp_file_path2}; |
| content::WebContents* web_contents2 = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| |
| // WebContents* should be the same because we are passing launchParams to the |
| // opened application. |
| EXPECT_EQ(web_contents, web_contents2); |
| |
| // Second launch_files are passed to the opened application. |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "window.launchParamsPromise.then(" |
| " launchParams => " |
| " domAutomationController.send(launchParams.files[0].name))", |
| &file_name)); |
| EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(), file_name); |
| } |
| |
| 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) { |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| |
| 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. |
| const GURL& launch_url = WebAppProvider::Get(browser()->profile()) |
| ->registrar() |
| .GetAppLaunchURL(params.app_id); |
| params.launch_files = {launch_file_path}; |
| content::TestNavigationObserver navigation_observer(launch_url); |
| navigation_observer.StartWatchingNewWebContents(); |
| content::WebContents* web_contents = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| navigation_observer.Wait(); |
| |
| // 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); |
| |
| // Set up a Promise to resolves to launchParams, when launchQueue's consumer |
| // callback is called. |
| EXPECT_TRUE(content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise = new Promise(resolve => {" |
| " window.resolveLaunchParamsPromise = resolve;" |
| "});" |
| "launchQueue.setConsumer(launchParams => {" |
| " window.resolveLaunchParamsPromise(launchParams);" |
| "});")); |
| |
| // Wait for launch. Set window.launchParams for manipulation. |
| EXPECT_TRUE(content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise.then(launchParams => {" |
| " window.launchParams = launchParams;" |
| "});")); |
| |
| // 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.getFile(). |
| 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].getFile($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].getFile($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"))); |
| } |
| }; |
| |
| // Launching behavior for apps that do not want to received launch directory are |
| // tested in |SystemWebAppManagerBrowserTestBase.LaunchFilesForSystemWebApp|. |
| // Note: This test uses ExecuteScriptXXX instead of ExecJs and EvalJs because of |
| // some quirks surrounding origin trials and content security policies. |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchDirectoryBrowserTest, |
| LaunchDirectoryForSystemWebApp) { |
| WaitForTestSystemAppInstall(); |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| |
| 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)); |
| |
| const GURL& launch_url = WebAppProvider::Get(browser()->profile()) |
| ->registrar() |
| .GetAppLaunchURL(params.app_id); |
| |
| // First launch. |
| params.launch_files = {temp_file_path}; |
| content::TestNavigationObserver navigation_observer(launch_url); |
| navigation_observer.StartWatchingNewWebContents(); |
| content::WebContents* web_contents = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| navigation_observer.Wait(); |
| |
| // Set up a Promise that resolves to launchParams, when launchQueue's consumer |
| // callback is called. |
| EXPECT_TRUE(content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise = new Promise(resolve => {" |
| " window.resolveLaunchParamsPromise = resolve;" |
| "});" |
| "launchQueue.setConsumer(launchParams => {" |
| " window.resolveLaunchParamsPromise(launchParams);" |
| "});")); |
| |
| // Wait for launch. Set window.firstLaunchParams for inspection. |
| EXPECT_TRUE( |
| content::ExecuteScript(web_contents, |
| "window.launchParamsPromise.then(launchParams => {" |
| " window.firstLaunchParams = launchParams;" |
| "});")); |
| |
| // Check launch directory is correct. |
| bool is_directory; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "domAutomationController.send(window.firstLaunchParams." |
| "files[0].isDirectory)", |
| &is_directory)); |
| EXPECT_TRUE(is_directory); |
| |
| std::string file_name; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "domAutomationController.send(window.firstLaunchParams.files[0].name)", |
| &file_name)); |
| EXPECT_EQ(temp_directory.GetPath().BaseName().AsUTF8Unsafe(), file_name); |
| |
| // Check launch files are correct. |
| bool is_file; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "domAutomationController.send(window.firstLaunchParams.files[1].isFile)", |
| &is_file)); |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "domAutomationController.send(window.firstLaunchParams.files[1].name)", |
| &file_name)); |
| EXPECT_EQ(temp_file_path.BaseName().AsUTF8Unsafe(), file_name); |
| |
| // Reset the Promise to get second launchParams. |
| EXPECT_TRUE(content::ExecuteScript( |
| web_contents, |
| "window.launchParamsPromise = new Promise(resolve => {" |
| " window.resolveLaunchParamsPromise = resolve;" |
| "});")); |
| |
| // 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)); |
| params.launch_files = {temp_file_path2}; |
| content::WebContents* web_contents2 = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| |
| // WebContents* should be the same because we are passing launchParams to the |
| // opened application. |
| EXPECT_EQ(web_contents, web_contents2); |
| |
| // Wait for launch. Sets window.secondLaunchParams for inspection. |
| EXPECT_TRUE( |
| content::ExecuteScript(web_contents, |
| "window.launchParamsPromise.then(launchParams => {" |
| " window.secondLaunchParams = launchParams;" |
| "});")); |
| |
| // Second launch_dir and launch_files are passed to the opened application. |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "domAutomationController.send(window.secondLaunchParams.files[0]." |
| "isDirectory)", |
| &is_directory)); |
| EXPECT_TRUE(is_directory); |
| |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "domAutomationController.send(window.secondLaunchParams.files[0].name)", |
| &file_name)); |
| EXPECT_EQ(temp_directory2.GetPath().BaseName().AsUTF8Unsafe(), file_name); |
| |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "domAutomationController.send(window.secondLaunchParams.files[1]." |
| "isFile)", |
| &is_file)); |
| EXPECT_TRUE(is_file); |
| |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "domAutomationController.send(window.secondLaunchParams.files[1].name)", |
| &file_name)); |
| EXPECT_EQ(temp_file_path2.BaseName().AsUTF8Unsafe(), file_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 defined(OS_CHROMEOS) |
| |
| // 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); |
| } |
| |
| // Wait for System App Launch. |
| content::WebContents* WaitForApplicationLaunch( |
| Profile* profile, |
| const base::FilePath& launch_file) { |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| params.launch_files = {launch_file}; |
| |
| content::WebContents* web_contents = LaunchApp(params); |
| EXPECT_TRUE(WaitForLoadStop(web_contents)); |
| return web_contents; |
| } |
| |
| // Install a File Handling API launchQueue consumer, which copies the provided |
| // launch params to a JavaScript global named |js_property_name|. |
| 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("launchQueue.setConsumer(launchParams => {" |
| " window[$1] = launchParams;" |
| " domAutomationController.send(true);" |
| "});", |
| js_property_name), |
| &launch_params_received)); |
| return launch_params_received; |
| } |
| |
| 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 = |
| WaitForApplicationLaunch(profile, launch_file); |
| |
| EXPECT_TRUE(WaitAndExposeLaunchParamsToWindow(web_contents, "launchParams")); |
| |
| // Check the launch file is the one we expect. |
| std::string file_name; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractString( |
| web_contents, |
| "domAutomationController.send(window.launchParams.files[1].name);", |
| &file_name)); |
| EXPECT_EQ(kTestGifFile, file_name); |
| |
| // Check we can read the file by looking for GIF file signature. |
| 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].getEntries();" |
| " for await (const f of files)" |
| " fileNames.push(f.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].getFile($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 = WaitForApplicationLaunch( |
| profile, GetFileSystemProviderFilePath("readonly.png")); |
| |
| 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 = WaitForApplicationLaunch( |
| profile, GetFileSystemProviderFilePath("readonly.png")); |
| |
| 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 // defined(OS_CHROMEOS) |
| |
| class SystemWebAppManagerFileHandlingOriginTrialsBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerFileHandlingOriginTrialsBrowserTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithEnabledOriginTrials( |
| OriginTrialsMap({{GetOrigin(GURL("chrome://test-system-app/")), |
| {"NativeFileSystem2", "FileHandling"}}})); |
| } |
| |
| ~SystemWebAppManagerFileHandlingOriginTrialsBrowserTest() override = default; |
| |
| private: |
| url::Origin GetOrigin(const GURL& url) { return url::Origin::Create(url); } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerFileHandlingOriginTrialsBrowserTest, |
| FileHandlingWorks) { |
| WaitForTestSystemAppInstall(); |
| apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType()); |
| params.source = apps::mojom::AppLaunchSource::kSourceChromeInternal; |
| |
| 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)); |
| |
| const GURL& launch_url = WebAppProvider::Get(browser()->profile()) |
| ->registrar() |
| .GetAppLaunchURL(params.app_id); |
| |
| params.launch_files = {temp_file_path}; |
| content::TestNavigationObserver navigation_observer(launch_url); |
| navigation_observer.StartWatchingNewWebContents(); |
| content::WebContents* web_contents = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()) |
| ->BrowserAppLauncher() |
| .LaunchAppWithParams(params); |
| navigation_observer.Wait(); |
| |
| // Wait for the Promise to resolve. |
| bool promise_resolved = false; |
| EXPECT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "launchQueue.setConsumer(launchParams => {" |
| " domAutomationController.send(true);" |
| "});", |
| &promise_resolved)); |
| EXPECT_TRUE(promise_resolved); |
| } |
| |
| class SystemWebAppManagerNotShownInLauncherTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerNotShownInLauncherTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppNotShownInLauncher(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInLauncherTest, |
| NotShownInLauncher) { |
| WaitForSystemAppInstallAndLaunch(GetMockAppType()); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| // OS Integration only relevant for Chrome OS. |
| #if defined(OS_CHROMEOS) |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(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 // defined(OS_CHROMEOS) |
| } |
| |
| class SystemWebAppManagerNotShownInSearchTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerNotShownInSearchTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppNotShownInSearch(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInSearchTest, |
| NotShownInSearch) { |
| WaitForSystemAppInstallAndLaunch(GetMockAppType()); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| // OS Integration only relevant for Chrome OS. |
| #if defined(OS_CHROMEOS) |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()); |
| proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInSearch()); |
| }); |
| #endif // defined(OS_CHROMEOS) |
| } |
| |
| class SystemWebAppManagerAdditionalSearchTermsTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerAdditionalSearchTermsTest() |
| : SystemWebAppManagerBrowserTest(/*install_mock=*/false) { |
| maybe_installation_ = |
| TestSystemWebAppInstallation::SetUpAppWithAdditionalSearchTerms(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAdditionalSearchTermsTest, |
| AdditionalSearchTerms) { |
| WaitForSystemAppInstallAndLaunch(GetMockAppType()); |
| AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value(); |
| |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(browser()->profile()); |
| proxy->AppRegistryCache().ForOneApp( |
| app_id, [](const apps::AppUpdate& update) { |
| EXPECT_EQ(std::vector<std::string>({"Security"}), |
| update.AdditionalSearchTerms()); |
| }); |
| } |
| |
| // 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"}}})); |
| } 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()); |
| } |
| |
| // 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(); |
| 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 defined(OS_CHROMEOS) |
| #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 = |
| apps::AppServiceProxyFactory::GetForProfile(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 defined(OS_CHROMEOS) |
| #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 = |
| apps::AppServiceProxyFactory::GetForProfile(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(); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SystemWebAppManagerChromeUntrustedTest, Install) { |
| Browser* app_browser = WaitForSystemAppInstallAndLaunch(GetMockAppType()); |
| 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_}})); |
| } |
| |
| ~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 defined(OS_CHROMEOS) |
| |
| class SystemWebAppManagerAppSuspensionBrowserTest |
| : public SystemWebAppManagerBrowserTest { |
| public: |
| SystemWebAppManagerAppSuspensionBrowserTest() |
| : SystemWebAppManagerBrowserTest(false) {} |
| |
| apps::mojom::Readiness GetAppReadiness(const AppId& app_id) { |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(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 = |
| apps::AppServiceProxyFactory::GetForProfile(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::OS_SETTINGS); |
| } |
| 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(); |
| } |
| apps::AppServiceProxyFactory::GetForProfile(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::OS_SETTINGS); |
| } |
| |
| apps::AppServiceProxy* proxy = |
| apps::AppServiceProxyFactory::GetForProfile(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_TEST_SUITE_P(All, |
| SystemWebAppManagerAppSuspensionBrowserTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| #endif // defined(OS_CHROMEOS) |
| |
| // We test with and without enabling kDesktopPWAsWithoutExtensions. |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerBrowserTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerLaunchFilesBrowserTest, |
| testing::Bool()); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerLaunchDirectoryBrowserTest, |
| testing::Bool()); |
| |
| #if defined(OS_CHROMEOS) |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest, |
| testing::Bool()); |
| #endif |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerNotShownInLauncherTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerNotShownInSearchTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerAdditionalSearchTermsTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerChromeUntrustedTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerOriginTrialsBrowserTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerFileHandlingOriginTrialsBrowserTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SystemWebAppManagerUninstallBrowserTest, |
| ::testing::Values(ProviderType::kBookmarkApps, |
| ProviderType::kWebApps), |
| ProviderTypeParamToString); |
| |
| } // namespace web_app |