| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_icon_image_loader.h" |
| |
| #include "base/macros.h" |
| #include "base/memory/linked_ptr.h" |
| #include "chrome/browser/chromeos/launcher_search_provider/error_reporter.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/image/canvas_image_source.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| #include "url/gurl.h" |
| |
| using chromeos::launcher_search_provider::ErrorReporter; |
| |
| namespace app_list { |
| |
| namespace { |
| |
| const char kTestExtensionId[] = "foo"; |
| const char kTestCustomIconURL[] = "chrome-extension://foo/bar"; |
| |
| // Generates an image source which is filled with |fill_color|. |
| class FillColorImageSource : public gfx::CanvasImageSource { |
| public: |
| FillColorImageSource(const gfx::Size& image_size, const SkColor fill_color) |
| : CanvasImageSource(image_size, false), fill_color_(fill_color) {} |
| |
| void Draw(gfx::Canvas* canvas) override { |
| canvas->FillRect(gfx::Rect(size()), fill_color_); |
| } |
| |
| private: |
| const SkColor fill_color_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FillColorImageSource); |
| }; |
| |
| // Test implementation of LauncherSearchIconImageLoader. |
| class LauncherSearchIconImageLoaderTestImpl |
| : public LauncherSearchIconImageLoader { |
| public: |
| // Use base class constructor. |
| using LauncherSearchIconImageLoader::LauncherSearchIconImageLoader; |
| |
| const gfx::ImageSkia& LoadExtensionIcon() override { |
| // Returns 32x32 black image. |
| extension_icon_ = gfx::ImageSkia( |
| std::make_unique<FillColorImageSource>(icon_size_, SK_ColorBLACK), |
| icon_size_); |
| return extension_icon_; |
| } |
| |
| // Calls OnExtensionIconImageChnaged callback with |extension_icon|. |
| void LoadExtensionIconAsync(const gfx::ImageSkia& image) { |
| OnExtensionIconChanged(image); |
| } |
| |
| void LoadIconResourceFromExtension() override { |
| // For success case, returns 32x32 blue image. |
| is_load_extension_icon_resource_called_ = true; |
| } |
| |
| bool IsLoadExtensionIconResourceCalled() const { |
| return is_load_extension_icon_resource_called_; |
| } |
| |
| // Calls OnCustomIconLoaded callback with |custom_icon|. Sets an empty image |
| // for simulating a failure case. |
| void CallOnCustomIconLoaded(gfx::ImageSkia custom_icon) { |
| OnCustomIconLoaded(custom_icon); |
| } |
| |
| private: |
| gfx::ImageSkia extension_icon_; |
| bool is_load_extension_icon_resource_called_ = false; |
| }; |
| |
| // A fake error reporter to test error message. |
| class FakeErrorReporter : public ErrorReporter { |
| public: |
| FakeErrorReporter() : ErrorReporter(nullptr) { |
| last_message_.reset(new std::string()); |
| } |
| explicit FakeErrorReporter(const linked_ptr<std::string>& last_message) |
| : ErrorReporter(nullptr), last_message_(last_message) {} |
| ~FakeErrorReporter() override {} |
| void Warn(const std::string& message) override { |
| last_message_->clear(); |
| last_message_->append(message); |
| } |
| |
| const std::string& GetLastWarningMessage() { return *last_message_.get(); } |
| |
| std::unique_ptr<ErrorReporter> Duplicate() override { |
| return std::make_unique<FakeErrorReporter>(last_message_); |
| } |
| |
| private: |
| linked_ptr<std::string> last_message_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakeErrorReporter); |
| }; |
| |
| // Creates a test extension with |extension_id|. |
| scoped_refptr<extensions::Extension> CreateTestExtension( |
| const std::string& extension_id) { |
| base::DictionaryValue manifest; |
| std::string error; |
| manifest.SetKey(extensions::manifest_keys::kVersion, base::Value("1")); |
| manifest.SetKey(extensions::manifest_keys::kName, |
| base::Value("TestExtension")); |
| return extensions::Extension::Create( |
| base::FilePath(), extensions::Manifest::UNPACKED, manifest, |
| extensions::Extension::NO_FLAGS, extension_id, &error); |
| } |
| |
| // Returns true if icon image of |result_image| equals to |expected_image|. |
| bool IsEqual(const gfx::ImageSkia& expected_image, |
| const gfx::ImageSkia& result_image) { |
| return gfx::test::AreBitmapsEqual( |
| expected_image.GetRepresentation(1.0).sk_bitmap(), |
| result_image.GetRepresentation(1.0).sk_bitmap()); |
| } |
| |
| } // namespace |
| |
| class LauncherSearchIconImageLoaderTest : public testing::Test { |
| protected: |
| void SetUp() override { extension_ = CreateTestExtension(kTestExtensionId); } |
| |
| std::unique_ptr<FakeErrorReporter> GetFakeErrorReporter() { |
| return std::make_unique<FakeErrorReporter>(); |
| } |
| |
| scoped_refptr<extensions::Extension> extension_; |
| }; |
| |
| TEST_F(LauncherSearchIconImageLoaderTest, WithoutCustomIconSuccessCase) { |
| GURL icon_url; // No custom icon. |
| LauncherSearchIconImageLoaderTestImpl impl(icon_url, nullptr, nullptr, 32, |
| GetFakeErrorReporter()); |
| impl.LoadResources(); |
| |
| // Assert that extension icon image is set to icon image and badge icon image |
| // is null. |
| gfx::Size icon_size(32, 32); |
| gfx::ImageSkia expected_image( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorBLACK), |
| icon_size); |
| ASSERT_TRUE(IsEqual(expected_image, impl.GetIconImage())); |
| |
| ASSERT_TRUE(impl.GetBadgeIconImage().isNull()); |
| } |
| |
| TEST_F(LauncherSearchIconImageLoaderTest, ExtensionIconAsyncLoadSuccessCase) { |
| GURL icon_url; // No custom icon. |
| LauncherSearchIconImageLoaderTestImpl impl(icon_url, nullptr, nullptr, 32, |
| GetFakeErrorReporter()); |
| impl.LoadResources(); |
| |
| // Extension icon is loaded as async. |
| gfx::Size icon_size(32, 32); |
| gfx::ImageSkia extension_icon( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorGREEN), |
| icon_size); |
| impl.LoadExtensionIconAsync(extension_icon); |
| |
| // Assert that the asynchronously loaded image is set to icon image and badge |
| // icon image is null. |
| gfx::ImageSkia expected_image( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorGREEN), |
| icon_size); |
| ASSERT_TRUE(IsEqual(expected_image, impl.GetIconImage())); |
| |
| ASSERT_TRUE(impl.GetBadgeIconImage().isNull()); |
| } |
| |
| TEST_F(LauncherSearchIconImageLoaderTest, WithCustomIconSuccessCase) { |
| GURL icon_url(kTestCustomIconURL); |
| LauncherSearchIconImageLoaderTestImpl impl( |
| icon_url, nullptr, extension_.get(), 32, GetFakeErrorReporter()); |
| ASSERT_FALSE(impl.IsLoadExtensionIconResourceCalled()); |
| impl.LoadResources(); |
| |
| // Assert that LoadExtensionIconResource is called. |
| ASSERT_TRUE(impl.IsLoadExtensionIconResourceCalled()); |
| |
| // Load custom icon as async. |
| gfx::Size icon_size(32, 32); |
| gfx::ImageSkia custom_icon( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorGREEN), |
| icon_size); |
| impl.CallOnCustomIconLoaded(custom_icon); |
| |
| // Assert that custom icon image is set to icon image and extension icon image |
| // is set to badge icon image. |
| gfx::ImageSkia expected_image( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorGREEN), |
| icon_size); |
| ASSERT_TRUE(IsEqual(expected_image, impl.GetIconImage())); |
| |
| gfx::ImageSkia expected_badge_icon_image( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorBLACK), |
| icon_size); |
| ASSERT_TRUE(IsEqual(expected_badge_icon_image, impl.GetBadgeIconImage())); |
| } |
| |
| TEST_F(LauncherSearchIconImageLoaderTest, InvalidCustomIconUrl) { |
| // Use a really long URL (for testing the string truncation). |
| // The URL is from the wrong extension (foo2), so should be rejected. |
| std::string invalid_url = |
| "chrome-extension://foo2/bar/" |
| "901234567890123456789012345678901234567890123456789012345678901234567890" |
| "1"; |
| ASSERT_EQ(101U, invalid_url.size()); |
| |
| std::unique_ptr<FakeErrorReporter> fake_error_reporter = |
| GetFakeErrorReporter(); |
| GURL icon_url(invalid_url); |
| LauncherSearchIconImageLoaderTestImpl impl(icon_url, nullptr, |
| extension_.get(), 32, |
| fake_error_reporter->Duplicate()); |
| impl.LoadResources(); |
| |
| // Warning message should be provided. |
| ASSERT_EQ( |
| "[chrome.launcherSearchProvider.setSearchResults] Invalid icon URL: " |
| "chrome-extension://foo2/bar/" |
| "901234567890123456789012345678901234567890123456789012345678901234567..." |
| ". Must have a valid URL within chrome-extension://foo.", |
| fake_error_reporter->GetLastWarningMessage()); |
| |
| // LoadExtensionIconResource should not be called. |
| ASSERT_FALSE(impl.IsLoadExtensionIconResourceCalled()); |
| } |
| |
| TEST_F(LauncherSearchIconImageLoaderTest, FailedToLoadCustomIcon) { |
| std::unique_ptr<FakeErrorReporter> fake_error_reporter = |
| GetFakeErrorReporter(); |
| GURL icon_url(kTestCustomIconURL); |
| LauncherSearchIconImageLoaderTestImpl impl(icon_url, nullptr, |
| extension_.get(), 32, |
| fake_error_reporter->Duplicate()); |
| impl.LoadResources(); |
| ASSERT_TRUE(impl.IsLoadExtensionIconResourceCalled()); |
| |
| // Fails to load custom icon by passing an empty image. |
| gfx::ImageSkia custom_icon; |
| impl.CallOnCustomIconLoaded(custom_icon); |
| |
| // Warning message should be shown. |
| ASSERT_EQ( |
| "[chrome.launcherSearchProvider.setSearchResults] Failed to load icon " |
| "URL: chrome-extension://foo/bar", |
| fake_error_reporter->GetLastWarningMessage()); |
| |
| // Assert that extension icon image is set to icon image and badge icon image |
| // is null. |
| gfx::Size icon_size(32, 32); |
| gfx::ImageSkia expected_image( |
| std::make_unique<FillColorImageSource>(icon_size, SK_ColorBLACK), |
| icon_size); |
| ASSERT_TRUE(IsEqual(expected_image, impl.GetIconImage())); |
| |
| ASSERT_TRUE(impl.GetBadgeIconImage().isNull()); |
| } |
| |
| } // namespace app_list |