blob: 02c310496e2e53587619cb9de1fe7705e2598ff5 [file] [log] [blame]
// Copyright 2016 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/installable/installable_manager.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom.h"
using IconPurpose = blink::Manifest::ImageResource::Purpose;
class InstallableManagerUnitTest : public testing::Test {
public:
InstallableManagerUnitTest()
: manager_(std::make_unique<InstallableManager>(nullptr)) {}
protected:
static base::NullableString16 ToNullableUTF16(const std::string& str) {
return base::NullableString16(base::UTF8ToUTF16(str), false);
}
static blink::Manifest GetValidManifest() {
blink::Manifest manifest;
manifest.name = ToNullableUTF16("foo");
manifest.short_name = ToNullableUTF16("bar");
manifest.start_url = GURL("http://example.com");
manifest.display = blink::mojom::DisplayMode::kStandalone;
blink::Manifest::ImageResource primary_icon;
primary_icon.type = base::ASCIIToUTF16("image/png");
primary_icon.sizes.push_back(gfx::Size(144, 144));
primary_icon.purpose.push_back(IconPurpose::ANY);
manifest.icons.push_back(primary_icon);
// No need to include the optional badge icon as it does not affect the
// unit tests.
return manifest;
}
bool IsManifestValid(const blink::Manifest& manifest,
bool check_webapp_manifest_display = true,
bool prefer_maskable_icon = false) {
// Explicitly reset the error code before running the method.
manager_->set_valid_manifest_error(NO_ERROR_DETECTED);
return manager_->IsManifestValidForWebApp(
manifest, check_webapp_manifest_display, prefer_maskable_icon);
}
InstallableStatusCode GetErrorCode() {
return manager_->valid_manifest_error();
}
private:
std::unique_ptr<InstallableManager> manager_;
};
TEST_F(InstallableManagerUnitTest, EmptyManifestIsInvalid) {
blink::Manifest manifest;
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_EMPTY, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, CheckMinimalValidManifest) {
blink::Manifest manifest = GetValidManifest();
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestRequiresNameOrShortName) {
blink::Manifest manifest = GetValidManifest();
manifest.name = base::NullableString16();
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.name = ToNullableUTF16("foo");
manifest.short_name = base::NullableString16();
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.name = base::NullableString16();
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_NAME_OR_SHORT_NAME, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestRequiresNonEmptyNameORShortName) {
blink::Manifest manifest = GetValidManifest();
manifest.name = ToNullableUTF16("");
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.name = ToNullableUTF16("foo");
manifest.short_name = ToNullableUTF16("");
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.name = ToNullableUTF16("");
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_NAME_OR_SHORT_NAME, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestRequiresValidStartURL) {
blink::Manifest manifest = GetValidManifest();
manifest.start_url = GURL();
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(START_URL_NOT_VALID, GetErrorCode());
manifest.start_url = GURL("/");
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(START_URL_NOT_VALID, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestRequiresImagePNG) {
blink::Manifest manifest = GetValidManifest();
manifest.icons[0].type = base::ASCIIToUTF16("image/gif");
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
manifest.icons[0].type.clear();
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
// If the type is null, the icon src will be checked instead.
manifest.icons[0].src = GURL("http://example.com/icon.png");
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// Capital file extension is also permissible.
manifest.icons[0].src = GURL("http://example.com/icon.PNG");
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// Non-png extensions are rejected.
manifest.icons[0].src = GURL("http://example.com/icon.gif");
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestRequiresPurposeAny) {
blink::Manifest manifest = GetValidManifest();
// The icon MUST have IconPurpose::ANY at least.
manifest.icons[0].purpose[0] = IconPurpose::BADGE;
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
// If one of the icon purposes match the requirement, it should be accepted.
manifest.icons[0].purpose.push_back(IconPurpose::ANY);
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest,
ManifestRequiresPurposeAnyOrMaskableWhenPreferringMaskable) {
blink::Manifest manifest = GetValidManifest();
EXPECT_TRUE(IsManifestValid(manifest, true, true));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.icons[0].purpose[0] = IconPurpose::MASKABLE;
EXPECT_TRUE(IsManifestValid(manifest, true, true));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// The icon MUST have IconPurpose::ANY or IconPurpose::Maskable.
manifest.icons[0].purpose[0] = IconPurpose::BADGE;
EXPECT_FALSE(IsManifestValid(manifest, true, true));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
// If one of the icon purposes match the requirement, it should be accepted.
manifest.icons[0].purpose.push_back(IconPurpose::ANY);
EXPECT_TRUE(IsManifestValid(manifest, true, true));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.icons[0].purpose[1] = IconPurpose::MASKABLE;
EXPECT_TRUE(IsManifestValid(manifest, true, true));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestRequiresMinimalSize) {
blink::Manifest manifest = GetValidManifest();
// The icon MUST be 144x144 size at least.
manifest.icons[0].sizes[0] = gfx::Size(1, 1);
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
manifest.icons[0].sizes[0] = gfx::Size(143, 143);
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
// If one of the sizes match the requirement, it should be accepted.
manifest.icons[0].sizes.push_back(gfx::Size(144, 144));
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// Higher than the required size is okay.
manifest.icons[0].sizes[1] = gfx::Size(200, 200);
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// Non-square is okay.
manifest.icons[0].sizes[1] = gfx::Size(144, 200);
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// The representation of the keyword 'any' should be recognized.
manifest.icons[0].sizes[1] = gfx::Size(0, 0);
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// When preferring maskable icons, both non-maskable and maskable
// icons are smaller than required.
manifest.icons[0].sizes.clear();
manifest.icons[0].sizes.push_back(gfx::Size(1, 1));
blink::Manifest::ImageResource maskable_icon;
maskable_icon.type = base::ASCIIToUTF16("image/png");
maskable_icon.sizes.push_back(gfx::Size(82, 82));
maskable_icon.purpose.push_back(IconPurpose::MASKABLE);
manifest.icons.push_back(maskable_icon);
EXPECT_FALSE(IsManifestValid(manifest, true, true));
EXPECT_EQ(MANIFEST_MISSING_SUITABLE_ICON, GetErrorCode());
// When preferring maskable icons, the maskable icon is smaller than required.
manifest.icons[0].sizes[0] = gfx::Size(144, 144);
manifest.icons[1].sizes[0] = gfx::Size(82, 82);
EXPECT_TRUE(IsManifestValid(manifest, true, true));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
// When preferring maskable icons, the maskable icon satisfies the size
// requirement though the non maskable one doesn't
manifest.icons[0].sizes[0] = gfx::Size(1, 1);
manifest.icons[1].sizes[0] = gfx::Size(83, 83);
EXPECT_TRUE(IsManifestValid(manifest, true, true));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
}
TEST_F(InstallableManagerUnitTest, ManifestDisplayModes) {
blink::Manifest manifest = GetValidManifest();
manifest.display = blink::mojom::DisplayMode::kUndefined;
EXPECT_TRUE(
IsManifestValid(manifest, false /* check_webapp_manifest_display */));
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_DISPLAY_NOT_SUPPORTED, GetErrorCode());
manifest.display = blink::mojom::DisplayMode::kBrowser;
EXPECT_TRUE(
IsManifestValid(manifest, false /* check_webapp_manifest_display */));
EXPECT_FALSE(IsManifestValid(manifest));
EXPECT_EQ(MANIFEST_DISPLAY_NOT_SUPPORTED, GetErrorCode());
manifest.display = blink::mojom::DisplayMode::kMinimalUi;
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.display = blink::mojom::DisplayMode::kStandalone;
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
manifest.display = blink::mojom::DisplayMode::kFullscreen;
EXPECT_TRUE(IsManifestValid(manifest));
EXPECT_EQ(NO_ERROR_DETECTED, GetErrorCode());
}