| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback.h" |
| #include "base/callback_helpers.h" |
| #include "base/containers/contains.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "build/chromeos_buildflags.h" |
| #include "cc/test/pixel_comparator.h" |
| #include "cc/test/pixel_test_utils.h" |
| #include "chrome/browser/apps/app_service/app_icon/app_icon_factory.h" |
| #include "chrome/browser/apps/app_service/app_icon/app_icon_test_util.h" |
| #include "chrome/browser/extensions/chrome_app_icon.h" |
| #include "chrome/browser/web_applications/test/test_file_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_icon_test_utils.h" |
| #include "chrome/browser/web_applications/test/web_app_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_icon_manager.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_registrar.h" |
| #include "chrome/browser/web_applications/web_app_registry_update.h" |
| #include "chrome/browser/web_applications/web_app_sync_bridge.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/services/app_service/public/cpp/icon_types.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "extensions/grit/extensions_browser_resources.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/layout.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ash/constants/ash_features.h" |
| #include "chrome/browser/apps/icon_standardizer.h" |
| #include "chrome/browser/chromeos/arc/icon_decode_request.h" |
| #include "chrome/browser/ui/app_list/md_icon_normalizer.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/grit/chrome_unscaled_resources.h" |
| #endif |
| |
| namespace apps { |
| |
| class WebAppIconFactoryTest : public ChromeRenderViewHostTestHarness { |
| public: |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| WebAppIconFactoryTest() { |
| scoped_feature_list_.InitWithFeatures( |
| {}, {features::kWebAppsCrosapi, chromeos::features::kLacrosPrimary}); |
| } |
| #else |
| WebAppIconFactoryTest() = default; |
| #endif |
| |
| ~WebAppIconFactoryTest() override = default; |
| |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| web_app_provider_ = web_app::WebAppProvider::GetForWebApps(profile()); |
| ASSERT_TRUE(web_app_provider_); |
| |
| base::RunLoop run_loop; |
| web_app_provider_->sync_bridge().Init(run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| icon_manager_ = static_cast<web_app::WebAppIconManager*>( |
| &(web_app_provider_->icon_manager())); |
| ASSERT_TRUE(icon_manager_); |
| |
| sync_bridge_ = &web_app_provider_->sync_bridge(); |
| } |
| |
| void RegisterApp(std::unique_ptr<web_app::WebApp> web_app) { |
| std::unique_ptr<web_app::WebAppRegistryUpdate> update = |
| sync_bridge().BeginUpdate(); |
| update->CreateApp(std::move(web_app)); |
| sync_bridge().CommitUpdate(std::move(update), base::DoNothing()); |
| } |
| |
| void WriteIcons(const std::string& app_id, |
| const std::vector<IconPurpose>& purposes, |
| const std::vector<int>& sizes_px, |
| const std::vector<SkColor>& colors) { |
| ASSERT_EQ(sizes_px.size(), colors.size()); |
| ASSERT_TRUE(!purposes.empty()); |
| |
| IconBitmaps icon_bitmaps; |
| for (size_t i = 0; i < sizes_px.size(); ++i) { |
| if (base::Contains(purposes, IconPurpose::ANY)) { |
| web_app::AddGeneratedIcon(&icon_bitmaps.any, sizes_px[i], colors[i]); |
| } |
| if (base::Contains(purposes, IconPurpose::MASKABLE)) { |
| web_app::AddGeneratedIcon(&icon_bitmaps.maskable, sizes_px[i], |
| colors[i]); |
| } |
| } |
| |
| base::RunLoop run_loop; |
| icon_manager_->WriteData(app_id, std::move(icon_bitmaps), {}, {}, |
| base::BindLambdaForTesting([&](bool success) { |
| EXPECT_TRUE(success); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| void GenerateWebAppIcon(const std::string& app_id, |
| IconPurpose purpose, |
| const std::vector<int>& sizes_px, |
| apps::ScaleToSize scale_to_size_in_px, |
| gfx::ImageSkia& output_image_skia) { |
| base::RunLoop run_loop; |
| icon_manager().ReadIcons( |
| app_id, purpose, sizes_px, |
| base::BindOnce( |
| [](gfx::ImageSkia* image_skia, int size_in_dip, |
| apps::ScaleToSize scale_to_size_in_px, |
| base::OnceClosure load_app_icon_callback, |
| std::map<SquareSizePx, SkBitmap> icon_bitmaps) { |
| for (auto it : scale_to_size_in_px) { |
| int icon_size_in_px = |
| gfx::ScaleToFlooredSize(gfx::Size(size_in_dip, size_in_dip), |
| it.first) |
| .width(); |
| |
| SkBitmap bitmap = icon_bitmaps[it.second]; |
| if (bitmap.width() != icon_size_in_px) { |
| bitmap = skia::ImageOperations::Resize( |
| bitmap, skia::ImageOperations::RESIZE_LANCZOS3, |
| icon_size_in_px, icon_size_in_px); |
| } |
| image_skia->AddRepresentation( |
| gfx::ImageSkiaRep(bitmap, it.first)); |
| } |
| std::move(load_app_icon_callback).Run(); |
| }, |
| &output_image_skia, kSizeInDip, scale_to_size_in_px, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| extensions::ChromeAppIcon::ResizeFunction resize_function; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (purpose == IconPurpose::ANY) { |
| output_image_skia = apps::CreateStandardIconImage(output_image_skia); |
| } |
| if (purpose == IconPurpose::MASKABLE) { |
| output_image_skia = apps::ApplyBackgroundAndMask(output_image_skia); |
| } |
| #endif |
| |
| extensions::ChromeAppIcon::ApplyEffects( |
| kSizeInDip, resize_function, true /* app_launchable */, |
| true /* from_bookmark */, extensions::ChromeAppIcon::Badge::kNone, |
| &output_image_skia); |
| |
| EnsureRepresentationsLoaded(output_image_skia); |
| } |
| |
| void GenerateWebAppCompressedIcon(const std::string& app_id, |
| IconPurpose purpose, |
| const std::vector<int>& sizes_px, |
| apps::ScaleToSize scale_to_size_in_px, |
| std::vector<uint8_t>& result) { |
| gfx::ImageSkia image_skia; |
| GenerateWebAppIcon(app_id, purpose, sizes_px, scale_to_size_in_px, |
| image_skia); |
| |
| const float scale = 1.0; |
| const gfx::ImageSkiaRep& image_skia_rep = |
| image_skia.GetRepresentation(scale); |
| ASSERT_EQ(image_skia_rep.scale(), scale); |
| |
| const SkBitmap& bitmap = image_skia_rep.GetBitmap(); |
| const bool discard_transparency = false; |
| ASSERT_TRUE(gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, discard_transparency, |
| &result)); |
| } |
| |
| std::vector<uint8_t> GenerateWebAppNonEffectCompressedIcon( |
| const std::string& app_id, |
| const SquareSizePx icon_size_in_px) { |
| base::test::TestFuture<std::vector<uint8_t>> result; |
| icon_manager().ReadSmallestCompressedIconAny(app_id, icon_size_in_px, |
| result.GetCallback()); |
| return result.Get(); |
| } |
| |
| void LoadIconFromWebApp(const std::string& app_id, |
| apps::IconEffects icon_effects, |
| gfx::ImageSkia& output_image_skia) { |
| base::RunLoop run_loop; |
| |
| auto icon_type = apps::IconType::kUncompressed; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| icon_type = apps::IconType::kStandard; |
| #endif |
| |
| apps::LoadIconFromWebApp( |
| profile(), icon_type, kSizeInDip, app_id, icon_effects, |
| base::BindOnce( |
| [](gfx::ImageSkia* image, base::OnceClosure load_app_icon_callback, |
| apps::IconValuePtr icon) { |
| *image = icon->uncompressed; |
| std::move(load_app_icon_callback).Run(); |
| }, |
| &output_image_skia, run_loop.QuitClosure())); |
| run_loop.Run(); |
| |
| EnsureRepresentationsLoaded(output_image_skia); |
| } |
| |
| apps::IconValuePtr LoadCompressedIconBlockingFromWebApp( |
| const std::string& app_id, |
| apps::IconEffects icon_effects) { |
| base::RunLoop run_loop; |
| apps::IconValuePtr icon_value; |
| apps::LoadIconFromWebApp( |
| profile(), apps::IconType::kCompressed, kSizeInDip, app_id, |
| icon_effects, base::BindLambdaForTesting([&](apps::IconValuePtr icon) { |
| icon_value = std::move(icon); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| return icon_value; |
| } |
| |
| web_app::WebAppIconManager& icon_manager() { return *icon_manager_; } |
| |
| web_app::WebAppProvider& web_app_provider() { return *web_app_provider_; } |
| |
| web_app::WebAppSyncBridge& sync_bridge() { return *sync_bridge_; } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| raw_ptr<web_app::WebAppProvider> web_app_provider_; |
| raw_ptr<web_app::WebAppIconManager> icon_manager_; |
| raw_ptr<web_app::WebAppSyncBridge> sync_bridge_; |
| }; |
| |
| TEST_F(WebAppIconFactoryTest, LoadNonMaskableIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 96; |
| const int kIconSize2 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY}, sizes_px, colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, sizes_px)); |
| |
| gfx::ImageSkia src_image_skia; |
| GenerateWebAppIcon(app_id, IconPurpose::ANY, sizes_px, |
| {{1.0, kIconSize1}, {2.0, kIconSize2}}, src_image_skia); |
| |
| gfx::ImageSkia dst_image_skia; |
| apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| icon_effect |= apps::IconEffects::kCrOsStandardIcon; |
| #endif |
| |
| LoadIconFromWebApp(app_id, icon_effect, dst_image_skia); |
| |
| VerifyIcon(src_image_skia, dst_image_skia); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadNonMaskableNonEffectCompressedIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = kSizeInDip; |
| const int kIconSize2 = 128; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY}, sizes_px, colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, sizes_px)); |
| |
| std::vector<uint8_t> src_data; |
| src_data = GenerateWebAppNonEffectCompressedIcon(app_id, kSizeInDip); |
| |
| auto icon = |
| LoadCompressedIconBlockingFromWebApp(app_id, apps::IconEffects::kNone); |
| |
| VerifyCompressedIcon(src_data, *icon); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, |
| LoadNonMaskableNonEffectCompressedIconWithDifferentSizeIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 96; |
| const int kIconSize2 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY}, sizes_px, colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, sizes_px)); |
| |
| std::vector<uint8_t> src_data; |
| src_data = GenerateWebAppNonEffectCompressedIcon(app_id, kSizeInDip); |
| |
| auto icon = |
| LoadCompressedIconBlockingFromWebApp(app_id, apps::IconEffects::kNone); |
| |
| VerifyCompressedIcon(src_data, *icon); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadNonMaskableCompressedIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 96; |
| const int kIconSize2 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY}, sizes_px, colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, sizes_px)); |
| |
| std::vector<uint8_t> src_data; |
| GenerateWebAppCompressedIcon(app_id, IconPurpose::ANY, sizes_px, |
| {{1.0, kIconSize1}, {2.0, kIconSize2}}, |
| src_data); |
| |
| apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners; |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| icon_effect |= apps::IconEffects::kCrOsStandardIcon; |
| #endif |
| |
| auto icon = LoadCompressedIconBlockingFromWebApp(app_id, icon_effect); |
| |
| VerifyCompressedIcon(src_data, *icon); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadMaskableIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 128; |
| const int kIconSize2 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY, IconPurpose::MASKABLE}, sizes_px, |
| colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, {kIconSize1}); |
| web_app->SetDownloadedIconSizes(IconPurpose::MASKABLE, {kIconSize2}); |
| |
| RegisterApp(std::move(web_app)); |
| |
| gfx::ImageSkia src_image_skia; |
| gfx::ImageSkia dst_image_skia; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| ASSERT_TRUE( |
| icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, {kIconSize2})); |
| |
| GenerateWebAppIcon(app_id, IconPurpose::MASKABLE, {kIconSize2}, |
| {{1.0, kIconSize2}, {2.0, kIconSize2}}, src_image_skia); |
| |
| LoadIconFromWebApp(app_id, |
| apps::IconEffects::kRoundCorners | |
| apps::IconEffects::kCrOsStandardBackground | |
| apps::IconEffects::kCrOsStandardMask, |
| dst_image_skia); |
| VerifyIcon(src_image_skia, dst_image_skia); |
| return; |
| #endif |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, {kIconSize1})); |
| |
| GenerateWebAppIcon(app_id, IconPurpose::ANY, {kIconSize1}, |
| {{1.0, kIconSize1}, {2.0, kIconSize1}}, src_image_skia); |
| |
| LoadIconFromWebApp(app_id, apps::IconEffects::kRoundCorners, dst_image_skia); |
| |
| VerifyIcon(src_image_skia, dst_image_skia); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadMaskableCompressedIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 128; |
| const int kIconSize2 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY, IconPurpose::MASKABLE}, sizes_px, |
| colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, {kIconSize1}); |
| web_app->SetDownloadedIconSizes(IconPurpose::MASKABLE, {kIconSize2}); |
| |
| RegisterApp(std::move(web_app)); |
| |
| std::vector<uint8_t> src_data; |
| apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners; |
| apps::IconValuePtr icon; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| icon_effect |= apps::IconEffects::kCrOsStandardBackground | |
| apps::IconEffects::kCrOsStandardMask; |
| ASSERT_TRUE( |
| icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, {kIconSize2})); |
| |
| GenerateWebAppCompressedIcon(app_id, IconPurpose::MASKABLE, {kIconSize2}, |
| {{1.0, kIconSize2}, {2.0, kIconSize2}}, |
| src_data); |
| |
| icon = LoadCompressedIconBlockingFromWebApp(app_id, icon_effect); |
| |
| VerifyCompressedIcon(src_data, *icon); |
| return; |
| #endif |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, {kIconSize1})); |
| |
| GenerateWebAppCompressedIcon(app_id, IconPurpose::ANY, {kIconSize1}, |
| {{1.0, kIconSize1}, {2.0, kIconSize1}}, |
| src_data); |
| |
| icon = LoadCompressedIconBlockingFromWebApp(app_id, icon_effect); |
| |
| VerifyCompressedIcon(src_data, *icon); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadNonMaskableIconWithMaskableIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 96; |
| const int kIconSize2 = 128; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY, IconPurpose::MASKABLE}, sizes_px, |
| colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::MASKABLE, {kIconSize1}); |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, {kIconSize2}); |
| |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, {kIconSize2})); |
| |
| gfx::ImageSkia src_image_skia; |
| GenerateWebAppIcon(app_id, IconPurpose::ANY, {kIconSize2}, |
| {{1.0, kIconSize2}, {2.0, kIconSize2}}, src_image_skia); |
| |
| gfx::ImageSkia dst_image_skia; |
| apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| icon_effect |= apps::IconEffects::kCrOsStandardIcon; |
| #endif |
| |
| LoadIconFromWebApp(app_id, icon_effect, dst_image_skia); |
| |
| VerifyIcon(src_image_skia, dst_image_skia); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadSmallMaskableIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 128; |
| const int kIconSize2 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW}; |
| WriteIcons(app_id, {IconPurpose::ANY, IconPurpose::MASKABLE}, sizes_px, |
| colors); |
| |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| web_app->SetDownloadedIconSizes(IconPurpose::MASKABLE, sizes_px); |
| |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::MASKABLE, sizes_px)); |
| |
| gfx::ImageSkia src_image_skia; |
| GenerateWebAppIcon(app_id, IconPurpose::MASKABLE, sizes_px, |
| {{1.0, kIconSize1}, {2.0, kIconSize1}}, src_image_skia); |
| |
| gfx::ImageSkia dst_image_skia; |
| LoadIconFromWebApp(app_id, |
| apps::IconEffects::kRoundCorners | |
| apps::IconEffects::kCrOsStandardBackground | |
| apps::IconEffects::kCrOsStandardMask, |
| dst_image_skia); |
| |
| VerifyIcon(src_image_skia, dst_image_skia); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadExactSizeIcon) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 48; |
| const int kIconSize2 = 64; |
| const int kIconSize3 = 96; |
| const int kIconSize4 = 128; |
| const int kIconSize5 = 256; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2, kIconSize3, |
| kIconSize4, kIconSize5}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW, |
| SK_ColorBLACK, SK_ColorRED, SK_ColorBLUE}; |
| WriteIcons(app_id, {IconPurpose::ANY}, sizes_px, colors); |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, sizes_px)); |
| |
| gfx::ImageSkia src_image_skia; |
| GenerateWebAppIcon(app_id, IconPurpose::ANY, sizes_px, |
| {{1.0, kIconSize2}, {2.0, kIconSize4}}, src_image_skia); |
| |
| gfx::ImageSkia dst_image_skia; |
| apps::IconEffects icon_effect = apps::IconEffects::kRoundCorners; |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| icon_effect |= apps::IconEffects::kCrOsStandardIcon; |
| #endif |
| |
| LoadIconFromWebApp(app_id, icon_effect, dst_image_skia); |
| |
| VerifyIcon(src_image_skia, dst_image_skia); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, LoadIconFailed) { |
| auto web_app = web_app::test::CreateWebApp(); |
| const std::string app_id = web_app->app_id(); |
| |
| const int kIconSize1 = 48; |
| const int kIconSize2 = 64; |
| const int kIconSize3 = 96; |
| const std::vector<int> sizes_px{kIconSize1, kIconSize2, kIconSize3}; |
| const std::vector<SkColor> colors{SK_ColorGREEN, SK_ColorYELLOW, |
| SK_ColorBLACK}; |
| WriteIcons(app_id, {IconPurpose::ANY}, sizes_px, colors); |
| web_app->SetDownloadedIconSizes(IconPurpose::ANY, sizes_px); |
| |
| RegisterApp(std::move(web_app)); |
| |
| ASSERT_TRUE(icon_manager().HasIcons(app_id, IconPurpose::ANY, sizes_px)); |
| |
| gfx::ImageSkia src_image_skia; |
| LoadDefaultIcon(src_image_skia); |
| |
| gfx::ImageSkia dst_image_skia; |
| LoadIconFromWebApp( |
| app_id, |
| apps::IconEffects::kRoundCorners | apps::IconEffects::kCrOsStandardIcon, |
| dst_image_skia); |
| |
| VerifyIcon(src_image_skia, dst_image_skia); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| TEST_F(WebAppIconFactoryTest, ConvertSquareBitmapsToImageSkia_Empty) { |
| gfx::ImageSkia converted_image = ConvertSquareBitmapsToImageSkia( |
| /*icon_bitmaps=*/std::map<SquareSizePx, SkBitmap>{}, |
| /*icon_effects=*/apps::IconEffects::kNone, |
| /*size_hint_in_dip=*/32); |
| |
| EXPECT_TRUE(converted_image.isNull()); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, |
| ConvertSquareBitmapsToImageSkia_OneBigIconForDownscale) { |
| std::map<SquareSizePx, SkBitmap> icon_bitmaps; |
| web_app::AddGeneratedIcon(&icon_bitmaps, web_app::icon_size::k512, |
| SK_ColorYELLOW); |
| |
| gfx::ImageSkia converted_image = ConvertSquareBitmapsToImageSkia( |
| icon_bitmaps, /*icon_effects=*/apps::IconEffects::kNone, |
| /*size_hint_in_dip=*/32); |
| |
| const std::vector<ui::ResourceScaleFactor>& scale_factors = |
| ui::GetSupportedResourceScaleFactors(); |
| ASSERT_EQ(2U, scale_factors.size()); |
| |
| for (auto& scale_factor : scale_factors) { |
| const float scale = ui::GetScaleForResourceScaleFactor(scale_factor); |
| ASSERT_TRUE(converted_image.HasRepresentation(scale)); |
| EXPECT_EQ( |
| SK_ColorYELLOW, |
| converted_image.GetRepresentation(scale).GetBitmap().getColor(0, 0)); |
| } |
| } |
| |
| TEST_F(WebAppIconFactoryTest, |
| ConvertSquareBitmapsToImageSkia_OneSmallIconNoUpscale) { |
| std::map<SquareSizePx, SkBitmap> icon_bitmaps; |
| web_app::AddGeneratedIcon(&icon_bitmaps, web_app::icon_size::k16, |
| SK_ColorMAGENTA); |
| |
| gfx::ImageSkia converted_image = ConvertSquareBitmapsToImageSkia( |
| icon_bitmaps, /*icon_effects=*/apps::IconEffects::kNone, |
| /*size_hint_in_dip=*/32); |
| EXPECT_TRUE(converted_image.isNull()); |
| } |
| |
| TEST_F(WebAppIconFactoryTest, ConvertSquareBitmapsToImageSkia_MatchBigger) { |
| const std::vector<SquareSizePx> sizes_px{ |
| web_app::icon_size::k16, web_app::icon_size::k32, web_app::icon_size::k48, |
| web_app::icon_size::k64, web_app::icon_size::k128}; |
| const std::vector<SkColor> colors{SK_ColorBLUE, SK_ColorRED, SK_ColorMAGENTA, |
| SK_ColorGREEN, SK_ColorWHITE}; |
| |
| std::map<SquareSizePx, SkBitmap> icon_bitmaps; |
| for (size_t i = 0; i < sizes_px.size(); ++i) { |
| web_app::AddGeneratedIcon(&icon_bitmaps, sizes_px[i], colors[i]); |
| } |
| |
| gfx::ImageSkia converted_image = ConvertSquareBitmapsToImageSkia( |
| icon_bitmaps, /*icon_effects=*/apps::IconEffects::kNone, |
| /*size_hint_in_dip=*/32); |
| |
| const std::vector<ui::ResourceScaleFactor>& scale_factors = |
| ui::GetSupportedResourceScaleFactors(); |
| ASSERT_EQ(2U, scale_factors.size()); |
| |
| // Expects 32px and 64px to be chosen for 32dip-normal and 32dip-hi-DPI (2.0f |
| // scale). |
| const std::vector<SkColor> expected_colors{SK_ColorRED, SK_ColorGREEN}; |
| |
| for (size_t i = 0; i < scale_factors.size(); ++i) { |
| const float scale = ui::GetScaleForResourceScaleFactor(scale_factors[i]); |
| ASSERT_TRUE(converted_image.HasRepresentation(scale)); |
| EXPECT_EQ( |
| expected_colors[i], |
| converted_image.GetRepresentation(scale).GetBitmap().getColor(0, 0)); |
| } |
| } |
| |
| TEST_F(WebAppIconFactoryTest, ConvertSquareBitmapsToImageSkia_StandardEffect) { |
| const std::vector<SquareSizePx> sizes_px{web_app::icon_size::k48, |
| web_app::icon_size::k96}; |
| const std::vector<SkColor> colors{SK_ColorBLUE, SK_ColorRED}; |
| |
| std::map<SquareSizePx, SkBitmap> icon_bitmaps; |
| for (size_t i = 0; i < sizes_px.size(); ++i) { |
| web_app::AddGeneratedIcon(&icon_bitmaps, sizes_px[i], colors[i]); |
| } |
| |
| gfx::ImageSkia converted_image = ConvertSquareBitmapsToImageSkia( |
| icon_bitmaps, |
| /*icon_effects=*/apps::IconEffects::kCrOsStandardBackground | |
| apps::IconEffects::kCrOsStandardMask, |
| /*size_hint_in_dip=*/32); |
| |
| EnsureRepresentationsLoaded(converted_image); |
| |
| const std::vector<ui::ResourceScaleFactor>& scale_factors = |
| ui::GetSupportedResourceScaleFactors(); |
| ASSERT_EQ(2U, scale_factors.size()); |
| |
| for (size_t i = 0; i < scale_factors.size(); ++i) { |
| const float scale = ui::GetScaleForResourceScaleFactor(scale_factors[i]); |
| ASSERT_TRUE(converted_image.HasRepresentation(scale)); |
| |
| // No color in the upper left corner. |
| EXPECT_FALSE( |
| converted_image.GetRepresentation(scale).GetBitmap().getColor(0, 0)); |
| |
| // Has color in the center. |
| const SquareSizePx center_px = sizes_px[i] / 2; |
| EXPECT_TRUE(converted_image.GetRepresentation(scale).GetBitmap().getColor( |
| center_px, center_px)); |
| } |
| } |
| |
| // Regression test for crash. https://crbug.com/1335266 |
| TEST_F(WebAppIconFactoryTest, ApplyBackgroundAndMask_NullImage) { |
| gfx::ImageSkia image = apps::ApplyBackgroundAndMask(gfx::ImageSkia()); |
| DCHECK(image.isNull()); |
| } |
| |
| #endif // BUILDFLAG(IS_CHROMEOS_ASH) |
| |
| } // namespace apps |