// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/cpp/shelf_item_delegate.h"
#include "ash/public/cpp/shelf_model.h"
#include "ash/webui/system_apps/public/system_web_app_type.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/gtest_tags.h"
#include "base/test/scoped_feature_list.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/apps/app_service/app_icon/app_icon_factory.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/apps/app_service/launch_utils.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/speech_monitor.h"
#include "chrome/browser/ash/app_list/app_list_client_impl.h"
#include "chrome/browser/ash/app_list/app_list_model_updater.h"
#include "chrome/browser/ash/app_list/test/chrome_app_list_test_support.h"
#include "chrome/browser/ash/extensions/default_app_order.h"
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#include "chrome/browser/ash/file_manager/volume.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h"
#include "chrome/browser/ash/system_web_apps/test_support/test_system_web_app_installation.h"
#include "chrome/browser/file_system_access/file_system_access_permission_request_manager.h"
#include "chrome/browser/policy/system_features_disable_list_policy_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller_util.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/proto/web_app.pb.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.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 "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/permissions/permission_util.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/services/app_service/public/cpp/app_launch_util.h"
#include "components/services/app_service/public/cpp/app_registry_cache.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/app_update.h"
#include "components/services/app_service/public/cpp/intent_util.h"
#include "components/services/app_service/public/cpp/types_util.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.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/browsertest_util.h"
#include "extensions/common/constants.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/context_menu/context_menu.mojom.h"
#include "ui/base/idle/idle.h"
#include "ui/base/idle/scoped_set_idle_state.h"
#include "ui/display/display.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/test/event_generator.h"
namespace ash {
namespace {
// Helper to call AppServiceProxyFactory::GetForProfile().
apps::AppServiceProxyBase* 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
using SystemWebAppManagerBrowserTestBasicInstall =
SystemWebAppManagerBrowserTest;
// Test that System Apps install correctly with a manifest.
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTestBasicInstall, 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(GetAppType(), &app_browser);
webapps::AppId app_id = app_browser->app_controller()->app_id();
EXPECT_EQ(GetManager().GetAppIdForSystemApp(GetAppType()), app_id);
EXPECT_TRUE(GetManager().IsSystemWebApp(app_id));
Profile* profile = app_browser->profile();
web_app::WebAppRegistrar& registrar =
web_app::WebAppProvider::GetForTest(profile)->registrar_unsafe();
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);
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [](const apps::AppUpdate& update) {
EXPECT_TRUE(update.ShowInLauncher().value_or(false));
EXPECT_TRUE(update.ShowInSearch().value_or(false));
EXPECT_FALSE(update.ShowInManagement().value_or(true));
EXPECT_EQ(apps::Readiness::kReady, update.Readiness());
});
}
// 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) {
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(GetAppType(), &app_browser);
// In scope, the toolbar should not be visible.
EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar());
// Out of scope chrome:// URL.
GURL out_of_scope_chrome_page("chrome://foo");
content::NavigateToURLBlockUntilNavigationsComplete(
app_browser->tab_strip_model()->GetActiveWebContents(),
out_of_scope_chrome_page, 1);
EXPECT_TRUE(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());
// URL has been added to be within scope for the SWA.
GURL in_scope_for_swa_page("http://example.com/in-scope");
content::NavigateToURLBlockUntilNavigationsComplete(
app_browser->tab_strip_model()->GetActiveWebContents(),
in_scope_for_swa_page, 1);
EXPECT_FALSE(app_browser->app_controller()->ShouldShowCustomTabBar());
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest, LaunchMetricsWork) {
WaitForTestSystemAppInstall();
base::HistogramTester histograms;
content::TestNavigationObserver navigation_observer(GetStartUrl());
navigation_observer.StartWatchingNewWebContents();
ash::SystemAppLaunchParams params;
params.launch_source = apps::LaunchSource::kFromAppListGrid;
LaunchSystemWebAppAsync(browser()->profile(), GetAppType(), params);
navigation_observer.Wait();
histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromAppListGrid", 1);
histograms.ExpectUniqueSample("Apps.DefaultAppLaunch.FromAppListGrid", 39, 1);
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest,
LaunchMetricsWorkFromAppProxy) {
WaitForTestSystemAppInstall();
base::HistogramTester histograms;
content::TestNavigationObserver navigation_observer(GetStartUrl());
navigation_observer.StartWatchingNewWebContents();
auto* proxy = GetAppServiceProxy(browser()->profile());
proxy->Launch(GetManager().GetAppIdForSystemApp(GetAppType()).value(),
ui::EF_NONE, apps::LaunchSource::kFromAppListGrid,
std::make_unique<apps::WindowInfo>(display::kDefaultDisplayId));
navigation_observer.Wait();
histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromAppListGrid", 1);
histograms.ExpectUniqueSample("Apps.DefaultAppLaunch.FromAppListGrid", 39, 1);
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest,
LaunchMetricsWorkWithIntent) {
WaitForTestSystemAppInstall();
base::HistogramTester histograms;
content::TestNavigationObserver navigation_observer(GetStartUrl());
navigation_observer.StartWatchingNewWebContents();
auto* proxy = GetAppServiceProxy(browser()->profile());
auto intent = std::make_unique<apps::Intent>(apps_util::kIntentActionView);
intent->mime_type = "text/plain";
proxy->LaunchAppWithIntent(
GetManager().GetAppIdForSystemApp(GetAppType()).value(), ui::EF_NONE,
std::move(intent), apps::LaunchSource::kFromAppListGrid,
std::make_unique<apps::WindowInfo>(display::kDefaultDisplayId),
base::DoNothing());
navigation_observer.Wait();
histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromAppListGrid", 1);
histograms.ExpectUniqueSample("Apps.DefaultAppLaunch.FromAppListGrid", 39, 1);
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest, UpdatesLaunchStats) {
WaitForTestSystemAppInstall();
auto app_id = GetManager().GetAppIdForSystemApp(GetAppType()).value();
content::TestNavigationObserver navigation_observer(GetStartUrl());
navigation_observer.StartWatchingNewWebContents();
base::Time launch_start_time = base::Time::Now();
ash::SystemAppLaunchParams params;
params.launch_source = apps::LaunchSource::kFromAppListGrid;
LaunchSystemWebAppAsync(browser()->profile(), GetAppType(), params);
navigation_observer.Wait();
auto* proxy = GetAppServiceProxy(browser()->profile());
EXPECT_TRUE(proxy->AppRegistryCache().ForOneApp(
app_id,
[&](const apps::AppUpdate& update) {
EXPECT_GE(update.LastLaunchTime(), launch_start_time);
}))
<< "Expect app to exist";
}
class SystemWebAppManagerLaunchWithUrlBrowserTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerLaunchWithUrlBrowserTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppLaunchWithUrl());
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchWithUrlBrowserTest,
LaunchWithCallback) {
WaitForTestSystemAppInstall();
content::TestNavigationObserver navigation_observer(GetStartUrl());
navigation_observer.StartWatchingNewWebContents();
ash::SystemAppLaunchParams params;
params.launch_source = apps::LaunchSource::kFromOtherApp;
params.url = GetStartUrl();
bool is_called = false;
LaunchSystemWebAppAsync(
browser()->profile(), GetAppType(), params, nullptr,
base::BindLambdaForTesting(
[&is_called](apps::LaunchResult&& callback_result) {
is_called = true;
}));
navigation_observer.Wait();
EXPECT_TRUE(is_called);
}
class SystemWebAppManagerFileHandlingBrowserTestBase
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
using IncludeLaunchDirectory =
TestSystemWebAppInstallation::IncludeLaunchDirectory;
explicit SystemWebAppManagerFileHandlingBrowserTestBase(
IncludeLaunchDirectory include_launch_directory) {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppThatReceivesLaunchFiles(
include_launch_directory));
}
content::WebContents* LaunchApp(std::vector<base::FilePath> launch_files,
bool wait_for_load = true) {
apps::AppLaunchParams params = LaunchParamsForApp(GetAppType());
params.launch_source = apps::LaunchSource::kFromChromeInternal;
params.override_url = GetStartUrl();
params.launch_files = std::move(launch_files);
return SystemWebAppBrowserTestBase::LaunchApp(std::move(params));
}
content::WebContents* LaunchAppWithoutWaiting(
std::vector<base::FilePath> launch_files) {
apps::AppLaunchParams params = LaunchParamsForApp(GetAppType());
params.launch_source = apps::LaunchSource::kFromChromeInternal;
params.override_url = GetStartUrl();
params.launch_files = std::move(launch_files);
return SystemWebAppBrowserTestBase::LaunchAppWithoutWaiting(
std::move(params));
}
// Must be called before WaitAndExposeLaunchParamsToWindow. This sets up the
// promise used to wait for launchParam callback.
[[nodiscard]] ::testing::AssertionResult PrepareToReceiveLaunchParams(
content::WebContents* web_contents) {
return content::ExecJs(
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.
[[nodiscard]] ::testing::AssertionResult WaitAndExposeLaunchParamsToWindow(
content::WebContents* web_contents,
const std::string js_property_name = "launchParams") {
return content::ExecJs(
web_contents,
content::JsReplace("window.launchParamsPromise.then(launchParams => {"
" window[$1] = launchParams"
"})",
js_property_name));
}
private:
base::test::ScopedFeatureList scoped_feature_web_app_provider_type_;
};
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(),
content::EvalJs(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(),
content::EvalJs(web_contents, "window.launchParams2.files[0].name"));
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchFilesBrowserTest,
LaunchMetricsWorks) {
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));
base::HistogramTester histograms;
content::TestNavigationObserver navigation_observer(GetStartUrl());
navigation_observer.StartWatchingNewWebContents();
ash::SystemAppLaunchParams params;
params.launch_paths = {temp_file_path};
params.launch_source = apps::LaunchSource::kFromOtherApp;
LaunchSystemWebAppAsync(browser()->profile(), GetAppType(), params);
navigation_observer.Wait();
histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromOtherApp", 1);
}
class SystemWebAppManagerLaunchDirectoryBrowserTest
: public SystemWebAppManagerFileHandlingBrowserTestBase {
public:
SystemWebAppManagerLaunchDirectoryBrowserTest()
: SystemWebAppManagerFileHandlingBrowserTestBase(
IncludeLaunchDirectory::kYes) {}
// Returns the content of |file_handle_or_promise| file handle.
[[nodiscard]] content::EvalJsResult ReadContentFromJsFileHandle(
content::WebContents* web_contents,
const std::string& file_handle_or_promise) {
return content::EvalJs(web_contents,
"Promise.resolve(" + file_handle_or_promise + ")" +
".then(async fileHandle => {"
" const file = await fileHandle.getFile();"
" return file.text();"
"})");
}
// Writes |content_to_write| to |file_handle_or_promise| file handle.
[[nodiscard]] ::testing::AssertionResult WriteContentToJsFileHandle(
content::WebContents* web_contents,
const std::string& file_handle_or_promise,
const std::string& content_to_write) {
return content::ExecJs(
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();"
"})",
content_to_write));
}
// Remove file by |file_name| from |dir_handle_or_promise| directory handle.
[[nodiscard]] ::testing::AssertionResult RemoveFileFromJsDirectoryHandle(
content::WebContents* web_contents,
const std::string& dir_handle_or_promise,
const std::string& file_name) {
return content::ExecJs(
web_contents, content::JsReplace(
"Promise.resolve(" + dir_handle_or_promise + ")" +
".then(dir_handle => dir_handle.removeEntry($1))",
file_name));
}
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).
FileSystemAccessPermissionRequestManager::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", content::EvalJs(web_contents,
"window.launchParams1.files[0].kind"));
EXPECT_EQ(
temp_directory.GetPath().BaseName().AsUTF8Unsafe(),
content::EvalJs(web_contents, "window.launchParams1.files[0].name"));
EXPECT_EQ("file", content::EvalJs(web_contents,
"window.launchParams1.files[1].kind"));
EXPECT_EQ(
temp_file_path.BaseName().AsUTF8Unsafe(),
content::EvalJs(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", content::EvalJs(web_contents,
"window.launchParams2.files[0].kind"));
EXPECT_EQ(
temp_directory2.GetPath().BaseName().AsUTF8Unsafe(),
content::EvalJs(web_contents, "window.launchParams2.files[0].name"));
EXPECT_EQ("file", content::EvalJs(web_contents,
"window.launchParams2.files[1].kind"));
EXPECT_EQ(
temp_file_path2.BaseName().AsUTF8Unsafe(),
content::EvalJs(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
// FileSystemAccess 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);
}
// Base class for testing File Handling and File System Access with Chrome OS
// File System Provider features.
class SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest
: public SystemWebAppManagerLaunchDirectoryBrowserTest {
public:
[[nodiscard]] content::EvalJsResult FileHandleIsGif(
content::WebContents* web_contents,
const std::string& file_handle_or_promise) {
return content::EvalJs(
web_contents,
"Promise.resolve(" + file_handle_or_promise + ")" +
".then(async fileHandle => {"
" const file = await fileHandle.getFile();"
" const arrayBuf = await file.arrayBuffer();"
" const bytes = new Uint8Array(arrayBuf.slice(0, 3));"
" return bytes[0] === 0x47 /* G */"
" && bytes[1] === 0x49 /* I */"
" && bytes[2] === 0x46; /* F */"
"});");
}
[[nodiscard]] content::EvalJsResult FileHandleIsPng(
content::WebContents* web_contents,
const std::string& file_handle_or_promise) {
return content::EvalJs(
web_contents,
"Promise.resolve(" + file_handle_or_promise + ")" +
".then(async fileHandle => {"
" const file = await fileHandle.getFile();"
" const arrayBuf = await file.arrayBuffer();"
" const bytes = new Uint8Array(arrayBuf.slice(0, 4));"
" return bytes[0] === 0x89 /* 0x89 */"
" && bytes[1] === 0x50 /* P */"
" && bytes[2] === 0x4E /* N */"
" && bytes[3] === 0x47; /* G */"
"});");
}
// Returns whether the file is written.
[[nodiscard]] ::testing::AssertionResult CanWriteFile(
content::WebContents* web_contents,
const std::string& file_handle_or_promise) {
return content::ExecJs(
web_contents,
"Promise.resolve(" + file_handle_or_promise + ")" +
".then(async fileHandle => {"
" const writable = await fileHandle.createWritable();"
" await writable.write('test');"
" await writable.close();"
"});");
}
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) {
// TODO(b/287166490): Fix the test and remove this.
if (GetParam().crosapi_state == TestProfileParam::CrosapiParam::kEnabled) {
GTEST_SKIP()
<< "Skipping test body for CrosapiParam::kEnabled, see b/287166490.";
}
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,
content::EvalJs(web_contents, "window.launchParams.files[1].name"));
EXPECT_EQ(true,
FileHandleIsGif(web_contents, "window.launchParams.files[1]"));
// Check we can list the directory.
EXPECT_EQ(base::StrCat({kTestPngFile, ";", kTestGifFile}),
content::EvalJs(
web_contents,
"(async function() {"
" let fileNames = [];"
" const files = await window.launchParams.files[0].keys();"
" for await (const name of files)"
" fileNames.push(name);"
" return fileNames.sort().join(';');"
"})();"));
// Verify we can read a file (other than launch file) inside the directory.
EXPECT_EQ(true, FileHandleIsPng(
web_contents,
content::JsReplace(
"window.launchParams.files[0].getFileHandle($1)",
kTestPngFile)));
}
// Test that the File System Access implementation doesn't cause a crash when
// writing to readonly files.
IN_PROC_BROWSER_TEST_P(
SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest,
LaunchFromFileSystemProvider_WriteFileFails) {
// TODO(b/287166490): Fix the test and remove this.
if (GetParam().crosapi_state == TestProfileParam::CrosapiParam::kEnabled) {
GTEST_SKIP()
<< "Skipping test body for CrosapiParam::kEnabled, see b/287166490.";
}
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(CanWriteFile(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::ExecJs(web_contents, "(function(){})();"));
}
// Test that the File System Access implementation doesn't cause a crash when
// deleting readonly files.
IN_PROC_BROWSER_TEST_P(
SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest,
LaunchFromFileSystemProvider_DeleteFileFails) {
// TODO(b/287166490): Fix the test and remove this.
if (GetParam().crosapi_state == TestProfileParam::CrosapiParam::kEnabled) {
GTEST_SKIP()
<< "Skipping test body for CrosapiParam::kEnabled, see b/287166490.";
}
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"));
// Deleting the file should fail.
EXPECT_FALSE(content::ExecJs(
web_contents,
content::JsReplace("window.launchParams.files[0].removeEntry($1)",
"readonly.png")));
// Do a no-op JavaScript to check the page is still operational. If the page
// crashed, the following call will fail.
EXPECT_TRUE(content::ExecJs(web_contents, "(function() {})();"));
}
class SystemWebAppManagerNotShownInLauncherTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerNotShownInLauncherTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppNotShownInLauncher());
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInLauncherTest,
NotShownInLauncher) {
WaitForTestSystemAppInstall();
webapps::AppId app_id =
GetManager().GetAppIdForSystemApp(GetAppType()).value();
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [](const apps::AppUpdate& update) {
EXPECT_FALSE(update.ShowInLauncher().value_or(true));
});
// 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);
}
class SystemWebAppManagerNotShownInSearchTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerNotShownInSearchTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppNotShownInSearch());
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInSearchTest,
NotShownInSearch) {
WaitForTestSystemAppInstall();
webapps::AppId app_id =
GetManager().GetAppIdForSystemApp(GetAppType()).value();
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [](const apps::AppUpdate& update) {
EXPECT_FALSE(update.ShowInSearch().value_or(true));
});
}
class SystemWebAppManagerHandlesFileOpenIntentsTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerHandlesFileOpenIntentsTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppThatHandlesFileOpenIntents());
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerHandlesFileOpenIntentsTest,
HandlesFileOpenIntents) {
WaitForTestSystemAppInstall();
webapps::AppId app_id =
GetManager().GetAppIdForSystemApp(GetAppType()).value();
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [](const apps::AppUpdate& update) {
EXPECT_TRUE(update.HandlesIntents().value_or(false));
});
}
class SystemWebAppManagerAdditionalSearchTermsTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerAdditionalSearchTermsTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithAdditionalSearchTerms());
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAdditionalSearchTermsTest,
AdditionalSearchTerms) {
WaitForTestSystemAppInstall();
webapps::AppId app_id =
GetManager().GetAppIdForSystemApp(GetAppType()).value();
// AdditionalSearchTerms is flaky on Windows as it's a Chrome OS feature.
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [](const apps::AppUpdate& update) {
EXPECT_EQ(std::vector<std::string>({"Security"}),
update.AdditionalSearchTerms());
});
}
class SystemWebAppManagerHasTabStripWithNewTabButtonTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerHasTabStripWithNewTabButtonTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithTabStrip(
/*has_tab_strip=*/true, /*hide_new_tab_button=*/false));
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerHasTabStripWithNewTabButtonTest,
ShouldHaveTabStripWithNewTabButton) {
WaitForTestSystemAppInstall();
Browser* browser;
EXPECT_TRUE(LaunchApp(GetAppType(), &browser));
EXPECT_TRUE(browser->app_controller()->has_tab_strip());
EXPECT_FALSE(browser->app_controller()->ShouldHideNewTabButton());
}
class SystemWebAppManagerHasTabStripWithHiddenNewTabButtonTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerHasTabStripWithHiddenNewTabButtonTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithTabStrip(
/*has_tab_strip=*/true, /*hide_new_tab_button=*/true));
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerHasTabStripWithHiddenNewTabButtonTest,
HasTabStripWithNoNewTabButton) {
WaitForTestSystemAppInstall();
Browser* browser;
EXPECT_TRUE(LaunchApp(GetAppType(), &browser));
EXPECT_TRUE(browser->app_controller()->has_tab_strip());
EXPECT_TRUE(browser->app_controller()->ShouldHideNewTabButton());
}
class SystemWebAppManagerHasNoTabStripWithNewTabButtonTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerHasNoTabStripWithNewTabButtonTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithTabStrip(
/*has_tab_strip=*/false, /*hide_new_tab_button=*/false));
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerHasNoTabStripWithNewTabButtonTest,
HasNoTabStripWithNoNewTabButton) {
WaitForTestSystemAppInstall();
Browser* browser;
EXPECT_TRUE(LaunchApp(GetAppType(), &browser));
EXPECT_FALSE(browser->app_controller()->has_tab_strip());
EXPECT_TRUE(browser->app_controller()->ShouldHideNewTabButton());
}
class SystemWebAppManagerHasNoTabStripWithHiddenNewTabButtonTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerHasNoTabStripWithHiddenNewTabButtonTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithTabStrip(
/*has_tab_strip=*/false, /*hide_new_tab_button=*/true));
}
};
IN_PROC_BROWSER_TEST_P(
SystemWebAppManagerHasNoTabStripWithHiddenNewTabButtonTest,
HasNoTabStripWithNoNewTabButton) {
WaitForTestSystemAppInstall();
Browser* browser;
EXPECT_TRUE(LaunchApp(GetAppType(), &browser));
EXPECT_FALSE(browser->app_controller()->has_tab_strip());
EXPECT_TRUE(browser->app_controller()->ShouldHideNewTabButton());
}
// We only support custom bounds on Chrome OS.
class SystemWebAppManagerDefaultBoundsTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerDefaultBoundsTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithDefaultBounds(
kDefaultBounds));
}
protected:
const gfx::Rect kDefaultBounds = {0, 0, 333, 444};
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerDefaultBoundsTest, HasDefaultBounds) {
WaitForTestSystemAppInstall();
Browser* browser;
EXPECT_TRUE(LaunchApp(GetAppType(), &browser));
EXPECT_EQ(kDefaultBounds, browser->app_controller()->GetDefaultBounds());
EXPECT_EQ(kDefaultBounds, browser->window()->GetBounds());
}
// Tests that SWA are correctly uninstalled across restarts.
class SystemWebAppManagerUninstallBrowserTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerUninstallBrowserTest() {
if (content::IsPreTest()) {
// Use an app with FileHandling enabled since it will perform extra setup
// steps.
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppThatReceivesLaunchFiles(
TestSystemWebAppInstallation::IncludeLaunchDirectory::kNo));
} else {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpWithoutApps());
}
}
~SystemWebAppManagerUninstallBrowserTest() override = default;
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerUninstallBrowserTest, PRE_Uninstall) {
WaitForTestSystemAppInstall();
EXPECT_TRUE(GetManager().GetAppIdForSystemApp(GetAppType()).has_value());
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerUninstallBrowserTest, Uninstall) {
WaitForTestSystemAppInstall();
EXPECT_TRUE(GetManager().GetAppIds().empty());
auto* app_service_proxy =
apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
bool swa_found = false;
app_service_proxy->AppRegistryCache().ForEachApp(
[&](const apps::AppUpdate& app) {
if ((app.AppType() == apps::AppType::kSystemWeb ||
app.AppType() == apps::AppType::kWeb) &&
apps_util::IsInstalled(app.Readiness())) {
swa_found = true;
}
});
EXPECT_FALSE(swa_found);
}
// Test that all registered System Apps can be re-installed.
class SystemWebAppManagerInstallAllAppsBrowserTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerInstallAllAppsBrowserTest() {
features_.InitAndEnableFeature(features::kEnableAllSystemWebApps);
}
~SystemWebAppManagerInstallAllAppsBrowserTest() override = default;
private:
base::test::ScopedFeatureList features_;
};
// TODO(crbug.com/40162953): At the moment, PRE_Test failures aren't
// reported in test summary, thus won't fail the CI build job. So we need a
// ordinary test to fail the job and block CQ.
//
// Technically speaking, this test can merge into PRE_Upgrade if the
// aforementioned crbug is fixed.
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerInstallAllAppsBrowserTest,
BasicConsistencyCheck) {
// Wait for apps to install before performing assertions, otherwise the test
// might flake. See https://crbug.com/1286600#c6.
GetManager().InstallSystemAppsForTesting();
const auto& app_map = GetManager().system_app_delegates();
ASSERT_GT(app_map.size(), 0U);
for (const auto& type_and_info : app_map) {
// Check all system app types has a corresponding SystemWebAppDataProto
// entry defined.
EXPECT_TRUE(SystemWebAppDataProto_SystemWebAppType_IsValid(
static_cast<SystemWebAppDataProto_SystemWebAppType>(
type_and_info.first)))
<< "Please make sure you have added a corresponding entry to "
"SystemWebAppDataProto when adding a new System Web App.";
// Check app's install_url and start_url are from the same origin.
//
// TODO(crbug.com/40709016): Include OS Settings in this check.
//
// OS Settings uses a different install_url origin (by mistake) which are
// persisted to disk. We can't fix it until the above crbug is fixed.
// Without fixing the above bug, non-fresh profiles will run into
// https://crbug.com/1220354.
if (type_and_info.first != SystemWebAppType::SETTINGS) {
EXPECT_TRUE(url::IsSameOriginWith(
type_and_info.second->GetInstallUrl(),
type_and_info.second->GetWebAppInfo()->start_url()));
}
// Check app's web app shortcuts fields is self-consistent.
auto install_info = type_and_info.second->GetWebAppInfo();
EXPECT_EQ(install_info->shortcuts_menu_icon_bitmaps.size(),
install_info->shortcuts_menu_item_infos.size());
}
// Check each SWA app has their own unique origin (i.e. doesn't share origin
// with a different app).
std::set<url::Origin> install_url_origins;
std::set<url::Origin> start_url_origins;
for (const auto& type_and_info : app_map) {
auto install_url_origin =
url::Origin::Create(type_and_info.second->GetInstallUrl());
EXPECT_EQ(0u, install_url_origins.count(install_url_origin))
<< "System web app's install_url origin should be unique.";
install_url_origins.insert(install_url_origin);
auto start_url_origin =
url::Origin::Create(type_and_info.second->GetWebAppInfo()->start_url());
EXPECT_EQ(0u, start_url_origins.count(start_url_origin))
<< "System web app's start_url origin should be unique.";
start_url_origins.insert(start_url_origin);
}
// Check apps (other than Terminal, which is published by its own App
// publisher) are exposed in AppService.
for (const auto& type_and_info : app_map) {
if (type_and_info.first == SystemWebAppType::TERMINAL)
continue;
std::optional<std::string> app_id =
GetManager().GetAppIdForSystemApp(type_and_info.first);
EXPECT_TRUE(app_id);
bool app_found = false;
apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
->AppRegistryCache()
.ForOneApp(*app_id, [&](const apps::AppUpdate& app) {
app_found = true;
EXPECT_EQ(
app.Name(),
base::UTF16ToUTF8(type_and_info.second->GetWebAppInfo()->title));
});
EXPECT_TRUE(app_found) << "System Web App "
<< type_and_info.second->GetInternalName()
<< " can't be found in AppService after install.";
}
// Verify that all system web apps which are enabled by default and appear in
// the launcher have an explicit launcher position set.
std::vector<std::string> app_order;
chromeos::default_app_order::Get(&app_order);
// Demo/testing apps don't need a launcher position.
const base::flat_set<SystemWebAppType> kLauncherPositionExemptTypes = {
SystemWebAppType::SAMPLE};
for (const auto& [app_type, app_delegate] : app_map) {
if (app_delegate->IsAppEnabled() && app_delegate->ShouldShowInLauncher() &&
!base::Contains(kLauncherPositionExemptTypes, app_type)) {
EXPECT_TRUE(base::Contains(app_order,
GetManager().GetAppIdForSystemApp(app_type)))
<< "System app '" << app_delegate->GetInternalName()
<< "' appears in the launcher but does not have an app order "
"definition. Its app ID should be added to GetDefault() in "
"//chrome/browser/ash/extensions/default_app_order.cc, which "
"should match the order in go/default-apps";
}
}
// Verify that all system web apps have an icon.
for (const auto& [_, delegate] : app_map) {
const auto info = delegate->GetWebAppInfo();
EXPECT_FALSE(info->manifest_icons.empty())
<< delegate->GetInternalName() << " needs a manifest icon";
EXPECT_FALSE(delegate->GetWebAppInfo()->icon_bitmaps.empty())
<< delegate->GetInternalName() << " needs an icon bitmap";
}
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerInstallAllAppsBrowserTest, Upgrade) {
GetManager().InstallSystemAppsForTesting();
const auto& app_ids = GetManager().GetAppIds();
EXPECT_EQ(GetManager().system_app_delegates().size(), app_ids.size());
// Some system web apps keep their resources (e.g. html pages) in real
// Chrome OS images. Here we test a few apps whose resources are bundled in
// chrome and always available. These apps are able to cover the code path we
// execute when launching the app.
const SystemWebAppType apps_to_launch[] = {
SystemWebAppType::SETTINGS,
SystemWebAppType::MEDIA, // Uses File Handling with launch directory
};
for (const auto& type : apps_to_launch) {
EXPECT_TRUE(LaunchApp(type));
}
}
class SystemWebAppManagerChromeUntrustedTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerChromeUntrustedTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpChromeUntrustedApp());
}
};
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(GetAppType(), &app_browser);
webapps::AppId app_id =
GetManager().GetAppIdForSystemApp(GetAppType()).value();
EXPECT_EQ(app_id, app_browser->app_controller()->app_id());
EXPECT_TRUE(GetManager().IsSystemWebApp(app_id));
Profile* profile = app_browser->profile();
web_app::WebAppRegistrar& registrar =
web_app::WebAppProvider::GetForTest(profile)->registrar_unsafe();
EXPECT_EQ("Test System App Untrusted", registrar.GetAppShortName(app_id));
EXPECT_EQ(SkColorSetRGB(0xFF, 0, 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 TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerOriginTrialsBrowserTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithEnabledOriginTrials(
OriginTrialsMap({{GetOrigin(main_url_), main_url_trials_},
{GetOrigin(trial_url_), trial_url_trials_}})));
}
~SystemWebAppManagerOriginTrialsBrowserTest() override = default;
protected:
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();
auto app_id = GetManager().GetAppIdForSystemApp(GetAppType()).value();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
web_app::WebAppTabHelper::CreateForWebContents(web_contents.get());
auto& tab_helper =
*web_app::WebAppTabHelper::FromWebContents(web_contents.get());
// Simulate when first navigating into app's launch url.
{
content::MockNavigationHandle mock_nav_handle(main_url_, nullptr);
mock_nav_handle.set_is_in_primary_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(app_id, *web_app::WebAppTabHelper::GetAppId(web_contents.get()));
}
// Simulate loading app's embedded child-frame that has origin trials.
{
content::MockNavigationHandle mock_nav_handle(trial_url_, nullptr);
mock_nav_handle.set_is_in_primary_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.
{
content::MockNavigationHandle mock_nav_handle(notrial_url_, nullptr);
mock_nav_handle.set_is_in_primary_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();
auto app_id = GetManager().GetAppIdForSystemApp(GetAppType()).value();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
web_app::WebAppTabHelper::CreateForWebContents(web_contents.get());
auto& tab_helper =
*web_app::WebAppTabHelper::FromWebContents(web_contents.get());
// Simulate when first navigating into app's launch url.
{
content::MockNavigationHandle mock_nav_handle(main_url_, nullptr);
mock_nav_handle.set_is_in_primary_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(app_id, *web_app::WebAppTabHelper::GetAppId(web_contents.get()));
}
// Simulate same-document navigation.
{
content::MockNavigationHandle mock_nav_handle(main_url_, nullptr);
mock_nav_handle.set_is_in_primary_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();
auto app_id = GetManager().GetAppIdForSystemApp(GetAppType()).value();
std::unique_ptr<content::WebContents> web_contents = CreateTestWebContents();
web_app::WebAppTabHelper::CreateForWebContents(web_contents.get());
auto& tab_helper =
*web_app::WebAppTabHelper::FromWebContents(web_contents.get());
// Simulate when first navigating into app's launch url.
{
content::MockNavigationHandle mock_nav_handle(main_url_, nullptr);
mock_nav_handle.set_is_in_primary_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(app_id, *web_app::WebAppTabHelper::GetAppId(web_contents.get()));
}
// Simulate navigating to a different site without origin trials.
{
content::MockNavigationHandle mock_nav_handle(notrial_url_, nullptr);
mock_nav_handle.set_is_in_primary_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(nullptr, web_app::WebAppTabHelper::GetAppId(web_contents.get()));
}
// Simulate navigating back to a SWA with origin trials.
{
content::MockNavigationHandle mock_nav_handle(main_url_, nullptr);
mock_nav_handle.set_is_in_primary_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(app_id, *web_app::WebAppTabHelper::GetAppId(web_contents.get()));
}
// 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.
{
content::MockNavigationHandle mock_nav_handle(trial_url_, nullptr);
mock_nav_handle.set_is_in_primary_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(nullptr, web_app::WebAppTabHelper::GetAppId(web_contents.get()));
}
}
class SystemWebAppManagerAppSuspensionBrowserTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerAppSuspensionBrowserTest() = default;
apps::Readiness GetAppReadiness(const webapps::AppId& app_id) {
apps::Readiness readiness;
bool app_found =
GetAppServiceProxy(browser()->profile())
->AppRegistryCache()
.ForOneApp(app_id, [&readiness](const apps::AppUpdate& update) {
readiness = update.Readiness();
});
CHECK(app_found);
return readiness;
}
std::optional<apps::IconKey> GetAppIconKey(const webapps::AppId& app_id) {
std::optional<apps::IconKey> icon_key;
bool app_found =
GetAppServiceProxy(browser()->profile())
->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(SystemWebAppType::SETTINGS)
.has_value());
{
ScopedListPrefUpdate update(
TestingBrowserProcess::GetGlobal()->local_state(),
policy::policy_prefs::kSystemFeaturesDisableList);
update->Append(static_cast<int>(policy::SystemFeature::kOsSettings));
}
WaitForTestSystemAppInstall();
std::optional<webapps::AppId> settings_id =
GetManager().GetAppIdForSystemApp(SystemWebAppType::SETTINGS);
DCHECK(settings_id.has_value());
EXPECT_EQ(apps::Readiness::kDisabledByPolicy, GetAppReadiness(*settings_id));
EXPECT_TRUE(apps::IconEffects::kBlocked &
GetAppIconKey(*settings_id)->icon_effects);
{
ScopedListPrefUpdate update(
TestingBrowserProcess::GetGlobal()->local_state(),
policy::policy_prefs::kSystemFeaturesDisableList);
update->clear();
}
SystemWebAppManager::GetWebAppProvider(browser()->profile())
->command_manager()
.AwaitAllCommandsCompleteForTesting();
EXPECT_EQ(apps::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) {
base::AddFeatureIdTagToTestResult(
"screenplay-44570758-2d0f-4ed9-8172-102244523249");
WaitForTestSystemAppInstall();
std::optional<webapps::AppId> settings_id =
GetManager().GetAppIdForSystemApp(SystemWebAppType::SETTINGS);
DCHECK(settings_id.has_value());
EXPECT_EQ(apps::Readiness::kReady, GetAppReadiness(*settings_id));
{
ScopedListPrefUpdate update(
TestingBrowserProcess::GetGlobal()->local_state(),
policy::policy_prefs::kSystemFeaturesDisableList);
update->Append(static_cast<int>(policy::SystemFeature::kOsSettings));
}
SystemWebAppManager::GetWebAppProvider(browser()->profile())
->command_manager()
.AwaitAllCommandsCompleteForTesting();
EXPECT_EQ(apps::Readiness::kDisabledByPolicy, GetAppReadiness(*settings_id));
EXPECT_TRUE(apps::IconEffects::kBlocked &
GetAppIconKey(*settings_id)->icon_effects);
{
ScopedListPrefUpdate update(
TestingBrowserProcess::GetGlobal()->local_state(),
policy::policy_prefs::kSystemFeaturesDisableList);
update->clear();
}
SystemWebAppManager::GetWebAppProvider(browser()->profile())
->command_manager()
.AwaitAllCommandsCompleteForTesting();
EXPECT_EQ(apps::Readiness::kReady, GetAppReadiness(*settings_id));
EXPECT_FALSE(apps::IconEffects::kBlocked &
GetAppIconKey(*settings_id)->icon_effects);
}
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerAppSuspensionBrowserTest);
class SystemWebAppManagerShortcutTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerShortcutTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithShortcuts());
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerShortcutTest, ShortcutUrl) {
WaitForTestSystemAppInstall();
webapps::AppId app_id =
GetManager()
.GetAppIdForSystemApp(SystemWebAppType::SHORTCUT_CUSTOMIZATION)
.value();
Browser* browser;
content::WebContents* web_contents =
LaunchApp(SystemWebAppType::SHORTCUT_CUSTOMIZATION, &browser);
EXPECT_TRUE(web_contents);
std::unique_ptr<ui::SimpleMenuModel> menu_model;
{
ShelfModel* const shelf_model = ShelfModel::Get();
PinAppWithIDToShelf(app_id);
ShelfItemDelegate* const delegate =
shelf_model->GetShelfItemDelegate(ShelfID(app_id));
base::RunLoop run_loop;
delegate->GetContextMenu(
display::Display::GetDefaultDisplay().id(),
base::BindLambdaForTesting(
[&run_loop,
&menu_model](std::unique_ptr<ui::SimpleMenuModel> model) {
menu_model = std::move(model);
run_loop.Quit();
}));
run_loop.Run();
}
auto check_shortcut = [&menu_model](size_t index, int shortcut_index,
const std::u16string& label) {
EXPECT_EQ(menu_model->GetTypeAt(index), ui::MenuModel::TYPE_COMMAND);
EXPECT_EQ(menu_model->GetCommandIdAt(index),
LAUNCH_APP_SHORTCUT_FIRST + shortcut_index);
EXPECT_EQ(menu_model->GetLabelAt(index), label);
};
// Shortcuts appear last in the context menu.
check_shortcut(menu_model->GetItemCount() - 3, 0, u"One");
// menu_model->GetItemCount() - 2 is used by a separator
check_shortcut(menu_model->GetItemCount() - 1, 1, u"Two");
const int command_id = LAUNCH_APP_SHORTCUT_FIRST + 1;
content::LoadStopObserver url_observer(web_contents);
menu_model->ActivatedAt(menu_model->GetIndexOfCommandId(command_id).value(),
ui::EF_LEFT_MOUSE_BUTTON);
url_observer.Wait();
}
class SystemWebAppManagerBackgroundTaskTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerBackgroundTaskTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppWithBackgroundTask());
}
void WaitForSystemAppsBackgroundTasksStart() {
base::RunLoop run_loop;
SystemWebAppManager::Get(browser()->profile())
->on_tasks_started()
.Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBackgroundTaskTest, TimerFires) {
// The SystemWebAppManager gets created in the Setup(), in the test
// constructor, and the background tasks get created during synchronize.
// Ideally, we'd make a TestNavigationObserver in the constructor, but they
// have to be single threaded, and throw a check fail. There's a race
// condition here because the background tasks are fired as callbacks in
// response to the install finishing. So, we wait for the apps to be
// installed, then wait on the navigation. A cleaner solution would be to have
// a hook in the background pages to detect the navigation as an event. That's
// a little too much work for one test though, and since this is mostly tested
// in unittests, this is probably enough.
content::TestNavigationObserver navigation_observer(
GURL("chrome://test-system-app/page2.html"));
navigation_observer.StartWatchingNewWebContents();
ui::ScopedSetIdleState idle(ui::IDLE_STATE_IDLE);
WaitForSystemAppsBackgroundTasksStart();
auto& tasks = GetManager().GetBackgroundTasksForTesting();
auto* timer = tasks[0]->get_timer_for_testing();
EXPECT_EQ(base::Seconds(120), timer->GetCurrentDelay());
EXPECT_EQ(SystemWebAppBackgroundTask::INITIAL_WAIT,
tasks[0]->get_state_for_testing());
// The "Immediate" timer waits for 2 minutes, and it's really hard to mock
// time properly in a browser test, so just fire the thing now. We're not
// testing that base::Timer works.
timer->FireNow();
navigation_observer.Wait();
EXPECT_TRUE(timer->IsRunning());
EXPECT_EQ(1u, tasks.size());
EXPECT_TRUE(tasks[0]->open_immediately_for_testing());
EXPECT_EQ(base::Days(1), tasks[0]->period_for_testing());
EXPECT_EQ(1u, tasks[0]->timer_activated_count_for_testing());
EXPECT_EQ(SystemWebAppBackgroundTask::WAIT_PERIOD,
tasks[0]->get_state_for_testing());
EXPECT_EQ(base::Days(1), timer->GetCurrentDelay());
}
class SystemWebAppManagerContextMenuBrowserTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppManagerContextMenuBrowserTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppsForContestMenuTest());
}
~SystemWebAppManagerContextMenuBrowserTest() override = default;
protected:
std::unique_ptr<TestRenderViewContextMenu> CreateContextMenu(
content::WebContents* web_contents,
const GURL& link_href) {
content::ContextMenuParams params;
params.unfiltered_link_url = link_href;
params.link_url = link_href;
params.src_url = link_href;
params.link_text = std::u16string();
params.media_type = blink::mojom::ContextMenuDataMediaType::kNone;
params.page_url = web_contents->GetVisibleURL();
params.source_type = ui::MENU_SOURCE_NONE;
auto menu = std::make_unique<TestRenderViewContextMenu>(
*web_contents->GetPrimaryMainFrame(), params);
menu->Init();
return menu;
}
// See TestSystemWebAppInstallation::SetUpAppsForContestMenuTest.
const SystemWebAppType kAppTypeSingleWindow = SystemWebAppType::SETTINGS;
const SystemWebAppType kAppTypeMultiWindow = SystemWebAppType::FILE_MANAGER;
const SystemWebAppType kAppTypeSingleWindowTabStrip = SystemWebAppType::MEDIA;
const SystemWebAppType kAppTypeMultiWindowTabStrip = SystemWebAppType::HELP;
};
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerContextMenuBrowserTest,
LinkToAppItself) {
WaitForTestSystemAppInstall();
{
// Single window, no tab strip.
auto* web_contents = LaunchApp(kAppTypeSingleWindow);
auto menu =
CreateContextMenu(web_contents, web_contents->GetLastCommittedURL());
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
{
// Single window, has tab strip.
auto* web_contents = LaunchApp(kAppTypeSingleWindowTabStrip);
auto menu =
CreateContextMenu(web_contents, web_contents->GetLastCommittedURL());
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
{
// Multi window, no tab strip.
auto* web_contents = LaunchApp(kAppTypeMultiWindow);
auto menu =
CreateContextMenu(web_contents, web_contents->GetLastCommittedURL());
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
{
// Multi window, has tab strip.
auto* web_contents = LaunchApp(kAppTypeMultiWindowTabStrip);
auto menu =
CreateContextMenu(web_contents, web_contents->GetLastCommittedURL());
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerContextMenuBrowserTest,
LinkToOtherSystemWebApp) {
WaitForTestSystemAppInstall();
{
// Typical SWA, single window, no tab strip.
auto* web_contents = LaunchApp(kAppTypeSingleWindow);
auto menu = CreateContextMenu(web_contents,
GetStartUrl(kAppTypeSingleWindowTabStrip));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
{
// Deliberately test on a multi-window, tab-strip app to cover edge cases.
auto* web_contents = LaunchApp(kAppTypeMultiWindowTabStrip);
auto menu =
CreateContextMenu(web_contents, GetStartUrl(kAppTypeMultiWindow));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
}
IN_PROC_BROWSER_TEST_P(SystemWebAppManagerContextMenuBrowserTest, WebLink) {
WaitForTestSystemAppInstall();
GURL kWebUrl = GURL("https://example.com/");
{
// Typical SWA, single window, no tab strip.
auto* web_contents = LaunchApp(kAppTypeSingleWindow);
auto menu = CreateContextMenu(web_contents, kWebUrl);
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
{
// Deliberately test on a multi-window, tab-strip app to cover edge cases.
auto* web_contents = LaunchApp(kAppTypeMultiWindowTabStrip);
auto menu = CreateContextMenu(web_contents, kWebUrl);
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWTAB));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKNEWWINDOW));
EXPECT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_FALSE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKBOOKMARKAPP));
}
}
class SystemWebAppSingleWindowTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppSingleWindowTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpStandaloneSingleWindowApp());
}
~SystemWebAppSingleWindowTest() override = default;
};
IN_PROC_BROWSER_TEST_P(SystemWebAppSingleWindowTest, WindowReuse) {
WaitForTestSystemAppInstall();
content::WebContents* web_contents = LaunchApp(GetAppType());
// Second launch reuses the window.
EXPECT_EQ(web_contents, LaunchAppWithoutWaiting(GetAppType()));
// Third launch reuses the window despite different URL.
apps::AppLaunchParams params = LaunchParamsForApp(GetAppType());
params.override_url = GURL("http://example.com/in-scope");
EXPECT_EQ(web_contents, LaunchAppWithoutWaiting(std::move(params)));
}
class SystemWebAppAccessibilityTest : public SystemWebAppSingleWindowTest {
protected:
void EnableChromeVox();
test::SpeechMonitor speech_monitor_;
};
void SystemWebAppAccessibilityTest::EnableChromeVox() {
AccessibilityManager::Get()->EnableSpokenFeedback(true);
speech_monitor_.ExpectSpeechPattern("*");
speech_monitor_.Call([this]() {
extensions::browsertest_util::ExecuteScriptInBackgroundPageDeprecated(
browser()->profile(), extension_misc::kChromeVoxExtensionId, R"JS(
import('/chromevox/background/chromevox_state.js').then(
module => module.ChromeVoxState.ready().then(() =>
window.domAutomationController.send('done')));
)JS");
});
}
IN_PROC_BROWSER_TEST_P(SystemWebAppAccessibilityTest,
CanCycleToWindowControlButtons) {
EnableChromeVox();
WaitForTestSystemAppInstall();
// Launch the app so it shows up in shelf.
Browser* app_browser;
gfx::NativeWindow app_window;
speech_monitor_.Call([&]() {
LaunchApp(GetAppType(), &app_browser);
app_window = app_browser->window()->GetNativeWindow();
// F6 to switch pane.
ui::test::EventGenerator generator(app_window->GetRootWindow(), app_window);
generator.PressAndReleaseKey(ui::VKEY_F6, ui::EF_FINAL);
});
speech_monitor_.ExpectSpeech("Test System App");
speech_monitor_.ExpectSpeech("Application");
// Launcher-B to find minimize button.
speech_monitor_.Call([&]() {
// Search+B to switch pane.
ui::test::EventGenerator generator(app_window->GetRootWindow());
generator.PressAndReleaseKeyAndModifierKeys(
ui::VKEY_B, ui::EF_COMMAND_DOWN | ui::EF_FINAL);
});
speech_monitor_.ExpectSpeech("Minimize");
speech_monitor_.ExpectSpeech("Button");
// Start the actions.
speech_monitor_.Replay();
}
class SystemWebAppAbortsLaunchTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppAbortsLaunchTest() {
SetSystemWebAppInstallation(
TestSystemWebAppInstallation::SetUpAppThatAbortsLaunch());
}
~SystemWebAppAbortsLaunchTest() override = default;
};
IN_PROC_BROWSER_TEST_P(SystemWebAppAbortsLaunchTest, LaunchAborted) {
WaitForTestSystemAppInstall();
LaunchSystemWebAppAsync(browser()->profile(), GetAppType());
EXPECT_EQ(0U, GetSystemWebAppBrowserCount(GetAppType()));
}
class SystemWebAppIconHealthMetricsTest
: public TestProfileTypeMixin<SystemWebAppBrowserTestBase> {
public:
SystemWebAppIconHealthMetricsTest() {
// Only reinstall on version change, so we don't force reinstall
// and overwrite the broken icon.
auto installation = TestSystemWebAppInstallation::SetUpAppWithValidIcons();
installation->set_update_policy(
SystemWebAppManager::UpdatePolicy::kOnVersionChange);
SetSystemWebAppInstallation(std::move(installation));
}
~SystemWebAppIconHealthMetricsTest() override = default;
protected:
static constexpr char kIconsAreHealthyHistogramName[] =
"Webapp.SystemApps.IconsAreHealthyInSession";
base::HistogramTester tester_;
void WaitForInstallAndIconCheck() {
WaitForTestSystemAppInstall();
base::RunLoop run_loop;
SystemWebAppManager::Get(browser()->profile())
->on_icon_check_completed()
.Post(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
};
IN_PROC_BROWSER_TEST_P(SystemWebAppIconHealthMetricsTest, ReportsMetrics) {
WaitForInstallAndIconCheck();
tester_.ExpectBucketCount(kIconsAreHealthyHistogramName, true, 1);
// Given SWA install with no broken icon, pref should report no broken icons.
EXPECT_FALSE(browser()->profile()->GetPrefs()->GetBoolean(
SystemWebAppManager::kSystemWebAppSessionHasBrokenIconsPrefName));
}
IN_PROC_BROWSER_TEST_P(SystemWebAppIconHealthMetricsTest,
PRE_PRE_ReinstallFixesBrokenIcon) {
WaitForInstallAndIconCheck();
// Given SWA install with no broken icon, pref should report no broken icons.
CHECK_EQ(
false,
browser()->profile()->GetPrefs()->GetBoolean(
SystemWebAppManager::kSystemWebAppSessionHasBrokenIconsPrefName));
// Intentionally break icons by corrupting the on-disk icon file.
auto app_id = GetManager().GetAppIdForSystemApp(GetAppType()).value();
base::FilePath icon_path =
SystemWebAppManager::GetWebAppProvider(browser()->profile())
->icon_manager()
.GetIconFilePathForTesting(app_id, web_app::IconPurpose::ANY, 32);
{
base::ScopedAllowBlockingForTesting allow_blocking;
base::WriteFile(icon_path, "Not a PNG file");
}
// Restart to let SystemWebAppManager perform the check.
}
IN_PROC_BROWSER_TEST_P(SystemWebAppIconHealthMetricsTest,
PRE_ReinstallFixesBrokenIcon) {
WaitForInstallAndIconCheck();
tester_.ExpectBucketCount(kIconsAreHealthyHistogramName, false, 1);
// TODO(crbug.com/40162953): Change CHECK_EQ to EXPECT_TRUE when
// assertions report correctly as test failure in PRE_TESTs.
// Icon check should update pref to report broken icons.
CHECK_EQ(
true,
browser()->profile()->GetPrefs()->GetBoolean(
SystemWebAppManager::kSystemWebAppSessionHasBrokenIconsPrefName));
}
IN_PROC_BROWSER_TEST_P(SystemWebAppIconHealthMetricsTest,
ReinstallFixesBrokenIcon) {
WaitForInstallAndIconCheck();
tester_.ExpectBucketCount(kIconsAreHealthyHistogramName, true, 1);
tester_.ExpectBucketCount(
SystemWebAppManager::kIconsFixedOnReinstallHistogramName, true, 1);
}
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerBrowserTestBasicInstall);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerLaunchFilesBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerLaunchDirectoryBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerLaunchDirectoryFileSystemProviderBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerNotShownInLauncherTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerNotShownInSearchTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerHandlesFileOpenIntentsTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerAdditionalSearchTermsTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerChromeUntrustedTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerOriginTrialsBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerUninstallBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerInstallAllAppsBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerShortcutTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerBackgroundTaskTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerHasTabStripWithNewTabButtonTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerHasTabStripWithHiddenNewTabButtonTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerHasNoTabStripWithNewTabButtonTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerHasNoTabStripWithHiddenNewTabButtonTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerDefaultBoundsTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppSingleWindowTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppAccessibilityTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppAbortsLaunchTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppIconHealthMetricsTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerContextMenuBrowserTest);
INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
SystemWebAppManagerLaunchWithUrlBrowserTest);
} // namespace ash