blob: 33e3a9f9dd084d8622022930a10dc590480aaa0c [file] [log] [blame]
// 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 "chrome/browser/web_applications/web_app_install_utils.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/feature_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/os_integration/web_app_file_handler_manager.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/web_app.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_generator.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "components/services/app_service/public/cpp/icon_info.h"
#include "components/services/app_service/public/cpp/protocol_handler_info.h"
#include "components/services/app_service/public/cpp/share_target.h"
#include "mojo/public/cpp/bindings/struct_ptr.h"
#include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom-shared.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/manifest/manifest.h"
#include "third_party/blink/public/mojom/manifest/display_mode.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom-shared.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/skia_util.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace web_app {
using Purpose = blink::mojom::ManifestImageResource_Purpose;
namespace {
const char16_t kAppTestShortName[] = u"Test short name";
const char16_t kAppTestTitle[] = u"Test title";
const char16_t kAlternativeAppTestTitle[] = u"Different test title";
const char16_t kShortcutItemTestName[] = u"shortcut item ";
constexpr SquareSizePx kIconSize = 64;
// This value is greater than kMaxIcons in web_app_install_utils.cc.
constexpr unsigned int kNumTestIcons = 30;
IconPurpose IconInfoPurposeToManifestPurpose(
apps::IconInfo::Purpose icon_info_purpose) {
switch (icon_info_purpose) {
case apps::IconInfo::Purpose::kAny:
return IconPurpose::ANY;
case apps::IconInfo::Purpose::kMonochrome:
return IconPurpose::MONOCHROME;
case apps::IconInfo::Purpose::kMaskable:
return IconPurpose::MASKABLE;
}
}
GURL StartUrl() {
return GURL("https://www.example.com/index.html");
}
// Returns a stack-allocated WebAppInstallInfo with `StartUrl()` as the
// start_url and manifest_id. Needed to migrate existing tests from the default
// constructor. Prefer to instead use
// WebAppInstallInfo::CreateWithStartUrlForTesting when adding new tests.
WebAppInstallInfo CreateWebAppInstallInfo() {
return WebAppInstallInfo(GenerateManifestIdFromStartUrlOnly(StartUrl()),
StartUrl());
}
// Returns a stack-allocated WebAppInstallInfo. Needed to migrate existing tests
// from the default constructor. Prefer to instead use
// WebAppInstallInfo::CreateWithStartUrlForTesting when adding new tests.
WebAppInstallInfo CreateWebAppInstallInfoFromStartUrl(const GURL& start_url) {
return WebAppInstallInfo(GenerateManifestIdFromStartUrlOnly(start_url),
start_url);
}
} // namespace
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({blink::features::kFileHandlingIcons,
blink::features::kWebAppManifestLockScreen},
/*disabled_features=*/{});
auto web_app_info =
CreateWebAppInstallInfoFromStartUrl(GURL("http://www.notchromium.org"));
web_app_info.title = kAlternativeAppTestTitle;
apps::IconInfo info;
const GURL kAppIcon1("fav1.png");
info.url = kAppIcon1;
web_app_info.manifest_icons.push_back(info);
blink::mojom::Manifest manifest;
const GURL kAppManifestUrl("http://www.chromium.org/manifest.json");
manifest.manifest_url = kAppManifestUrl;
const GURL kAppUrl("http://www.chromium.org/index.html");
manifest.start_url = kAppUrl;
manifest.id = kAppUrl;
manifest.scope = kAppUrl.GetWithoutFilename();
manifest.short_name = kAppTestShortName;
{
auto handler = blink::mojom::ManifestFileHandler::New();
handler->action = GURL("http://example.com/open-files");
handler->accept[u"image/png"].push_back(u".png");
handler->name = u"Images";
{
blink::Manifest::ImageResource icon;
icon.src = GURL("fav1.png");
icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
handler->icons.push_back(icon);
}
manifest.file_handlers.push_back(std::move(handler));
}
{
auto protocol_handler = blink::mojom::ManifestProtocolHandler::New();
protocol_handler->protocol = u"mailto";
protocol_handler->url = GURL("http://example.com/handle=%s");
manifest.protocol_handlers.push_back(std::move(protocol_handler));
}
{
auto scope_extension = blink::mojom::ManifestScopeExtension::New();
scope_extension->origin =
url::Origin::Create(GURL("https://scope_extensions_origin.com/"));
scope_extension->has_origin_wildcard = false;
manifest.scope_extensions.push_back(std::move(scope_extension));
}
{
network::ParsedPermissionsPolicyDeclaration declaration;
declaration.feature = network::mojom::PermissionsPolicyFeature::kFullscreen;
declaration.allowed_origins = {
*network::OriginWithPossibleWildcards::FromOrigin(
url::Origin::Create(GURL("https://www.example.com")))};
declaration.matches_all_origins = false;
declaration.matches_opaque_src = false;
manifest.permissions_policy.push_back(std::move(declaration));
}
{
blink::Manifest::RelatedApplication related_app;
related_app.platform = u"platform";
related_app.url = GURL("http://www.example.com");
related_app.id = u"id";
manifest.related_applications.push_back(std::move(related_app));
}
{
// Ensure empty structs are ignored.
manifest.lock_screen = blink::mojom::ManifestLockScreen::New();
manifest.note_taking = blink::mojom::ManifestNoteTaking::New();
}
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(kAppTestShortName, web_app_info.title);
EXPECT_EQ(kAppUrl, web_app_info.start_url());
EXPECT_EQ(kAppUrl.GetWithoutFilename(), web_app_info.scope);
EXPECT_EQ(DisplayMode::kBrowser, web_app_info.display_mode);
EXPECT_TRUE(web_app_info.display_override.empty());
EXPECT_EQ(kAppManifestUrl, web_app_info.manifest_url);
EXPECT_TRUE(web_app_info.lock_screen_start_url.is_empty());
EXPECT_TRUE(web_app_info.note_taking_new_note_url.is_empty());
// The icon info from |web_app_info| should be left as is, since the manifest
// doesn't have any icon information.
EXPECT_EQ(1u, web_app_info.manifest_icons.size());
EXPECT_EQ(kAppIcon1, web_app_info.manifest_icons[0].url);
// Test that |manifest.name| takes priority over |manifest.short_name|, and
// that icons provided by the manifest replace icons in |web_app_info|.
manifest.name = kAppTestTitle;
manifest.display = DisplayMode::kMinimalUi;
blink::Manifest::ImageResource icon;
const GURL kAppIcon2("fav2.png");
icon.src = kAppIcon2;
icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
manifest.icons.push_back(icon);
const GURL kAppIcon3("fav3.png");
icon.src = kAppIcon3;
icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
manifest.icons.push_back(icon);
// Add an icon without purpose ANY (expect to be ignored).
icon.purpose = {Purpose::MONOCHROME};
manifest.icons.push_back(icon);
manifest.display_override.push_back(DisplayMode::kMinimalUi);
manifest.display_override.push_back(DisplayMode::kStandalone);
{
auto lock_screen = blink::mojom::ManifestLockScreen::New();
lock_screen->start_url =
GURL("http://www.chromium.org/lock-screen-start-url");
manifest.lock_screen = std::move(lock_screen);
}
{
// Update with a valid new_note_url.
auto note_taking = blink::mojom::ManifestNoteTaking::New();
note_taking->new_note_url = GURL("http://www.chromium.org/new-note-url");
manifest.note_taking = std::move(note_taking);
}
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(kAppTestTitle, web_app_info.title);
EXPECT_EQ(DisplayMode::kMinimalUi, web_app_info.display_mode);
ASSERT_EQ(2u, web_app_info.display_override.size());
EXPECT_EQ(DisplayMode::kMinimalUi, web_app_info.display_override[0]);
EXPECT_EQ(DisplayMode::kStandalone, web_app_info.display_override[1]);
// We currently duplicate the app icons with multiple Purposes.
EXPECT_EQ(5u, web_app_info.manifest_icons.size());
EXPECT_EQ(kAppIcon2, web_app_info.manifest_icons[0].url);
EXPECT_EQ(kAppIcon3, web_app_info.manifest_icons[1].url);
EXPECT_EQ(kAppIcon2, web_app_info.manifest_icons[2].url);
EXPECT_EQ(kAppIcon3, web_app_info.manifest_icons[3].url);
EXPECT_EQ(kAppIcon3, web_app_info.manifest_icons[4].url);
// Check file handlers were updated.
ASSERT_EQ(1u, web_app_info.file_handlers.size());
auto file_handler = web_app_info.file_handlers[0];
ASSERT_EQ(1u, file_handler.accept.size());
EXPECT_EQ(file_handler.accept[0].mime_type, "image/png");
EXPECT_EQ(manifest.file_handlers[0]->action, file_handler.action);
EXPECT_TRUE(file_handler.accept[0].file_extensions.contains(".png"));
// Check protocol handlers were updated.
EXPECT_EQ(1u, web_app_info.protocol_handlers.size());
auto protocol_handler = web_app_info.protocol_handlers[0];
EXPECT_EQ(protocol_handler.protocol, "mailto");
EXPECT_EQ(protocol_handler.url, GURL("http://example.com/handle=%s"));
// Check scope extensions were updated.
EXPECT_EQ(1u, web_app_info.scope_extensions.size());
auto scope_extension = *web_app_info.scope_extensions.begin();
EXPECT_EQ(scope_extension.origin,
url::Origin::Create(GURL("https://scope_extensions_origin.com/")));
EXPECT_FALSE(scope_extension.has_origin_wildcard);
EXPECT_EQ(GURL("http://www.chromium.org/lock-screen-start-url"),
web_app_info.lock_screen_start_url);
EXPECT_EQ(GURL("http://www.chromium.org/new-note-url"),
web_app_info.note_taking_new_note_url);
// Check permissions policy was updated.
EXPECT_EQ(1u, web_app_info.permissions_policy.size());
auto declaration = web_app_info.permissions_policy[0];
EXPECT_EQ(declaration.feature,
network::mojom::PermissionsPolicyFeature::kFullscreen);
EXPECT_EQ(1u, declaration.allowed_origins.size());
EXPECT_EQ("https://www.example.com",
declaration.allowed_origins[0].Serialize());
EXPECT_FALSE(declaration.matches_all_origins);
EXPECT_FALSE(declaration.matches_opaque_src);
// Check related applications were updated.
ASSERT_EQ(1u, web_app_info.related_applications.size());
auto related_app = web_app_info.related_applications[0];
EXPECT_EQ(u"platform", related_app.platform);
EXPECT_EQ(GURL("http://www.example.com"), related_app.url);
EXPECT_EQ(u"id", related_app.id);
}
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_EmptyName) {
auto web_app_info =
CreateWebAppInstallInfoFromStartUrl(GURL("https://url.com"));
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
manifest.name = std::nullopt;
manifest.short_name = kAppTestShortName;
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(kAppTestShortName, web_app_info.title);
}
// Test that maskable icons are parsed as separate manifest_icons from the
// manifest.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_MaskableIcon) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
blink::Manifest::ImageResource icon;
icon.src = GURL("fav1.png");
// Produces 2 separate manifest_icons.
icon.purpose = {Purpose::ANY, Purpose::MASKABLE};
manifest.icons.push_back(icon);
// Produces 1 icon_info.
icon.purpose = {Purpose::MASKABLE};
manifest.icons.push_back(icon);
// Produces 1 icon_info.
icon.purpose = {Purpose::MONOCHROME};
manifest.icons.push_back(icon);
auto web_app_info = CreateWebAppInstallInfo();
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(4u, web_app_info.manifest_icons.size());
std::map<IconPurpose, int> purpose_to_count;
for (const auto& icon_info : web_app_info.manifest_icons) {
purpose_to_count[IconInfoPurposeToManifestPurpose(icon_info.purpose)]++;
}
EXPECT_EQ(1, purpose_to_count[IconPurpose::ANY]);
EXPECT_EQ(1, purpose_to_count[IconPurpose::MONOCHROME]);
EXPECT_EQ(2, purpose_to_count[IconPurpose::MASKABLE]);
}
TEST(WebAppInstallUtils,
UpdateWebAppInfoFromManifest_MaskableIconOnly_UsesManifestIcons) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
blink::Manifest::ImageResource icon;
icon.src = GURL("fav1.png");
icon.purpose = {Purpose::MASKABLE};
manifest.icons.push_back(icon);
// WebAppInstallInfo has existing icons (simulating found in page metadata).
auto web_app_info = CreateWebAppInstallInfo();
apps::IconInfo icon_info;
web_app_info.manifest_icons.push_back(icon_info);
web_app_info.manifest_icons.push_back(icon_info);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
// Metadata icons are replaced by manifest icon.
EXPECT_EQ(1U, web_app_info.manifest_icons.size());
}
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_ShareTarget) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
auto web_app_info = CreateWebAppInstallInfo();
{
blink::Manifest::ShareTarget share_target;
share_target.action = GURL("http://example.com/share1");
share_target.method = blink::mojom::ManifestShareTarget_Method::kPost;
share_target.enctype =
blink::mojom::ManifestShareTarget_Enctype::kMultipartFormData;
share_target.params.title = u"kTitle";
share_target.params.text = u"kText";
blink::Manifest::FileFilter file_filter;
file_filter.name = u"kImages";
file_filter.accept.push_back(u".png");
file_filter.accept.push_back(u"image/png");
share_target.params.files.push_back(std::move(file_filter));
manifest.share_target = std::move(share_target);
}
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
{
EXPECT_TRUE(web_app_info.share_target.has_value());
const auto& share_target = *web_app_info.share_target;
EXPECT_EQ(share_target.action, GURL("http://example.com/share1"));
EXPECT_EQ(share_target.method, apps::ShareTarget::Method::kPost);
EXPECT_EQ(share_target.enctype,
apps::ShareTarget::Enctype::kMultipartFormData);
EXPECT_EQ(share_target.params.title, "kTitle");
EXPECT_EQ(share_target.params.text, "kText");
EXPECT_TRUE(share_target.params.url.empty());
EXPECT_EQ(share_target.params.files.size(), 1U);
EXPECT_EQ(share_target.params.files[0].name, "kImages");
EXPECT_EQ(share_target.params.files[0].accept.size(), 2U);
EXPECT_EQ(share_target.params.files[0].accept[0], ".png");
EXPECT_EQ(share_target.params.files[0].accept[1], "image/png");
}
{
blink::Manifest::ShareTarget share_target;
share_target.action = GURL("http://example.com/share2");
share_target.method = blink::mojom::ManifestShareTarget_Method::kGet;
share_target.enctype =
blink::mojom::ManifestShareTarget_Enctype::kFormUrlEncoded;
share_target.params.text = u"kText";
share_target.params.url = u"kUrl";
manifest.share_target = std::move(share_target);
}
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
{
EXPECT_TRUE(web_app_info.share_target.has_value());
const auto& share_target = *web_app_info.share_target;
EXPECT_EQ(share_target.action, GURL("http://example.com/share2"));
EXPECT_EQ(share_target.method, apps::ShareTarget::Method::kGet);
EXPECT_EQ(share_target.enctype,
apps::ShareTarget::Enctype::kFormUrlEncoded);
EXPECT_TRUE(share_target.params.title.empty());
EXPECT_EQ(share_target.params.text, "kText");
EXPECT_EQ(share_target.params.url, "kUrl");
EXPECT_TRUE(share_target.params.files.empty());
}
manifest.share_target = std::nullopt;
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_FALSE(web_app_info.share_target.has_value());
}
// Tests that WebAppInfo is correctly updated when Manifest contains Shortcuts.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestWithShortcuts) {
base::test::ScopedFeatureList feature_list(
blink::features::kFileHandlingIcons);
auto web_app_info =
CreateWebAppInstallInfoFromStartUrl(GURL("http://www.notchromium.org"));
web_app_info.title = kAlternativeAppTestTitle;
apps::IconInfo info;
const GURL kAppIcon1("fav1.png");
info.url = kAppIcon1;
web_app_info.manifest_icons.push_back(info);
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
const GURL kAppUrl("http://www.chromium.org/index.html");
manifest.start_url = kAppUrl;
manifest.id = kAppUrl;
manifest.scope = kAppUrl.GetWithoutFilename();
manifest.short_name = kAppTestShortName;
{
auto handler = blink::mojom::ManifestFileHandler::New();
handler->action = GURL("http://example.com/open-files");
handler->accept[u"image/png"].push_back(u".png");
handler->name = u"Images";
{
blink::Manifest::ImageResource icon;
icon.src = GURL("fav1.png");
icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
handler->icons.push_back(icon);
}
manifest.file_handlers.push_back(std::move(handler));
}
{
auto protocol_handler = blink::mojom::ManifestProtocolHandler::New();
protocol_handler->protocol = u"mailto";
protocol_handler->url = GURL("http://example.com/handle=%s");
manifest.protocol_handlers.push_back(std::move(protocol_handler));
}
{
auto scope_extension = blink::mojom::ManifestScopeExtension::New();
scope_extension->origin =
url::Origin::Create(GURL("https://scope_extensions_origin.com/"));
scope_extension->has_origin_wildcard = true;
manifest.scope_extensions.push_back(std::move(scope_extension));
}
WebAppInstallInfo web_app_info_original{web_app_info.Clone()};
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(kAppTestShortName, web_app_info.title);
EXPECT_EQ(kAppUrl, web_app_info.start_url());
EXPECT_EQ(kAppUrl.GetWithoutFilename(), web_app_info.scope);
EXPECT_EQ(DisplayMode::kBrowser, web_app_info.display_mode);
// The icon info from |web_app_info| should be left as is, since the manifest
// doesn't have any icon information.
EXPECT_EQ(1u, web_app_info.manifest_icons.size());
EXPECT_EQ(kAppIcon1, web_app_info.manifest_icons[0].url);
EXPECT_EQ(0u, web_app_info.shortcuts_menu_item_infos.size());
EXPECT_EQ(web_app_info_original.shortcuts_menu_item_infos,
web_app_info.shortcuts_menu_item_infos);
// Test that |manifest.name| takes priority over |manifest.short_name|, and
// that icons provided by the manifest replace icons in |web_app_info|.
manifest.name = kAppTestTitle;
manifest.display = DisplayMode::kMinimalUi;
blink::Manifest::ImageResource icon;
const GURL kAppIcon2("fav2.png");
icon.src = kAppIcon2;
icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
manifest.icons.push_back(icon);
const GURL kAppIcon3("fav3.png");
icon.src = kAppIcon3;
icon.purpose = {Purpose::ANY, Purpose::MONOCHROME};
manifest.icons.push_back(icon);
// Add an icon without purpose ANY (expect to be ignored).
icon.purpose = {Purpose::MONOCHROME};
manifest.icons.push_back(icon);
// Test that shortcuts in the manifest replace those in |web_app_info|.
const GURL kShortcutItemUrl("http://www.chromium.org/shortcuts/action");
blink::Manifest::ShortcutItem shortcut_item;
shortcut_item.name = std::u16string(kShortcutItemTestName) + u"4";
shortcut_item.url = kShortcutItemUrl;
const GURL kIconUrl2("http://www.chromium.org/shortcuts/icon2.png");
icon.src = kIconUrl2;
icon.sizes.emplace_back(10, 10);
icon.purpose = {Purpose::ANY};
shortcut_item.icons.push_back(icon);
manifest.shortcuts.push_back(shortcut_item);
shortcut_item.name = std::u16string(kShortcutItemTestName) + u"5";
const GURL kIconUrl3("http://www.chromium.org/shortcuts/icon3.png");
icon.src = kIconUrl3;
icon.purpose = {Purpose::MASKABLE, Purpose::MONOCHROME};
shortcut_item.icons.clear();
shortcut_item.icons.push_back(icon);
manifest.shortcuts.push_back(shortcut_item);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(kAppTestTitle, web_app_info.title);
EXPECT_EQ(DisplayMode::kMinimalUi, web_app_info.display_mode);
// Sanity check that original copy was not changed.
EXPECT_EQ(0u, web_app_info_original.shortcuts_menu_item_infos.size());
// We currently duplicate the app icons with multiple Purposes.
EXPECT_EQ(5u, web_app_info.manifest_icons.size());
EXPECT_EQ(kAppIcon2, web_app_info.manifest_icons[0].url);
EXPECT_EQ(kAppIcon3, web_app_info.manifest_icons[1].url);
EXPECT_EQ(kAppIcon2, web_app_info.manifest_icons[2].url);
EXPECT_EQ(kAppIcon3, web_app_info.manifest_icons[3].url);
EXPECT_EQ(kAppIcon3, web_app_info.manifest_icons[4].url);
EXPECT_EQ(2u, web_app_info.shortcuts_menu_item_infos.size());
EXPECT_EQ(1u, web_app_info.shortcuts_menu_item_infos[0]
.GetShortcutIconInfosForPurpose(IconPurpose::ANY)
.size());
WebAppShortcutsMenuItemInfo::Icon web_app_shortcut_icon =
web_app_info.shortcuts_menu_item_infos[0].GetShortcutIconInfosForPurpose(
IconPurpose::ANY)[0];
EXPECT_EQ(kIconUrl2, web_app_shortcut_icon.url);
EXPECT_EQ(0u, web_app_info.shortcuts_menu_item_infos[1]
.GetShortcutIconInfosForPurpose(IconPurpose::ANY)
.size());
EXPECT_EQ(1u, web_app_info.shortcuts_menu_item_infos[1]
.GetShortcutIconInfosForPurpose(IconPurpose::MONOCHROME)
.size());
EXPECT_EQ(1u, web_app_info.shortcuts_menu_item_infos[1]
.GetShortcutIconInfosForPurpose(IconPurpose::MASKABLE)
.size());
web_app_shortcut_icon =
web_app_info.shortcuts_menu_item_infos[1].GetShortcutIconInfosForPurpose(
IconPurpose::MONOCHROME)[0];
EXPECT_EQ(kIconUrl3, web_app_shortcut_icon.url);
// Check file handlers were updated.
ASSERT_EQ(1u, web_app_info.file_handlers.size());
auto file_handler = web_app_info.file_handlers[0];
ASSERT_EQ(1u, file_handler.accept.size());
EXPECT_EQ(file_handler.accept[0].mime_type, "image/png");
EXPECT_EQ(manifest.file_handlers[0]->action, file_handler.action);
EXPECT_TRUE(file_handler.accept[0].file_extensions.contains(".png"));
// Check protocol handlers were updated.
EXPECT_EQ(1u, web_app_info.protocol_handlers.size());
auto protocol_handler = web_app_info.protocol_handlers[0];
EXPECT_EQ(protocol_handler.protocol, "mailto");
EXPECT_EQ(protocol_handler.url, GURL("http://example.com/handle=%s"));
// Check scope extensions were updated.
EXPECT_EQ(1u, web_app_info.scope_extensions.size());
auto scope_extension = *web_app_info.scope_extensions.begin();
EXPECT_EQ(scope_extension.origin,
url::Origin::Create(GURL("https://scope_extensions_origin.com/")));
EXPECT_TRUE(scope_extension.has_origin_wildcard);
}
// Tests that we limit the number of shortcut menu items.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestTooManyShortcuts) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
const unsigned kMaxShortcuts = 10U;
for (unsigned int i = 1; i <= kMaxShortcuts + 1; ++i) {
blink::Manifest::ShortcutItem shortcut_item;
shortcut_item.name = kShortcutItemTestName + base::NumberToString16(i);
shortcut_item.url = GURL("http://www.chromium.org/shortcuts/action");
manifest.shortcuts.push_back(shortcut_item);
}
EXPECT_LT(kMaxShortcuts, manifest.shortcuts.size());
auto web_app_info = CreateWebAppInstallInfo();
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(kMaxShortcuts, web_app_info.shortcuts_menu_item_infos.size());
}
// Tests that we limit the number of icons declared by a site.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestTooManyIcons) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
for (unsigned int i = 0; i < kNumTestIcons; ++i) {
blink::Manifest::ImageResource icon;
icon.src = GURL("fav1.png");
icon.purpose.push_back(Purpose::ANY);
icon.sizes.emplace_back(i, i);
manifest.icons.push_back(std::move(icon));
}
auto web_app_info = CreateWebAppInstallInfo();
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
ASSERT_GT(kNumTestIcons, web_app_info.manifest_icons.size());
EXPECT_EQ(20U, web_app_info.manifest_icons.size());
}
// Tests that we limit the number of shortcut icons, verifying that at most 20
// shortcut icons are stored per web app.
//
// The test previously created 30 shortcuts, each with 1 icon. Due to the new
// limit of 10 shortcuts per web app, we now create 5 shortcuts, with 6 icons
// each.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestTooManyShortcutIcons) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
manifest.start_url = GURL("http://www.chromium.org/");
manifest.id = GURL("http://www.chromium.org/");
const unsigned kNumShortcuts = 5;
for (unsigned int i = 0; i < kNumShortcuts; ++i) {
blink::Manifest::ShortcutItem shortcut_item;
shortcut_item.name = kShortcutItemTestName + base::NumberToString16(i);
shortcut_item.url = GURL("http://www.chromium.org/shortcuts/action");
for (unsigned int j = 1; j <= kNumTestIcons / kNumShortcuts; ++j) {
blink::Manifest::ImageResource icon;
icon.src = GURL("http://www.chromium.org/shortcuts/icon1.png");
icon.sizes.emplace_back(j, j);
icon.purpose.emplace_back(IconPurpose::ANY);
shortcut_item.icons.push_back(std::move(icon));
}
manifest.shortcuts.push_back(std::move(shortcut_item));
}
WebAppInstallInfo web_app_info = CreateWebAppInfoFromManifest(manifest);
std::vector<WebAppShortcutsMenuItemInfo::Icon> all_icons;
for (const auto& shortcut : web_app_info.shortcuts_menu_item_infos) {
for (const auto& icon_info :
shortcut.GetShortcutIconInfosForPurpose(IconPurpose::ANY)) {
all_icons.push_back(icon_info);
}
}
ASSERT_GT(kNumTestIcons, all_icons.size());
EXPECT_EQ(20U, all_icons.size());
}
// Tests that we limit the size of icons declared by a site.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestIconsTooLarge) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
for (int size = 1023; size <= 1026; ++size) {
blink::Manifest::ImageResource icon;
icon.src = GURL("fav1.png");
icon.purpose.push_back(Purpose::ANY);
icon.sizes.emplace_back(size, size);
manifest.icons.push_back(std::move(icon));
}
auto web_app_info = CreateWebAppInstallInfo();
// Icons exceeding size 1024 are discarded.
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
// Only the early icons are within the size limit.
EXPECT_EQ(2U, web_app_info.manifest_icons.size());
for (const apps::IconInfo& icon : web_app_info.manifest_icons) {
EXPECT_LE(icon.square_size_px, 1024);
}
}
// Tests that we limit the size of shortcut icons declared by a site.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestShortcutIconsTooLarge) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
for (int size = 1023; size <= 1026; ++size) {
blink::Manifest::ShortcutItem shortcut_item;
shortcut_item.name = kShortcutItemTestName + base::NumberToString16(size);
shortcut_item.url = GURL("http://www.chromium.org/shortcuts/action");
blink::Manifest::ImageResource icon;
icon.src = GURL("http://www.chromium.org/shortcuts/icon1.png");
icon.purpose.push_back(Purpose::ANY);
icon.sizes.emplace_back(size, size);
shortcut_item.icons.push_back(std::move(icon));
manifest.shortcuts.push_back(shortcut_item);
}
auto web_app_info = CreateWebAppInstallInfo();
// Icons exceeding size 1024 are discarded.
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
std::vector<WebAppShortcutsMenuItemInfo::Icon> all_icons;
for (const auto& shortcut : web_app_info.shortcuts_menu_item_infos) {
for (const auto& icon_info :
shortcut.GetShortcutIconInfosForPurpose(IconPurpose::ANY)) {
all_icons.push_back(icon_info);
}
}
// Only the early icons are within the size limit.
EXPECT_EQ(2U, all_icons.size());
}
TEST(WebAppInstallUtils,
UpdateWebAppInfoFromManifest_CrossOriginUrls_DropsFields) {
base::test::ScopedFeatureList feature_list(
blink::features::kWebAppManifestLockScreen);
auto install_info = CreateWebAppInstallInfo();
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
const GURL kAppUrl("http://www.chromium.org/index.html");
manifest.start_url = kAppUrl;
manifest.id = kAppUrl;
manifest.scope = kAppUrl.GetWithoutFilename();
{
auto lock_screen = blink::mojom::ManifestLockScreen::New();
lock_screen->start_url =
GURL("http://www.some-other-origin.com/lock-screen-start-url");
manifest.lock_screen = std::move(lock_screen);
}
{
auto note_taking = blink::mojom::ManifestNoteTaking::New();
note_taking->new_note_url =
GURL("http://www.some-other-origin.com/new-note-url");
manifest.note_taking = std::move(note_taking);
}
UpdateWebAppInfoFromManifest(manifest, &install_info);
EXPECT_EQ(kAppUrl, install_info.start_url());
EXPECT_TRUE(install_info.lock_screen_start_url.is_empty());
EXPECT_TRUE(install_info.note_taking_new_note_url.is_empty());
}
TEST(WebAppInstallUtils,
UpdateWebAppInfoFromManifest_WithoutLockscreenFlag_DropsField) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(
blink::features::kWebAppManifestLockScreen);
auto install_info = CreateWebAppInstallInfo();
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
const GURL kAppUrl("http://www.chromium.org/index.html");
manifest.start_url = kAppUrl;
manifest.id = kAppUrl;
manifest.scope = kAppUrl.GetWithoutFilename();
{
auto lock_screen = blink::mojom::ManifestLockScreen::New();
lock_screen->start_url =
GURL("http://www.chromium.org/lock-screen-start-url");
manifest.lock_screen = std::move(lock_screen);
}
UpdateWebAppInfoFromManifest(manifest, &install_info);
EXPECT_EQ(kAppUrl, install_info.start_url());
EXPECT_TRUE(install_info.lock_screen_start_url.is_empty());
}
// Tests that SkBitmaps associated with shortcut item icons are populated in
// their own map in web_app_info.
TEST(WebAppInstallUtils, PopulateShortcutItemIcons) {
auto web_app_info = CreateWebAppInstallInfo();
WebAppShortcutsMenuItemInfo::Icon icon;
const GURL kIconUrl1("http://www.chromium.org/shortcuts/icon1.png");
{
WebAppShortcutsMenuItemInfo shortcut_item;
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_manifest_icons;
shortcut_item.name = std::u16string(kShortcutItemTestName) + u"1";
shortcut_item.url = GURL("http://www.chromium.org/shortcuts/action");
icon.url = kIconUrl1;
icon.square_size_px = kIconSize;
shortcut_manifest_icons.push_back(icon);
shortcut_item.SetShortcutIconInfosForPurpose(
IconPurpose::ANY, std::move(shortcut_manifest_icons));
web_app_info.shortcuts_menu_item_infos.push_back(std::move(shortcut_item));
}
const GURL kIconUrl2("http://www.chromium.org/shortcuts/icon2.png");
{
WebAppShortcutsMenuItemInfo shortcut_item;
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_manifest_icons;
shortcut_item.name = std::u16string(kShortcutItemTestName) + u"2";
icon.url = kIconUrl1;
icon.square_size_px = kIconSize;
shortcut_manifest_icons.push_back(icon);
icon.url = kIconUrl2;
icon.square_size_px = 2 * kIconSize;
shortcut_manifest_icons.push_back(icon);
shortcut_item.SetShortcutIconInfosForPurpose(
IconPurpose::ANY, std::move(shortcut_manifest_icons));
web_app_info.shortcuts_menu_item_infos.push_back(std::move(shortcut_item));
}
{
IconsMap icons_map;
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
std::vector<SkBitmap> bmp2 = {CreateSquareIcon(32, SK_ColorBLUE)};
std::vector<SkBitmap> bmp3 = {CreateSquareIcon(32, SK_ColorRED)};
icons_map.emplace(kIconUrl1, bmp1);
icons_map.emplace(kIconUrl2, bmp2);
icons_map.emplace(GURL("http://www.chromium.org/shortcuts/icon3.png"),
bmp3);
PopulateOtherIcons(&web_app_info, icons_map);
}
// Ensure that reused shortcut icons are processed correctly.
EXPECT_EQ(1U, web_app_info.shortcuts_menu_icon_bitmaps[0].any.size());
EXPECT_EQ(0U, web_app_info.shortcuts_menu_icon_bitmaps[0].maskable.size());
EXPECT_EQ(2U, web_app_info.shortcuts_menu_icon_bitmaps[1].any.size());
EXPECT_EQ(0U, web_app_info.shortcuts_menu_icon_bitmaps[1].maskable.size());
}
// Tests that when PopulateOtherItemIcons is called with no shortcut icon
// urls specified, no data is written to shortcuts_menu_item_infos.
TEST(WebAppInstallUtils, PopulateShortcutItemIconsNoShortcutIcons) {
auto web_app_info = CreateWebAppInstallInfo();
IconsMap icons_map;
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
std::vector<SkBitmap> bmp2 = {CreateSquareIcon(32, SK_ColorBLUE)};
std::vector<SkBitmap> bmp3 = {CreateSquareIcon(32, SK_ColorRED)};
icons_map.emplace(GURL("http://www.chromium.org/shortcuts/icon1.png"), bmp1);
icons_map.emplace(GURL("http://www.chromium.org/shortcuts/icon2.png"), bmp2);
icons_map.emplace(GURL("http://www.chromium.org/shortcuts/icon3.png"), bmp3);
PopulateOtherIcons(&web_app_info, icons_map);
EXPECT_EQ(0U, web_app_info.shortcuts_menu_item_infos.size());
}
// Tests that when PopulateProductIcons is called with maskable
// icons available, web_app_info.icon_bitmaps_{any,maskable} are correctly
// populated.
TEST(WebAppInstallUtils, PopulateProductIcons_MaskableIcons) {
// Construct |icons_map| to pass to PopulateProductIcons().
IconsMap icons_map;
const GURL kIconUrl1("http://www.chromium.org/shortcuts/icon1.png");
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
icons_map.emplace(kIconUrl1, bmp1);
const GURL kIconUrl2("http://www.chromium.org/shortcuts/icon2.png");
std::vector<SkBitmap> bmp2 = {CreateSquareIcon(64, SK_ColorBLUE)};
icons_map.emplace(kIconUrl2, bmp2);
// Construct |web_app_info| to pass icon infos.
auto web_app_info = CreateWebAppInstallInfo();
web_app_info.title = u"App Name";
apps::IconInfo info;
// Icon at URL 1 has both kAny and kMaskable purpose.
info.url = kIconUrl1;
info.purpose = apps::IconInfo::Purpose::kAny;
web_app_info.manifest_icons.push_back(info);
info.purpose = apps::IconInfo::Purpose::kMaskable;
web_app_info.manifest_icons.push_back(info);
// Icon at URL 2 has kMaskable purpose only.
info.url = kIconUrl2;
info.purpose = apps::IconInfo::Purpose::kMaskable;
web_app_info.manifest_icons.push_back(info);
PopulateProductIcons(&web_app_info, &icons_map);
EXPECT_EQ(SizesToGenerate().size(), web_app_info.icon_bitmaps.any.size());
// Expect only icon at URL 1 to be used and resized as.
for (const auto& icon_bitmap : web_app_info.icon_bitmaps.any) {
EXPECT_EQ(SK_ColorWHITE, icon_bitmap.second.getColor(0, 0));
}
EXPECT_EQ(2u, web_app_info.icon_bitmaps.maskable.size());
}
// Tests that when PopulateProductIcons is called with maskable
// icons only, web_app_info.icon_bitmaps_any is correctly populated.
TEST(WebAppInstallUtils, PopulateProductIcons_MaskableIconsOnly) {
// Construct |icons_map| to pass to PopulateProductIcons().
IconsMap icons_map;
const GURL kIconUrl1("http://www.chromium.org/shortcuts/icon1.png");
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
icons_map.emplace(kIconUrl1, bmp1);
// Construct |web_app_info| to pass icon infos.
auto web_app_info = CreateWebAppInstallInfo();
web_app_info.title = u"App Name";
apps::IconInfo info;
info.url = kIconUrl1;
info.purpose = apps::IconInfo::Purpose::kMaskable;
web_app_info.manifest_icons.push_back(info);
PopulateProductIcons(&web_app_info, &icons_map);
// Expect to fall back to using icon from icons_map.
EXPECT_EQ(SizesToGenerate().size(), web_app_info.icon_bitmaps.any.size());
for (const auto& icon_bitmap : web_app_info.icon_bitmaps.any) {
EXPECT_EQ(SK_ColorWHITE, icon_bitmap.second.getColor(0, 0));
}
}
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_InvalidManifestUrl) {
auto web_app_info = CreateWebAppInstallInfo();
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("foo");
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_TRUE(web_app_info.manifest_url.is_empty());
}
// Tests that when PopulateProductIcons is called with no
// app icon or shortcut icon data in web_app_info, and kDesktopPWAShortcutsMenu
// feature enabled, web_app_info.icon_bitmaps_any is correctly populated.
TEST(WebAppInstallUtils, PopulateProductIconsNoWebAppIconData_WithShortcuts) {
auto web_app_info = CreateWebAppInstallInfo();
web_app_info.title = u"App Name";
IconsMap icons_map;
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
icons_map.emplace(GURL("http://www.chromium.org/shortcuts/icon1.png"), bmp1);
PopulateProductIcons(&web_app_info, &icons_map);
// Expect to fall back to using icon from icons_map.
EXPECT_EQ(SizesToGenerate().size(), web_app_info.icon_bitmaps.any.size());
for (const auto& icon_bitmap : web_app_info.icon_bitmaps.any) {
EXPECT_EQ(SK_ColorWHITE, icon_bitmap.second.getColor(0, 0));
}
}
TEST(WebAppInstallUtils, PopulateProductIcons_IsGeneratedIcon) {
{
auto web_app_info = CreateWebAppInstallInfo();
web_app_info.title = u"App Name";
IconsMap icons_map;
PopulateProductIcons(&web_app_info, &icons_map);
EXPECT_TRUE(web_app_info.is_generated_icon);
EXPECT_TRUE(ContainsOneIconOfEachSize(web_app_info.icon_bitmaps.any));
}
{
auto web_app_info = CreateWebAppInstallInfo();
web_app_info.title = u"App Name";
IconsMap icons_map;
AddIconToIconsMap(GURL("http://www.example.org/icon32.png"), icon_size::k32,
SK_ColorCYAN, &icons_map);
// Does upsizing of the smallest icon.
PopulateProductIcons(&web_app_info, &icons_map);
EXPECT_FALSE(web_app_info.is_generated_icon);
EXPECT_TRUE(ContainsOneIconOfEachSize(web_app_info.icon_bitmaps.any));
for (const auto& bitmap_any : web_app_info.icon_bitmaps.any)
EXPECT_EQ(SK_ColorCYAN, bitmap_any.second.getColor(0, 0));
}
{
auto web_app_info = CreateWebAppInstallInfo();
web_app_info.title = u"App Name";
IconsMap icons_map;
AddIconToIconsMap(GURL("http://www.example.org/icon512.png"),
icon_size::k512, SK_ColorMAGENTA, &icons_map);
// Does downsizing of the biggest icon which is not in `SizesToGenerate()`.
PopulateProductIcons(&web_app_info, &icons_map);
EXPECT_FALSE(web_app_info.is_generated_icon);
EXPECT_TRUE(ContainsOneIconOfEachSize(web_app_info.icon_bitmaps.any));
for (const auto& bitmap_any : web_app_info.icon_bitmaps.any)
EXPECT_EQ(SK_ColorMAGENTA, bitmap_any.second.getColor(0, 0));
}
}
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_Translations) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
auto web_app_info = CreateWebAppInstallInfo();
{
blink::Manifest::TranslationItem item;
item.name = "name 1";
item.short_name = "short name 1";
item.description = "description 1";
manifest.translations[u"language 1"] = std::move(item);
}
{
blink::Manifest::TranslationItem item;
item.short_name = "short name 2";
item.description = "description 2";
manifest.translations[u"language 2"] = std::move(item);
}
{
blink::Manifest::TranslationItem item;
item.name = "name 3";
manifest.translations[u"language 3"] = std::move(item);
}
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_EQ(3u, web_app_info.translations.size());
EXPECT_EQ(web_app_info.translations["language 1"].name, "name 1");
EXPECT_EQ(web_app_info.translations["language 1"].short_name, "short name 1");
EXPECT_EQ(web_app_info.translations["language 1"].description,
"description 1");
EXPECT_FALSE(web_app_info.translations["language 2"].name);
EXPECT_EQ(web_app_info.translations["language 2"].short_name, "short name 2");
EXPECT_EQ(web_app_info.translations["language 2"].description,
"description 2");
EXPECT_EQ(web_app_info.translations["language 3"].name, "name 3");
EXPECT_FALSE(web_app_info.translations["language 3"].short_name);
EXPECT_FALSE(web_app_info.translations["language 3"].description);
}
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifest_TabStrip) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
auto web_app_info = CreateWebAppInstallInfo();
{
TabStrip tab_strip;
tab_strip.home_tab = TabStrip::Visibility::kAbsent;
manifest.tab_strip = std::move(tab_strip);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_TRUE(web_app_info.tab_strip.has_value());
EXPECT_EQ(
std::get<TabStrip::Visibility>(web_app_info.tab_strip.value().home_tab),
TabStrip::Visibility::kAbsent);
EXPECT_FALSE(web_app_info.tab_strip.value().new_tab_button.url.has_value());
}
{
blink::Manifest::ImageResource icon;
const GURL kAppIcon("fav1.png");
icon.purpose = {Purpose::ANY};
icon.src = kAppIcon;
TabStrip tab_strip;
blink::Manifest::HomeTabParams home_tab_params;
home_tab_params.icons.push_back(icon);
tab_strip.home_tab = home_tab_params;
blink::Manifest::NewTabButtonParams new_tab_button_params;
new_tab_button_params.url = GURL("https://www.example.com/");
tab_strip.new_tab_button = new_tab_button_params;
manifest.tab_strip = std::move(tab_strip);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_TRUE(web_app_info.tab_strip.has_value());
EXPECT_EQ(std::get<blink::Manifest::HomeTabParams>(
web_app_info.tab_strip.value().home_tab)
.icons.size(),
1u);
EXPECT_EQ(std::get<blink::Manifest::HomeTabParams>(
web_app_info.tab_strip.value().home_tab)
.icons[0]
.src,
kAppIcon);
EXPECT_EQ(web_app_info.tab_strip.value().new_tab_button.url,
GURL("https://www.example.com/"));
}
}
// All home tab icons are saved from the manifest except for icons that
// exceed the maximum allowed size.
TEST(WebAppInstallUtils, UpdateWebAppInfoFromManifestHomeTabIcons_TabStrip) {
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
TabStrip tab_strip;
tab_strip.home_tab = web_app::TabStrip::Visibility::kAuto;
blink::Manifest::HomeTabParams home_tab_params;
for (int size = 1023; size <= 1026; ++size) {
blink::Manifest::ImageResource icon;
icon.src = GURL("http://www.chromium.org/shortcuts/icon1.png");
icon.purpose.push_back(web_app::Purpose::ANY);
icon.sizes.emplace_back(size, size);
home_tab_params.icons.push_back(std::move(icon));
}
tab_strip.home_tab = home_tab_params;
manifest.tab_strip = std::move(tab_strip);
auto web_app_info = CreateWebAppInstallInfo();
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_TRUE(web_app_info.tab_strip.has_value());
const auto& home_tab = std::get<blink::Manifest::HomeTabParams>(
web_app_info.tab_strip.value().home_tab);
EXPECT_EQ(2U, home_tab.icons.size());
}
// Tests that when PopulateOtherItemIcons is called with no home tab icon
// urls specified, no data is written to other_icon_bitmaps.
TEST(WebAppInstallUtils, PopulateHomeTabIconsNoHomeTabIcons_TabStrip) {
auto web_app_info = CreateWebAppInstallInfo();
IconsMap icons_map;
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
std::vector<SkBitmap> bmp2 = {CreateSquareIcon(32, SK_ColorBLUE)};
std::vector<SkBitmap> bmp3 = {CreateSquareIcon(32, SK_ColorRED)};
icons_map.emplace(GURL("http://www.chromium.org/home_tab_icons/icon1.png"),
bmp1);
icons_map.emplace(GURL("http://www.chromium.org/home_tab_icons/icon2.png"),
bmp2);
icons_map.emplace(GURL("http://www.chromium.org/home_tab_icons/icon3.png"),
bmp3);
PopulateOtherIcons(&web_app_info, icons_map);
EXPECT_EQ(0U, web_app_info.other_icon_bitmaps.size());
}
// Tests that SkBitmaps associated with home tab icons are populated in
// their own map in web_app_info.
TEST(WebAppInstallUtils, PopulateHomeTabIcons_TabStrip) {
auto web_app_info = CreateWebAppInstallInfo();
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
TabStrip tab_strip;
tab_strip.home_tab = web_app::TabStrip::Visibility::kAuto;
blink::Manifest::HomeTabParams home_tab_params;
const GURL kIconUrl1("http://www.chromium.org/home_tab_icons/icon1.png");
{
blink::Manifest::ImageResource icon;
icon.src = kIconUrl1;
icon.purpose.push_back(web_app::Purpose::ANY);
icon.sizes.emplace_back(kIconSize, kIconSize);
home_tab_params.icons.push_back(std::move(icon));
}
const GURL kIconUrl2("http://www.chromium.org/home_tab_icons/icon2.png");
{
blink::Manifest::ImageResource icon;
icon.src = kIconUrl1;
icon.purpose.push_back(web_app::Purpose::ANY);
// This icon is too big and will be filtered out
icon.sizes.emplace_back(kIconSize * 2, kIconSize * 2);
home_tab_params.icons.push_back(std::move(icon));
}
EXPECT_EQ(2U, home_tab_params.icons.size());
tab_strip.home_tab = home_tab_params;
manifest.tab_strip = std::move(tab_strip);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_TRUE(web_app_info.tab_strip.has_value());
const auto& home_tab = std::get<blink::Manifest::HomeTabParams>(
web_app_info.tab_strip.value().home_tab);
EXPECT_EQ(2U, home_tab.icons.size());
{
IconsMap icons_map;
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
std::vector<SkBitmap> bmp2 = {CreateSquareIcon(32, SK_ColorBLUE)};
std::vector<SkBitmap> bmp3 = {CreateSquareIcon(32, SK_ColorRED)};
icons_map.emplace(kIconUrl1, bmp1);
icons_map.emplace(kIconUrl2, bmp2);
icons_map.emplace(GURL("http://www.chromium.org/home_tab_icons/icon3.png"),
bmp3);
PopulateOtherIcons(&web_app_info, icons_map);
}
// Ensure that reused home tab icons are processed correctly.
// Icons that are too big and icons that exist in icons_map, but are not in
// web_app_info.tab_strip are filtered out.
EXPECT_EQ(1U, web_app_info.other_icon_bitmaps.size());
}
// Tests proper parsing of ManifestImageResource icons from the manifest into
// |icons_with_size_any| based on the absence of a size parameter.
TEST(WebAppInstallUtils, PopulateAnyIconsCorrectlyManifestParsingSVGOnly) {
WebAppFileHandlerManager::SetIconsSupportedByOsForTesting(/*value=*/true);
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({blink::features::kFileHandlingIcons}, {});
auto web_app_info = CreateWebAppInstallInfo();
// Generate expected data structure for |icons_with_size_any|.
IconsWithSizeAny expected_icon_metadata;
const GURL manifest_icon_no_size_url(
"https://www.example.com/manifest_image_no_size.svg");
const GURL manifest_icon_size_url(
"https://www.example.com/manifest_image_size.svg");
const GURL file_handling_no_size_url(
"https://www.example.com/file_handling_no_size.svg");
const GURL file_handling_size_url(
"https://www.example.com/file_handling_size.png");
const GURL shortcut_icon_no_size_url(
"https://www.example.com/shortcut_menu_icon_no_size.svg");
const GURL shortcut_icon_size_url(
"https://www.example.com/shortcut_menu_icon_size.svg");
const GURL tab_strip_icon_no_size_url(
"https://www.example.com/tab_strip_icon_no_size.svg");
const GURL tab_strip_icon_size_url(
"https://www.example.com/tab_strip_icon_size.jpg");
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("https://www.random_manifest.com");
// Sample manifest icons, one with a size specified, one without.
blink::Manifest::ImageResource manifest_icon_no_size;
manifest_icon_no_size.src = manifest_icon_no_size_url;
manifest_icon_no_size.sizes = {{0, 0}, {196, 196}};
manifest_icon_no_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::ANY,
blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
manifest.icons.push_back(std::move(manifest_icon_no_size));
// Set up the expected icon metadata for manifest icons.
expected_icon_metadata.manifest_icons[IconPurpose::ANY] =
manifest_icon_no_size_url;
expected_icon_metadata.manifest_icons[IconPurpose::MONOCHROME] =
manifest_icon_no_size_url;
expected_icon_metadata.manifest_icon_provided_sizes.emplace(196, 196);
blink::Manifest::ImageResource manifest_icon_size;
manifest_icon_size.src = manifest_icon_size_url;
manifest_icon_size.sizes = {{24, 24}};
manifest_icon_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::ANY};
manifest.icons.push_back(std::move(manifest_icon_size));
expected_icon_metadata.manifest_icon_provided_sizes.emplace(24, 24);
// Sample file handler with no size specified for icons.
auto file_handler = blink::mojom::ManifestFileHandler::New();
file_handler->action = GURL("https://www.action.com/");
file_handler->name = u"Random File";
file_handler->accept[u"text/html"] = {u".html"};
blink::Manifest::ImageResource file_handling_icon_no_size;
file_handling_icon_no_size.src = file_handling_no_size_url;
file_handling_icon_no_size.sizes = {{0, 0}};
file_handling_icon_no_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::MASKABLE};
file_handler->icons.push_back(std::move(file_handling_icon_no_size));
// Set up the expected icon metadata for file handling icons.
expected_icon_metadata.file_handling_icons[IconPurpose::MASKABLE] =
file_handling_no_size_url;
blink::Manifest::ImageResource file_handling_icon_size;
file_handling_icon_size.src = file_handling_size_url;
file_handling_icon_size.sizes = {{64, 64}};
file_handling_icon_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
file_handler->icons.push_back(std::move(file_handling_icon_size));
manifest.file_handlers.push_back(std::move(file_handler));
expected_icon_metadata.file_handling_icon_provided_sizes.emplace(64, 64);
// Sample shortcut menu item info with no size specified for icons.
blink::Manifest::ShortcutItem shortcut_item;
shortcut_item.name = u"Shortcut Name";
shortcut_item.url = GURL("https://www.example.com");
blink::Manifest::ImageResource shortcut_icon_no_size;
shortcut_icon_no_size.src = shortcut_icon_no_size_url;
shortcut_icon_no_size.sizes = {{0, 0}, {512, 512}};
shortcut_icon_no_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::ANY};
shortcut_item.icons.push_back(std::move(shortcut_icon_no_size));
// Set up the expected icon metadata for shortcut menu icons.
expected_icon_metadata.shortcut_menu_icons[IconPurpose::ANY] =
shortcut_icon_no_size_url;
expected_icon_metadata.shortcut_menu_icons_provided_sizes.emplace(512, 512);
blink::Manifest::ImageResource shortcut_icon_with_size;
shortcut_icon_with_size.src = shortcut_icon_size_url;
shortcut_icon_with_size.sizes = {{48, 48}};
shortcut_icon_with_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::MASKABLE};
shortcut_item.icons.push_back(std::move(shortcut_icon_with_size));
manifest.shortcuts.push_back(std::move(shortcut_item));
expected_icon_metadata.shortcut_menu_icons_provided_sizes.emplace(48, 48);
// Sample home tab strip metadata with no size specified for icons.
TabStrip tab_strip;
blink::Manifest::HomeTabParams home_tab_params;
blink::Manifest::ImageResource tab_strip_icon_no_size;
tab_strip_icon_no_size.src = tab_strip_icon_no_size_url;
tab_strip_icon_no_size.sizes = {{0, 0}};
tab_strip_icon_no_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
home_tab_params.icons.push_back(std::move(tab_strip_icon_no_size));
blink::Manifest::ImageResource tab_strip_icon_size;
tab_strip_icon_size.src = tab_strip_icon_size_url;
tab_strip_icon_size.sizes = {{16, 16}};
tab_strip_icon_size.purpose = {
blink::mojom::ManifestImageResource_Purpose::ANY};
home_tab_params.icons.push_back(std::move(tab_strip_icon_size));
tab_strip.home_tab = std::move(home_tab_params);
manifest.tab_strip = std::move(tab_strip);
// Set up the expected icon metadata for home tab icons.
expected_icon_metadata.home_tab_icons[IconPurpose::MONOCHROME] =
tab_strip_icon_no_size_url;
expected_icon_metadata.home_tab_icon_provided_sizes.emplace(16, 16);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
ASSERT_EQ(expected_icon_metadata, web_app_info.icons_with_size_any);
}
class FileHandlersFromManifestTest : public ::testing::TestWithParam<bool> {
public:
FileHandlersFromManifestTest() {
feature_list_.InitAndEnableFeature(blink::features::kFileHandlingIcons);
WebAppFileHandlerManager::SetIconsSupportedByOsForTesting(GetParam());
}
~FileHandlersFromManifestTest() override = default;
protected:
static std::vector<blink::mojom::ManifestFileHandlerPtr>
CreateManifestFileHandlers(unsigned count) {
std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers;
for (unsigned i = 0; i < count; ++i) {
auto file_handler = blink::mojom::ManifestFileHandler::New();
file_handler->action = MakeActionUrl(i);
file_handler->name = base::UTF8ToUTF16(base::StringPrintf("n%u", i));
file_handler->accept[base::UTF8ToUTF16(MakeMimeType(i))] = {
base::UTF8ToUTF16(MakeExtension(i))};
blink::Manifest::ImageResource icon;
icon.src = MakeImageUrl(i);
icon.sizes = {{16, 16}, {32, 32}, {64, 64}};
icon.purpose = {blink::mojom::ManifestImageResource_Purpose::ANY};
file_handler->icons.push_back(std::move(icon));
blink::Manifest::ImageResource icon2;
icon2.src = MakeImageUrlForSecondImage(i);
icon2.sizes = {{16, 16}};
icon2.purpose = {blink::mojom::ManifestImageResource_Purpose::ANY,
blink::mojom::ManifestImageResource_Purpose::MASKABLE};
file_handler->icons.push_back(std::move(icon2));
manifest_file_handlers.push_back(std::move(file_handler));
}
return manifest_file_handlers;
}
static GURL MakeActionUrl(unsigned index) {
return GetStartUrl().Resolve(base::StringPrintf("a%u", index));
}
static GURL MakeImageUrl(unsigned index) {
return GetStartUrl().Resolve(base::StringPrintf("image%u.png", index));
}
static GURL MakeImageUrlForSecondImage(unsigned index) {
return GetStartUrl().Resolve(base::StringPrintf("image%u-2.png", index));
}
static std::string MakeMimeType(unsigned index) {
return base::StringPrintf("application/x-%u", index);
}
static std::string MakeExtension(unsigned index) {
return base::StringPrintf(".e%u", index);
}
static GURL GetStartUrl() {
return GURL("https://www.example.com/index.html");
}
base::test::ScopedFeatureList feature_list_;
};
TEST_P(FileHandlersFromManifestTest, Basic) {
std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers =
CreateManifestFileHandlers(6);
auto web_app_info = CreateWebAppInstallInfo();
PopulateFileHandlerInfoFromManifest(manifest_file_handlers, GetStartUrl(),
&web_app_info);
const apps::FileHandlers& file_handlers = web_app_info.file_handlers;
ASSERT_EQ(file_handlers.size(), 6U);
for (unsigned i = 0; i < 6U; ++i) {
EXPECT_EQ(file_handlers[i].action, MakeActionUrl(i));
ASSERT_EQ(file_handlers[i].accept.size(), 1U);
EXPECT_EQ(file_handlers[i].accept[0].mime_type, MakeMimeType(i));
EXPECT_EQ(file_handlers[i].accept[0].file_extensions.size(), 1U);
EXPECT_EQ(*file_handlers[i].accept[0].file_extensions.begin(),
MakeExtension(i));
if (WebAppFileHandlerManager::IconsEnabled()) {
ASSERT_EQ(file_handlers[i].downloaded_icons.size(), 3U);
// The manifest-specified `sizes` are ignored.
EXPECT_FALSE(file_handlers[i].downloaded_icons[0].square_size_px);
EXPECT_EQ(MakeImageUrl(i), file_handlers[i].downloaded_icons[0].url);
EXPECT_EQ(apps::IconInfo::Purpose::kAny,
file_handlers[i].downloaded_icons[0].purpose);
EXPECT_FALSE(file_handlers[i].downloaded_icons[1].square_size_px);
EXPECT_EQ(MakeImageUrlForSecondImage(i),
file_handlers[i].downloaded_icons[1].url);
EXPECT_EQ(apps::IconInfo::Purpose::kAny,
file_handlers[i].downloaded_icons[1].purpose);
EXPECT_FALSE(file_handlers[i].downloaded_icons[2].square_size_px);
EXPECT_EQ(MakeImageUrlForSecondImage(i),
file_handlers[i].downloaded_icons[2].url);
EXPECT_EQ(apps::IconInfo::Purpose::kMaskable,
file_handlers[i].downloaded_icons[2].purpose);
} else {
EXPECT_TRUE(file_handlers[i].downloaded_icons.empty());
}
}
}
TEST_P(FileHandlersFromManifestTest, PopulateFileHandlerIcons) {
if (!WebAppFileHandlerManager::IconsEnabled())
return;
std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers =
CreateManifestFileHandlers(1);
auto web_app_info = CreateWebAppInstallInfo();
PopulateFileHandlerInfoFromManifest(manifest_file_handlers, GetStartUrl(),
&web_app_info);
const GURL first_image_url = MakeImageUrl(0);
const GURL second_image_url = MakeImageUrlForSecondImage(0);
IconsMap icons_map;
// The first URL returns two valid bitmaps and one invalid (non-square), which
// should be ignored.
std::vector<SkBitmap> bmps1 = {CreateSquareIcon(17, SK_ColorWHITE),
CreateSquareIcon(29, SK_ColorBLUE),
gfx::test::CreateBitmap(16, 15)};
icons_map.emplace(first_image_url, bmps1);
std::vector<SkBitmap> bmps2 = {CreateSquareIcon(79, SK_ColorRED),
CreateSquareIcon(134, SK_ColorRED)};
icons_map.emplace(second_image_url, bmps2);
PopulateOtherIcons(&web_app_info, icons_map);
// Make sure bitmaps are copied from `icons_map` into `web_app_info`.
// Images downloaded from two distinct URLs.
ASSERT_EQ(2U, web_app_info.other_icon_bitmaps.size());
// First URL correlates to two bitmaps.
ASSERT_EQ(2U, web_app_info.other_icon_bitmaps[first_image_url].size());
EXPECT_TRUE(
gfx::BitmapsAreEqual(web_app_info.other_icon_bitmaps[first_image_url][0],
icons_map[first_image_url][0]));
EXPECT_TRUE(
gfx::BitmapsAreEqual(web_app_info.other_icon_bitmaps[first_image_url][1],
icons_map[first_image_url][1]));
// Second URL correlates to two more bitmaps.
ASSERT_EQ(2U, web_app_info.other_icon_bitmaps[second_image_url].size());
EXPECT_TRUE(
gfx::BitmapsAreEqual(web_app_info.other_icon_bitmaps[second_image_url][0],
icons_map[second_image_url][0]));
EXPECT_TRUE(
gfx::BitmapsAreEqual(web_app_info.other_icon_bitmaps[second_image_url][1],
icons_map[second_image_url][1]));
// We end up with one file handler with 6 icon infos. The second URL produces
// 4 IconInfos because it has two bitmaps and two purposes: 2 x 2 = 4.
ASSERT_EQ(1U, web_app_info.file_handlers.size());
// The metadata we expect to be saved after icons are finished downloading and
// processing. Note that the icon sizes saved to `apps::FileHandler::icons`
// match downloaded sizes, not those specified in the manifest.
struct Expectations {
GURL expected_url;
apps::IconInfo::SquareSizePx expected_size;
apps::IconInfo::Purpose expected_purpose;
};
auto expectations = std::to_array<Expectations>({
{first_image_url, 17, apps::IconInfo::Purpose::kAny},
{first_image_url, 29, apps::IconInfo::Purpose::kAny},
{second_image_url, 79, apps::IconInfo::Purpose::kAny},
{second_image_url, 134, apps::IconInfo::Purpose::kAny},
{second_image_url, 79, apps::IconInfo::Purpose::kMaskable},
{second_image_url, 134, apps::IconInfo::Purpose::kMaskable},
});
const size_t num_expectations =
sizeof(expectations) / sizeof(expectations[0]);
ASSERT_EQ(num_expectations,
web_app_info.file_handlers[0].downloaded_icons.size());
for (size_t i = 0; i < num_expectations; ++i) {
const auto& icon = web_app_info.file_handlers[0].downloaded_icons[i];
EXPECT_EQ(expectations[i].expected_url, icon.url);
EXPECT_EQ(expectations[i].expected_size, icon.square_size_px);
EXPECT_EQ(expectations[i].expected_purpose, icon.purpose);
}
}
// Tests both file handlers and home tab icons as they use the same variable to
// store their bitmaps.
TEST_P(FileHandlersFromManifestTest, PopulateFileHandlingAndHomeTabIcons) {
if (!WebAppFileHandlerManager::IconsEnabled()) {
return;
}
auto web_app_info = CreateWebAppInstallInfo();
// Put icons in for the home tab
blink::mojom::Manifest manifest;
manifest.manifest_url = GURL("http://www.chromium.org/manifest.json");
TabStrip tab_strip;
tab_strip.home_tab = web_app::TabStrip::Visibility::kAuto;
blink::Manifest::HomeTabParams home_tab_params;
const GURL kHomeTabIconUrl1(
"http://www.chromium.org/home_tab_icons/icon1.png");
{
blink::Manifest::ImageResource icon;
icon.src = kHomeTabIconUrl1;
icon.purpose.push_back(web_app::Purpose::ANY);
icon.sizes.emplace_back(kIconSize, kIconSize);
home_tab_params.icons.push_back(std::move(icon));
}
const GURL kHomeTabIconUrl2(
"http://www.chromium.org/home_tab_icons/icon2.png");
{
blink::Manifest::ImageResource icon;
icon.src = kHomeTabIconUrl1;
icon.purpose.push_back(web_app::Purpose::ANY);
// This icon is too big and will be filtered out
icon.sizes.emplace_back(kIconSize * 2, kIconSize * 2);
home_tab_params.icons.push_back(std::move(icon));
}
EXPECT_EQ(2U, home_tab_params.icons.size());
tab_strip.home_tab = home_tab_params;
manifest.tab_strip = std::move(tab_strip);
UpdateWebAppInfoFromManifest(manifest, &web_app_info);
EXPECT_TRUE(web_app_info.tab_strip.has_value());
const auto& home_tab = std::get<blink::Manifest::HomeTabParams>(
web_app_info.tab_strip.value().home_tab);
EXPECT_EQ(2U, home_tab.icons.size());
// Put icons in for file handlers
std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers =
CreateManifestFileHandlers(1);
PopulateFileHandlerInfoFromManifest(manifest_file_handlers, GetStartUrl(),
&web_app_info);
const GURL kFileHandlerIconUrl1 = MakeImageUrlForSecondImage(0);
const GURL kFileHandlerIconUrl2 = MakeImageUrl(0);
IconsMap icons_map;
{
std::vector<SkBitmap> bmp1 = {CreateSquareIcon(32, SK_ColorWHITE)};
std::vector<SkBitmap> bmp2 = {CreateSquareIcon(32, SK_ColorBLUE)};
std::vector<SkBitmap> bmp3 = {CreateSquareIcon(79, SK_ColorRED),
CreateSquareIcon(134, SK_ColorRED)};
std::vector<SkBitmap> bmp4 = {CreateSquareIcon(17, SK_ColorWHITE),
CreateSquareIcon(29, SK_ColorBLUE),
gfx::test::CreateBitmap(16, 15)};
icons_map.emplace(kHomeTabIconUrl1, bmp1);
icons_map.emplace(kHomeTabIconUrl2, bmp2);
icons_map.emplace(kFileHandlerIconUrl1, bmp3);
icons_map.emplace(kFileHandlerIconUrl2, bmp4);
PopulateOtherIcons(&web_app_info, icons_map);
}
// Ensure that reused home tab icons are processed correctly.
// Icons that are too big and icons that exist in icons_map, but are not in
// web_app_info.tab_strip are filtered out.
EXPECT_EQ(3U, web_app_info.other_icon_bitmaps.size());
// Fourth URL correlates to two bitmaps.
ASSERT_EQ(2U, web_app_info.other_icon_bitmaps[kFileHandlerIconUrl2].size());
EXPECT_TRUE(gfx::BitmapsAreEqual(
web_app_info.other_icon_bitmaps[kFileHandlerIconUrl2][0],
icons_map[kFileHandlerIconUrl2][0]));
EXPECT_TRUE(gfx::BitmapsAreEqual(
web_app_info.other_icon_bitmaps[kFileHandlerIconUrl2][1],
icons_map[kFileHandlerIconUrl2][1]));
// Third URL correlates to two more bitmaps.
ASSERT_EQ(2U, web_app_info.other_icon_bitmaps[kFileHandlerIconUrl1].size());
EXPECT_TRUE(gfx::BitmapsAreEqual(
web_app_info.other_icon_bitmaps[kFileHandlerIconUrl1][0],
icons_map[kFileHandlerIconUrl1][0]));
EXPECT_TRUE(gfx::BitmapsAreEqual(
web_app_info.other_icon_bitmaps[kFileHandlerIconUrl1][1],
icons_map[kFileHandlerIconUrl1][1]));
// First URL correlates to one more bitmap.
ASSERT_EQ(1U, web_app_info.other_icon_bitmaps[kHomeTabIconUrl1].size());
EXPECT_TRUE(
gfx::BitmapsAreEqual(web_app_info.other_icon_bitmaps[kHomeTabIconUrl1][0],
icons_map[kHomeTabIconUrl1][0]));
// We end up with one file handler with 6 icon infos. The second URL produces
// 4 IconInfos because it has two bitmaps and two purposes: 2 x 2 = 4.
ASSERT_EQ(1U, web_app_info.file_handlers.size());
// The metadata we expect to be saved after icons are finished downloading and
// processing. Note that the icon sizes saved to `apps::FileHandler::icons`
// match downloaded sizes, not those specified in the manifest.
struct Expectations {
GURL expected_url;
apps::IconInfo::SquareSizePx expected_size;
apps::IconInfo::Purpose expected_purpose;
};
auto expectations = std::to_array<Expectations>({
{kFileHandlerIconUrl2, 17, apps::IconInfo::Purpose::kAny},
{kFileHandlerIconUrl2, 29, apps::IconInfo::Purpose::kAny},
{kFileHandlerIconUrl1, 79, apps::IconInfo::Purpose::kAny},
{kFileHandlerIconUrl1, 134, apps::IconInfo::Purpose::kAny},
{kFileHandlerIconUrl1, 79, apps::IconInfo::Purpose::kMaskable},
{kFileHandlerIconUrl1, 134, apps::IconInfo::Purpose::kMaskable},
});
const size_t num_expectations =
sizeof(expectations) / sizeof(expectations[0]);
ASSERT_EQ(num_expectations,
web_app_info.file_handlers[0].downloaded_icons.size());
for (size_t i = 0; i < num_expectations; ++i) {
const auto& icon = web_app_info.file_handlers[0].downloaded_icons[i];
EXPECT_EQ(expectations[i].expected_url, icon.url);
EXPECT_EQ(expectations[i].expected_size, icon.square_size_px);
EXPECT_EQ(expectations[i].expected_purpose, icon.purpose);
}
}
// Test duplicate icon download urls that from the manifest.
TEST(WebAppInstallUtils, DuplicateIconDownloadURLs) {
base::test::ScopedFeatureList feature_list;
feature_list.InitWithFeatures({blink::features::kFileHandlingIcons}, {});
auto web_app_info = CreateWebAppInstallInfo();
// manifest icons
{
apps::IconInfo info;
info.url = GURL("http://www.chromium.org/image/icon1.png");
web_app_info.manifest_icons.push_back(info);
}
{
apps::IconInfo info;
info.url = GURL("http://www.chromium.org/image/icon2.png");
web_app_info.manifest_icons.push_back(info);
}
// shortcut icons
{
WebAppShortcutsMenuItemInfo shortcut_item;
{
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_manifest_icons;
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon2.png");
shortcut_manifest_icons.push_back(icon);
}
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon3.png");
shortcut_manifest_icons.push_back(icon);
}
shortcut_item.SetShortcutIconInfosForPurpose(
IconPurpose::ANY, std::move(shortcut_manifest_icons));
}
{
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_manifest_icons;
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon3.png");
shortcut_manifest_icons.push_back(icon);
}
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon4.png");
shortcut_manifest_icons.push_back(icon);
}
shortcut_item.SetShortcutIconInfosForPurpose(
IconPurpose::MONOCHROME, std::move(shortcut_manifest_icons));
}
web_app_info.shortcuts_menu_item_infos.push_back(std::move(shortcut_item));
}
{
WebAppShortcutsMenuItemInfo shortcut_item;
{
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_manifest_icons;
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon4.png");
shortcut_manifest_icons.push_back(icon);
}
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon5.png");
shortcut_manifest_icons.push_back(icon);
}
shortcut_item.SetShortcutIconInfosForPurpose(
IconPurpose::ANY, std::move(shortcut_manifest_icons));
}
{
std::vector<WebAppShortcutsMenuItemInfo::Icon> shortcut_manifest_icons;
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon5.png");
shortcut_manifest_icons.push_back(icon);
}
{
WebAppShortcutsMenuItemInfo::Icon icon;
icon.url = GURL("http://www.chromium.org/image/icon6.png");
shortcut_manifest_icons.push_back(icon);
}
shortcut_item.SetShortcutIconInfosForPurpose(
IconPurpose::MASKABLE, std::move(shortcut_manifest_icons));
}
web_app_info.shortcuts_menu_item_infos.push_back(std::move(shortcut_item));
}
// file handler icons
{
apps::FileHandler file_handler;
std::vector<apps::IconInfo> downloaded_icons;
{
apps::IconInfo info;
info.url = GURL("http://www.chromium.org/image/icon6.png");
web_app_info.manifest_icons.push_back(info);
}
{
apps::IconInfo info;
info.url = GURL("http://www.chromium.org/image/icon7.png");
web_app_info.manifest_icons.push_back(info);
}
web_app_info.file_handlers.push_back(file_handler);
}
{
apps::FileHandler file_handler;
std::vector<apps::IconInfo> downloaded_icons;
{
apps::IconInfo info;
info.url = GURL("http://www.chromium.org/image/icon7.png");
web_app_info.manifest_icons.push_back(info);
}
{
apps::IconInfo info;
info.url = GURL("http://www.chromium.org/image/icon8.png");
web_app_info.manifest_icons.push_back(info);
}
web_app_info.file_handlers.push_back(file_handler);
}
IconUrlSizeSet download_urls = GetValidIconUrlsToDownload(web_app_info);
const size_t download_urls_size = 8;
EXPECT_EQ(download_urls_size, download_urls.size());
for (size_t i = 0; i < download_urls_size; i++) {
std::string url_str = "http://www.chromium.org/image/icon" +
base::NumberToString(i + 1) + ".png";
EXPECT_EQ(1u, download_urls.count(IconUrlWithSize::CreateForUnspecifiedSize(
GURL(url_str))));
}
}
INSTANTIATE_TEST_SUITE_P(, FileHandlersFromManifestTest, testing::Bool());
TEST(WebAppInstallUtils, SetWebAppManifestFields_Summary) {
GURL start_url("https://www.chromium.org/index.html");
auto web_app_info = CreateWebAppInstallInfoFromStartUrl(start_url);
web_app_info.scope = web_app_info.start_url().GetWithoutFilename();
web_app_info.title = u"App Name";
web_app_info.description = u"App Description";
web_app_info.theme_color = SK_ColorCYAN;
web_app_info.dark_mode_theme_color = SK_ColorBLACK;
web_app_info.background_color = SK_ColorMAGENTA;
web_app_info.dark_mode_background_color = SK_ColorBLACK;
const webapps::AppId app_id = GenerateAppId(/*manifest_id_path=*/std::nullopt,
web_app_info.start_url());
auto web_app = std::make_unique<WebApp>(app_id);
SetWebAppManifestFields(web_app_info, *web_app);
EXPECT_EQ(web_app->scope(), GURL("https://www.chromium.org/"));
EXPECT_EQ(web_app->untranslated_name(), "App Name");
EXPECT_EQ(web_app->untranslated_description(), "App Description");
EXPECT_TRUE(web_app->theme_color().has_value());
EXPECT_EQ(*web_app->theme_color(), SK_ColorCYAN);
EXPECT_TRUE(web_app->dark_mode_theme_color().has_value());
EXPECT_EQ(*web_app->dark_mode_theme_color(), SK_ColorBLACK);
EXPECT_TRUE(web_app->background_color().has_value());
EXPECT_EQ(*web_app->background_color(), SK_ColorMAGENTA);
EXPECT_TRUE(web_app->dark_mode_background_color().has_value());
EXPECT_EQ(*web_app->dark_mode_background_color(), SK_ColorBLACK);
web_app_info.theme_color = std::nullopt;
web_app_info.dark_mode_theme_color = std::nullopt;
web_app_info.background_color = std::nullopt;
web_app_info.dark_mode_background_color = std::nullopt;
SetWebAppManifestFields(web_app_info, *web_app);
EXPECT_FALSE(web_app->theme_color().has_value());
EXPECT_FALSE(web_app->dark_mode_theme_color().has_value());
EXPECT_FALSE(web_app->background_color().has_value());
EXPECT_FALSE(web_app->dark_mode_background_color().has_value());
}
TEST(WebAppInstallUtils, SetWebAppManifestFields_ShareTarget) {
auto web_app_info = CreateWebAppInstallInfoFromStartUrl(StartUrl());
web_app_info.scope = web_app_info.start_url().GetWithoutFilename();
web_app_info.title = u"App Name";
const webapps::AppId app_id = GenerateAppId(/*manifest_id_path=*/std::nullopt,
web_app_info.start_url());
auto web_app = std::make_unique<WebApp>(app_id);
{
apps::ShareTarget share_target;
share_target.action = GURL("http://example.com/share1");
share_target.method = apps::ShareTarget::Method::kPost;
share_target.enctype = apps::ShareTarget::Enctype::kMultipartFormData;
share_target.params.title = "kTitle";
share_target.params.text = "kText";
apps::ShareTarget::Files file_filter;
file_filter.name = "kImages";
file_filter.accept.push_back(".png");
file_filter.accept.push_back("image/png");
share_target.params.files.push_back(std::move(file_filter));
web_app_info.share_target = std::move(share_target);
}
SetWebAppManifestFields(web_app_info, *web_app);
{
EXPECT_TRUE(web_app->share_target().has_value());
auto share_target = *web_app->share_target();
EXPECT_EQ(share_target.action, GURL("http://example.com/share1"));
EXPECT_EQ(share_target.method, apps::ShareTarget::Method::kPost);
EXPECT_EQ(share_target.enctype,
apps::ShareTarget::Enctype::kMultipartFormData);
EXPECT_EQ(share_target.params.title, "kTitle");
EXPECT_EQ(share_target.params.text, "kText");
EXPECT_TRUE(share_target.params.url.empty());
EXPECT_EQ(share_target.params.files.size(), 1U);
EXPECT_EQ(share_target.params.files[0].name, "kImages");
EXPECT_EQ(share_target.params.files[0].accept.size(), 2U);
EXPECT_EQ(share_target.params.files[0].accept[0], ".png");
EXPECT_EQ(share_target.params.files[0].accept[1], "image/png");
}
{
apps::ShareTarget share_target;
share_target.action = GURL("http://example.com/share2");
share_target.method = apps::ShareTarget::Method::kGet;
share_target.enctype = apps::ShareTarget::Enctype::kFormUrlEncoded;
share_target.params.text = "kText";
share_target.params.url = "kUrl";
web_app_info.share_target = std::move(share_target);
}
SetWebAppManifestFields(web_app_info, *web_app);
{
EXPECT_TRUE(web_app->share_target().has_value());
auto share_target = *web_app->share_target();
EXPECT_EQ(share_target.action, GURL("http://example.com/share2"));
EXPECT_EQ(share_target.method, apps::ShareTarget::Method::kGet);
EXPECT_EQ(share_target.enctype,
apps::ShareTarget::Enctype::kFormUrlEncoded);
EXPECT_TRUE(share_target.params.title.empty());
EXPECT_EQ(share_target.params.text, "kText");
EXPECT_EQ(share_target.params.url, "kUrl");
EXPECT_TRUE(share_target.params.files.empty());
}
web_app_info.share_target = std::nullopt;
SetWebAppManifestFields(web_app_info, *web_app);
EXPECT_FALSE(web_app->share_target().has_value());
}
} // namespace web_app