| // Copyright 2019 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 "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "components/services/app_service/public/cpp/app_types.h" |
| #include "components/services/app_service/public/cpp/icon_cache.h" |
| #include "components/services/app_service/public/cpp/icon_types.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/image/image_skia_rep.h" |
| |
| class AppsIconCacheTest : public testing::Test { |
| protected: |
| enum class HitOrMiss { |
| kHit, |
| kMiss, |
| }; |
| |
| using UniqueReleaser = std::unique_ptr<apps::IconLoader::Releaser>; |
| static constexpr HitOrMiss kHit = HitOrMiss::kHit; |
| static constexpr HitOrMiss kMiss = HitOrMiss::kMiss; |
| |
| class FakeIconLoader : public apps::IconLoader { |
| public: |
| int NumLoadIconFromIconKeyCalls() { return num_load_calls_; } |
| |
| void SetReturnPlaceholderIcons(bool b) { return_placeholder_icons_ = b; } |
| |
| private: |
| std::unique_ptr<Releaser> LoadIconFromIconKey( |
| apps::AppType app_type, |
| const std::string& app_id, |
| const apps::IconKey& icon_key, |
| apps::IconType icon_type, |
| int32_t size_hint_in_dip, |
| bool allow_placeholder_icon, |
| apps::LoadIconCallback callback) override { |
| num_load_calls_++; |
| |
| auto iv = std::make_unique<apps::IconValue>(); |
| if (icon_type == apps::IconType::kUncompressed) { |
| iv->icon_type = apps::IconType::kUncompressed; |
| iv->uncompressed = |
| gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(1, 1), 1.0f)); |
| iv->is_placeholder_icon = return_placeholder_icons_; |
| } |
| |
| std::move(callback).Run(std::move(iv)); |
| return nullptr; |
| } |
| |
| int num_load_calls_ = 0; |
| bool return_placeholder_icons_ = false; |
| }; |
| |
| UniqueReleaser LoadIcon(apps::IconLoader* loader, |
| FakeIconLoader* fake, |
| const std::string& app_id, |
| HitOrMiss expect_hom, |
| bool allow_placeholder_icon = false) { |
| int before = fake->NumLoadIconFromIconKeyCalls(); |
| |
| UniqueReleaser releaser; |
| releaser = loader->LoadIcon( |
| apps::AppType::kWeb, app_id, apps::IconType::kUncompressed, |
| /*size_hint_in_dip=*/1, allow_placeholder_icon, base::DoNothing()); |
| |
| int after = fake->NumLoadIconFromIconKeyCalls(); |
| HitOrMiss actual_hom = (after == before) ? kHit : kMiss; |
| EXPECT_EQ(expect_hom, actual_hom); |
| |
| return releaser; |
| } |
| |
| void TestBasics(apps::IconCache::GarbageCollectionPolicy gc_policy, |
| bool remove_icon = false) { |
| FakeIconLoader fake; |
| apps::IconCache cache(&fake, gc_policy); |
| |
| UniqueReleaser a0 = LoadIcon(&cache, &fake, "apricot", kMiss); |
| a0.reset(); |
| |
| UniqueReleaser b0 = LoadIcon(&cache, &fake, "banana", kMiss); |
| UniqueReleaser b1 = LoadIcon(&cache, &fake, "banana", kHit); |
| b0.reset(); |
| b1.reset(); |
| |
| UniqueReleaser c0 = LoadIcon(&cache, &fake, "cherry", kMiss); |
| UniqueReleaser c1 = LoadIcon(&cache, &fake, "cherry", kHit); |
| UniqueReleaser c2 = LoadIcon(&cache, &fake, "cherry", kHit); |
| c2.reset(); |
| c1.reset(); |
| |
| UniqueReleaser d0 = LoadIcon(&cache, &fake, "durian", kMiss); |
| d0.reset(); |
| |
| UniqueReleaser c3 = LoadIcon(&cache, &fake, "cherry", kHit); |
| c3.reset(); |
| |
| HitOrMiss expect_hom = kHit; |
| if (gc_policy == apps::IconCache::GarbageCollectionPolicy::kExplicit) { |
| if (remove_icon) { |
| cache.RemoveIcon(apps::AppType::kWeb, "cherry"); |
| cache.RemoveIcon(apps::AppType::kWeb, "apricot"); |
| expect_hom = kMiss; |
| } else { |
| cache.SweepReleasedIcons(); |
| } |
| } |
| |
| UniqueReleaser c4 = LoadIcon(&cache, &fake, "cherry", expect_hom); |
| c4.reset(); |
| c0.reset(); |
| |
| if (gc_policy == apps::IconCache::GarbageCollectionPolicy::kExplicit) { |
| if (remove_icon) { |
| cache.RemoveIcon(apps::AppType::kWeb, "cherry"); |
| } else { |
| cache.SweepReleasedIcons(); |
| } |
| } |
| |
| UniqueReleaser c5 = LoadIcon(&cache, &fake, "cherry", kMiss); |
| c5.reset(); |
| } |
| |
| void TestPlaceholder(apps::IconCache::GarbageCollectionPolicy gc_policy) { |
| FakeIconLoader fake; |
| apps::IconCache cache(&fake, gc_policy); |
| bool allow_placeholder_icon; |
| |
| fake.SetReturnPlaceholderIcons(true); |
| |
| allow_placeholder_icon = true; |
| UniqueReleaser f0 = |
| LoadIcon(&cache, &fake, "fig", kMiss, allow_placeholder_icon); |
| |
| fake.SetReturnPlaceholderIcons(false); |
| |
| // The next LoadIcon call is a kMiss, even though there is a cache entry, |
| // because the cache entry holds a placeholder icon, but we have |
| // allow_placeholder_icon == false. |
| // |
| // A side effect of the next LoadIcon call is to prime the cache with the |
| // real (non-placeholder) icon. |
| |
| allow_placeholder_icon = false; |
| UniqueReleaser f1 = |
| LoadIcon(&cache, &fake, "fig", kMiss, allow_placeholder_icon); |
| |
| // The next two LoadIcons are all both kHit's. The real icon can be served |
| // from the cache, regardless of allow_placeholder_icon's value. |
| |
| allow_placeholder_icon = false; |
| UniqueReleaser f2 = |
| LoadIcon(&cache, &fake, "fig", kHit, allow_placeholder_icon); |
| |
| allow_placeholder_icon = true; |
| UniqueReleaser f3 = |
| LoadIcon(&cache, &fake, "fig", kHit, allow_placeholder_icon); |
| } |
| |
| void TestAfterZeroRefcount(apps::IconCache::GarbageCollectionPolicy gc_policy, |
| bool remove_icon = false) { |
| FakeIconLoader fake; |
| apps::IconCache cache(&fake, gc_policy); |
| |
| UniqueReleaser w0 = LoadIcon(&cache, &fake, "watermelon", kMiss); |
| w0.reset(); |
| |
| // We now have a zero ref-count. Whether the next LoadIcon call is kHit or |
| // kMiss depends on our gc_policy. |
| |
| HitOrMiss expect_hom; |
| switch (gc_policy) { |
| case apps::IconCache::GarbageCollectionPolicy::kEager: |
| expect_hom = kMiss; |
| break; |
| case apps::IconCache::GarbageCollectionPolicy::kExplicit: |
| expect_hom = kHit; |
| break; |
| } |
| |
| UniqueReleaser w1 = LoadIcon(&cache, &fake, "watermelon", expect_hom); |
| w1.reset(); |
| |
| // Once again, we have a zero ref-count, but for a kExplicit gc_policy, we |
| // also explicitly SweepReleasedIcons(), so the next LoadIcon call should |
| // get kMiss. |
| |
| if (gc_policy == apps::IconCache::GarbageCollectionPolicy::kExplicit) { |
| if (remove_icon) { |
| cache.RemoveIcon(apps::AppType::kWeb, "watermelon"); |
| } else { |
| cache.SweepReleasedIcons(); |
| } |
| } |
| |
| UniqueReleaser w2 = LoadIcon(&cache, &fake, "watermelon", kMiss); |
| w2.reset(); |
| } |
| }; |
| |
| TEST_F(AppsIconCacheTest, Eager) { |
| static constexpr apps::IconCache::GarbageCollectionPolicy gc_policy = |
| apps::IconCache::GarbageCollectionPolicy::kEager; |
| |
| TestBasics(gc_policy); |
| TestPlaceholder(gc_policy); |
| TestAfterZeroRefcount(gc_policy); |
| } |
| |
| TEST_F(AppsIconCacheTest, ExplicitSweepReleasedIcons) { |
| static constexpr apps::IconCache::GarbageCollectionPolicy gc_policy = |
| apps::IconCache::GarbageCollectionPolicy::kExplicit; |
| |
| TestBasics(gc_policy); |
| TestPlaceholder(gc_policy); |
| TestAfterZeroRefcount(gc_policy); |
| } |
| |
| TEST_F(AppsIconCacheTest, ExplicitRemoveIcons) { |
| static constexpr apps::IconCache::GarbageCollectionPolicy gc_policy = |
| apps::IconCache::GarbageCollectionPolicy::kExplicit; |
| |
| TestBasics(gc_policy, true /* remove_icon */); |
| TestPlaceholder(gc_policy); |
| TestAfterZeroRefcount(gc_policy, true /* remove_icon */); |
| } |