blob: d4bf053c5eb131e5cde6c92757fa39d24dc4d60c [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_utils.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/web_applications/test/test_file_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/command_line.h"
#include "chromeos/constants/chromeos_switches.h"
#include "components/arc/arc_util.h"
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace web_app {
class ExternalWebAppUtilsTest : public testing::Test {
public:
ExternalWebAppUtilsTest() = default;
~ExternalWebAppUtilsTest() override = default;
// testing::Test:
void SetUp() override {
testing::Test::SetUp();
base::FilePath source_root_dir;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir));
file_utils_ = TestFileUtils::Create({
{base::FilePath(FILE_PATH_LITERAL("test_dir/icon.png")),
source_root_dir.AppendASCII("chrome/test/data/web_apps/blue-192.png")},
{base::FilePath(FILE_PATH_LITERAL("test_dir/basic.html")),
source_root_dir.AppendASCII("chrome/test/data/web_apps/basic.html")},
});
}
base::Optional<ExternalInstallOptions> ParseConfig(
const char* app_config_string) {
base::Optional<base::Value> app_config =
base::JSONReader::Read(app_config_string);
DCHECK(app_config);
FileUtilsWrapper file_utils;
OptionsOrError result =
::web_app::ParseConfig(file_utils, /*dir=*/base::FilePath(),
/*file=*/base::FilePath(), app_config.value());
if (ExternalInstallOptions* options =
absl::get_if<ExternalInstallOptions>(&result)) {
return std::move(*options);
}
return base::nullopt;
}
base::Optional<WebApplicationInfoFactory> ParseOfflineManifest(
const char* offline_manifest_string) {
base::Optional<base::Value> offline_manifest =
base::JSONReader::Read(offline_manifest_string);
DCHECK(offline_manifest);
WebApplicationInfoFactoryOrError result = ::web_app::ParseOfflineManifest(
*file_utils_, base::FilePath(FILE_PATH_LITERAL("test_dir")),
base::FilePath(FILE_PATH_LITERAL("test_dir/test.json")),
*offline_manifest);
if (WebApplicationInfoFactory* factory =
absl::get_if<WebApplicationInfoFactory>(&result)) {
return std::move(*factory);
}
return base::nullopt;
}
protected:
std::unique_ptr<TestFileUtils> file_utils_;
};
// ParseConfig() is also tested by ExternalWebAppManagerTest.
#if BUILDFLAG(IS_CHROMEOS_ASH)
namespace {
std::string BoolParamToString(
const ::testing::TestParamInfo<bool>& bool_param) {
return bool_param.param ? "true" : "false";
}
using IsTablet = bool;
using IsArcSupported = bool;
} // namespace
class ExternalWebAppUtilsTabletTest
: public ExternalWebAppUtilsTest,
public ::testing::WithParamInterface<IsTablet> {
public:
ExternalWebAppUtilsTabletTest() {
if (GetParam()) {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
chromeos::switches::kEnableTabletFormFactor);
}
}
~ExternalWebAppUtilsTabletTest() override = default;
bool is_tablet() const { return GetParam(); }
};
TEST_P(ExternalWebAppUtilsTabletTest, DisableIfTabletFormFactor) {
base::Optional<ExternalInstallOptions> disable_true_options = ParseConfig(R"(
{
"app_url": "https://test.org",
"launch_container": "window",
"disable_if_tablet_form_factor": true,
"user_type": ["test"]
}
)");
EXPECT_TRUE(disable_true_options->disable_if_tablet_form_factor);
base::Optional<ExternalInstallOptions> disable_false_options = ParseConfig(R"(
{
"app_url": "https://test.org",
"launch_container": "window",
"disable_if_tablet_form_factor": false,
"user_type": ["test"]
}
)");
EXPECT_FALSE(disable_false_options->disable_if_tablet_form_factor);
}
INSTANTIATE_TEST_SUITE_P(All,
ExternalWebAppUtilsTabletTest,
::testing::Values(true, false),
BoolParamToString);
class ExternalWebAppUtilsArcTest
: public ExternalWebAppUtilsTest,
public ::testing::WithParamInterface<IsArcSupported> {
public:
ExternalWebAppUtilsArcTest() {
if (GetParam()) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
chromeos::switches::kArcAvailability, "officially-supported");
}
}
~ExternalWebAppUtilsArcTest() override = default;
bool is_arc_supported() const { return GetParam(); }
};
TEST_P(ExternalWebAppUtilsArcTest, DisableIfArcSupported) {
base::Optional<ExternalInstallOptions> disable_true_options = ParseConfig(R"(
{
"app_url": "https://test.org",
"launch_container": "window",
"disable_if_arc_supported": true,
"user_type": ["test"]
}
)");
EXPECT_TRUE(disable_true_options->disable_if_arc_supported);
base::Optional<ExternalInstallOptions> disable_false_options = ParseConfig(R"(
{
"app_url": "https://test.org",
"launch_container": "window",
"disable_if_arc_supported": false,
"user_type": ["test"]
}
)");
EXPECT_FALSE(disable_false_options->disable_if_arc_supported);
}
INSTANTIATE_TEST_SUITE_P(All,
ExternalWebAppUtilsArcTest,
::testing::Values(true, false),
BoolParamToString);
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// TODO(crbug.com/1119710): Loading icon.png is flaky on Windows.
#if defined(OS_WIN)
#define MAYBE_OfflineManifestValid DISABLED_OfflineManifestValid
#else
#define MAYBE_OfflineManifestValid OfflineManifestValid
#endif
TEST_F(ExternalWebAppUtilsTest, MAYBE_OfflineManifestValid) {
std::unique_ptr<WebApplicationInfo> app_info = ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"],
"theme_color_argb_hex": "AABBCCDD"
}
)")
.value()
.Run();
EXPECT_TRUE(app_info);
EXPECT_EQ(app_info->title, base::UTF8ToUTF16("Test App"));
EXPECT_EQ(app_info->start_url, GURL("https://test.org/start.html"));
EXPECT_EQ(app_info->scope, GURL("https://test.org/"));
EXPECT_EQ(app_info->display_mode, DisplayMode::kStandalone);
EXPECT_EQ(app_info->icon_bitmaps_any.size(), 1u);
EXPECT_EQ(app_info->icon_bitmaps_any.at(192).getColor(0, 0), SK_ColorBLUE);
EXPECT_EQ(app_info->theme_color, SkColorSetARGB(0xFF, 0xBB, 0xCC, 0xDD));
}
TEST_F(ExternalWebAppUtilsTest, OfflineManifestName) {
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "name is required";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": 400,
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "name is string";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "name is non-empty";
}
TEST_F(ExternalWebAppUtilsTest, OfflineManifestStartUrl) {
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "start_url is required";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "not a url",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "start_url is valid";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/inner/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "start_url is within scope";
}
TEST_F(ExternalWebAppUtilsTest, OfflineManifestScope) {
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "scope is required";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "not a url",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "start_url is valid";
}
// TODO(crbug.com/1119710): Loading icon.png is flaky on Windows.
#if defined(OS_WIN)
#define MAYBE_OfflineManifestDisplay DISABLED_OfflineManifestDisplay
#else
#define MAYBE_OfflineManifestDisplay OfflineManifestDisplay
#endif
TEST_F(ExternalWebAppUtilsTest, MAYBE_OfflineManifestDisplay) {
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"icon_any_pngs": ["icon.png"]
}
)")) << "display is required";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "tab",
"icon_any_pngs": ["icon.png"]
}
)")) << "display is valid";
EXPECT_TRUE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"]
}
)")) << "display can be standalone";
EXPECT_TRUE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "browser",
"icon_any_pngs": ["icon.png"]
}
)")) << "display can be browser";
EXPECT_TRUE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "minimal-ui",
"icon_any_pngs": ["icon.png"]
}
)")) << "display can be minimal-ui";
EXPECT_TRUE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "fullscreen",
"icon_any_pngs": ["icon.png"]
}
)")) << "display can be fullscreen";
}
TEST_F(ExternalWebAppUtilsTest, OfflineManifestIconAnyPngs) {
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone"
}
)")) << "icon_any_pngs is required";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": "icon.png"
}
)")) << "icon_any_pngs is valid";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": [{
"src": "https://test.org/icon.png",
"sizes": "144x144",
"type": "image/png"
}]
}
)")) << "icon_any_pngs is valid";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["does-not-exist.png"]
}
)")) << "icon_any_pngs exists";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["basic.html"]
}
)")) << "icon_any_pngs is a PNG";
}
TEST_F(ExternalWebAppUtilsTest, OfflineManifestThemeColorArgbHex) {
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"],
"theme_color_argb_hex": 12345
}
)")) << "theme_color_argb_hex is valid";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"],
"theme_color_argb_hex": "blue"
}
)")) << "theme_color_argb_hex is valid";
EXPECT_FALSE(ParseOfflineManifest(R"(
{
"name": "Test App",
"start_url": "https://test.org/start.html",
"scope": "https://test.org/",
"display": "standalone",
"icon_any_pngs": ["icon.png"],
"theme_color_argb_hex": "#ff0000"
}
)")) << "theme_color_argb_hex is valid";
}
TEST_F(ExternalWebAppUtilsTest, ForceReinstallForMilestone) {
base::Optional<ExternalInstallOptions> non_number = ParseConfig(R"(
{
"app_url": "https://test.org",
"launch_container": "window",
"force_reinstall_for_milestone": "error",
"user_type": ["test"]
}
)");
EXPECT_FALSE(non_number.has_value());
base::Optional<ExternalInstallOptions> number = ParseConfig(R"(
{
"app_url": "https://test.org",
"launch_container": "window",
"force_reinstall_for_milestone": 89,
"user_type": ["test"]
}
)");
EXPECT_TRUE(number.has_value());
EXPECT_EQ(89, number->force_reinstall_for_milestone);
}
TEST_F(ExternalWebAppUtilsTest, IsReinstallPastMilestoneNeeded) {
// Arguments: last_preinstall_synchronize_milestone, current_milestone,
// force_reinstall_for_milestone.
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("87", "87", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("87", "88", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("88", "88", 89));
EXPECT_TRUE(IsReinstallPastMilestoneNeeded("88", "89", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("89", "89", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("89", "90", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("90", "90", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("90", "91", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("91", "91", 89));
// Long jumps:
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("80", "85", 89));
EXPECT_TRUE(IsReinstallPastMilestoneNeeded("80", "100", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("90", "95", 89));
// Wrong input:
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("error", "90", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("88", "error", 89));
EXPECT_FALSE(IsReinstallPastMilestoneNeeded("error", "error", 0));
}
} // namespace web_app