blob: a776b0473142e028f621b2b028ef11cd84bc2b29 [file] [log] [blame]
// Copyright 2020 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/external_web_app_manager.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/test/bind_test_util.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/components/web_app_helpers.h"
#include "chrome/browser/web_applications/test/test_file_utils.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace web_app {
class ExternalWebAppManagerBrowserTest
: public extensions::ExtensionBrowserTest {
public:
ExternalWebAppManagerBrowserTest() {
ExternalWebAppManager::SkipStartupForTesting();
}
GURL GetAppUrl() const {
return embedded_test_server()->GetURL("/web_apps/basic.html");
}
const AppRegistrar& registrar() {
return WebAppProvider::Get(browser()->profile())->registrar();
}
// Mocks "icon.png" as available in the config's directory.
InstallResultCode SyncDefaultAppConfig(const GURL& install_url,
std::string app_config_string) {
base::FilePath test_config_dir(FILE_PATH_LITERAL("test_dir"));
ExternalWebAppManager::SetConfigDirForTesting(&test_config_dir);
base::FilePath source_root_dir;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir));
base::FilePath test_icon_path =
source_root_dir.Append(GetChromeTestDataDir())
.AppendASCII("web_apps/blue-192.png");
TestFileUtils file_utils(
{{base::FilePath(FILE_PATH_LITERAL("test_dir/icon.png")),
test_icon_path}});
ExternalWebAppManager::SetFileUtilsForTesting(&file_utils);
std::vector<base::Value> app_configs;
app_configs.push_back(*base::JSONReader::Read(app_config_string));
ExternalWebAppManager::SetConfigsForTesting(&app_configs);
base::Optional<InstallResultCode> code;
base::RunLoop sync_run_loop;
WebAppProvider::Get(browser()->profile())
->external_web_app_manager_for_testing()
.LoadAndSynchronizeForTesting(base::BindLambdaForTesting(
[&](std::map<GURL, InstallResultCode> install_results,
std::map<GURL, bool> uninstall_results) {
code = install_results.at(install_url);
sync_run_loop.Quit();
}));
sync_run_loop.Run();
ExternalWebAppManager::SetConfigDirForTesting(nullptr);
ExternalWebAppManager::SetFileUtilsForTesting(nullptr);
ExternalWebAppManager::SetConfigsForTesting(nullptr);
return *code;
}
~ExternalWebAppManagerBrowserTest() override = default;
};
// This JSON config functionality is only available on Chrome OS.
#if defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest, UninstallAndReplace) {
ASSERT_TRUE(embedded_test_server()->Start());
Profile* profile = browser()->profile();
// Install Chrome app to be replaced.
const char kChromeAppDirectory[] = "app";
const char kChromeAppName[] = "App Test";
const extensions::Extension* app = InstallExtensionWithSourceAndFlags(
test_data_dir_.AppendASCII(kChromeAppDirectory), 1,
extensions::Manifest::INTERNAL, extensions::Extension::NO_FLAGS);
EXPECT_EQ(app->name(), kChromeAppName);
// Start listening for Chrome app uninstall.
extensions::TestExtensionRegistryObserver uninstall_observer(
extensions::ExtensionRegistry::Get(profile));
InstallResultCode code = SyncDefaultAppConfig(
GetAppUrl(), base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"uninstall_and_replace": ["$2"]
})",
{GetAppUrl().spec(), app->id()}, nullptr));
EXPECT_EQ(code, InstallResultCode::kSuccessNewInstall);
// Chrome app should get uninstalled.
scoped_refptr<const extensions::Extension> uninstalled_app =
uninstall_observer.WaitForExtensionUninstalled();
EXPECT_EQ(app, uninstalled_app.get());
}
// Check that offline fallback installs work offline.
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest,
OfflineFallbackManifestSiteOffline) {
constexpr char kAppInstallUrl[] = "https://offline-site.com/install.html";
constexpr char kAppName[] = "Offline app name";
constexpr char kAppStartUrl[] = "https://offline-site.com/start.html";
constexpr char kAppScope[] = "https://offline-site.com/";
AppId app_id = GenerateAppIdFromURL(GURL(kAppStartUrl));
EXPECT_FALSE(registrar().IsInstalled(app_id));
InstallResultCode code = SyncDefaultAppConfig(
GURL(kAppInstallUrl),
base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})",
{kAppInstallUrl, kAppName, kAppStartUrl, kAppScope}, nullptr));
EXPECT_EQ(code, InstallResultCode::kSuccessOfflineFallbackInstall);
EXPECT_TRUE(registrar().IsInstalled(app_id));
EXPECT_EQ(registrar().GetAppShortName(app_id), kAppName);
EXPECT_EQ(registrar().GetAppLaunchURL(app_id).spec(), kAppStartUrl);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), kAppScope);
// theme_color must be installed opaque.
EXPECT_EQ(registrar().GetAppThemeColor(app_id),
SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
EXPECT_EQ(ReadAppIconPixel(browser()->profile(), app_id, /*size=*/192,
/*x=*/0, /*y=*/0),
SK_ColorBLUE);
}
// Check that offline fallback installs attempt fetching the install_url.
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest,
OfflineFallbackManifestSiteOnline) {
ASSERT_TRUE(embedded_test_server()->Start());
// This install_url serves a manifest with different values to what we specify
// in the offline_manifest. Check that it gets used instead of the
// offline_manifest.
GURL install_url = embedded_test_server()->GetURL("/web_apps/basic.html");
GURL offline_start_url = embedded_test_server()->GetURL(
"/web_apps/offline-only-start-url-that-does-not-exist.html");
GURL scope = embedded_test_server()->GetURL("/web_apps/");
AppId offline_app_id = GenerateAppIdFromURL(offline_start_url);
EXPECT_FALSE(registrar().IsInstalled(offline_app_id));
InstallResultCode code = SyncDefaultAppConfig(
install_url,
base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"offline_manifest": {
"name": "Offline only app name",
"start_url": "$2",
"scope": "$3",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})",
{install_url.spec(), offline_start_url.spec(), scope.spec()},
nullptr));
EXPECT_EQ(code, InstallResultCode::kSuccessNewInstall);
EXPECT_FALSE(registrar().IsInstalled(offline_app_id));
// basic.html's manifest start_url is basic.html.
AppId app_id = GenerateAppIdFromURL(install_url);
EXPECT_TRUE(registrar().IsInstalled(app_id));
EXPECT_EQ(registrar().GetAppShortName(app_id), "Basic web app");
EXPECT_EQ(registrar().GetAppLaunchURL(app_id).spec(), install_url);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), scope);
}
// Check that offline only installs work offline.
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest,
OfflineOnlyManifestSiteOffline) {
constexpr char kAppInstallUrl[] = "https://offline-site.com/install.html";
constexpr char kAppName[] = "Offline app name";
constexpr char kAppStartUrl[] = "https://offline-site.com/start.html";
constexpr char kAppScope[] = "https://offline-site.com/";
AppId app_id = GenerateAppIdFromURL(GURL(kAppStartUrl));
EXPECT_FALSE(registrar().IsInstalled(app_id));
InstallResultCode code = SyncDefaultAppConfig(
GURL(kAppInstallUrl),
base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})",
{kAppInstallUrl, kAppName, kAppStartUrl, kAppScope}, nullptr));
EXPECT_EQ(code, InstallResultCode::kSuccessOfflineOnlyInstall);
EXPECT_TRUE(registrar().IsInstalled(app_id));
EXPECT_EQ(registrar().GetAppShortName(app_id), kAppName);
EXPECT_EQ(registrar().GetAppLaunchURL(app_id).spec(), kAppStartUrl);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), kAppScope);
// theme_color must be installed opaque.
EXPECT_EQ(registrar().GetAppThemeColor(app_id),
SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
EXPECT_EQ(ReadAppIconPixel(browser()->profile(), app_id, /*size=*/192,
/*x=*/0, /*y=*/0),
SK_ColorBLUE);
}
// Check that offline only installs don't fetch from the install_url.
IN_PROC_BROWSER_TEST_F(ExternalWebAppManagerBrowserTest,
OfflineOnlyManifestSiteOnline) {
ASSERT_TRUE(embedded_test_server()->Start());
// This install_url serves a manifest with different values to what we specify
// in the offline_manifest. Check that it doesn't get used.
GURL install_url = GetAppUrl();
const char kAppName[] = "Offline only app name";
GURL start_url = embedded_test_server()->GetURL(
"/web_apps/offline-only-start-url-that-does-not-exist.html");
GURL scope = embedded_test_server()->GetURL("/web_apps/");
AppId app_id = GenerateAppIdFromURL(start_url);
EXPECT_FALSE(registrar().IsInstalled(app_id));
InstallResultCode code = SyncDefaultAppConfig(
install_url,
base::ReplaceStringPlaceholders(
R"({
"app_url": "$1",
"launch_container": "window",
"user_type": ["unmanaged"],
"only_use_offline_manifest": true,
"offline_manifest": {
"name": "$2",
"start_url": "$3",
"scope": "$4",
"display": "minimal-ui",
"theme_color_argb_hex": "AABBCCDD",
"icon_any_pngs": ["icon.png"]
}
})",
{install_url.spec(), kAppName, start_url.spec(), scope.spec()},
nullptr));
EXPECT_EQ(code, InstallResultCode::kSuccessOfflineOnlyInstall);
EXPECT_TRUE(registrar().IsInstalled(app_id));
EXPECT_EQ(registrar().GetAppShortName(app_id), kAppName);
EXPECT_EQ(registrar().GetAppLaunchURL(app_id).spec(), start_url);
EXPECT_EQ(registrar().GetAppScope(app_id).spec(), scope);
// theme_color must be installed opaque.
EXPECT_EQ(registrar().GetAppThemeColor(app_id),
SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
EXPECT_EQ(ReadAppIconPixel(browser()->profile(), app_id, /*size=*/192,
/*x=*/0, /*y=*/0),
SK_ColorBLUE);
}
#endif // defined(OS_CHROMEOS)
} // namespace web_app