extension_icon_image_unittest.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "extensions/browser/extension_icon_image.h"
  5. #include <vector>
  6. #include "base/json/json_file_value_serializer.h"
  7. #include "base/path_service.h"
  8. #include "base/run_loop.h"
  9. #include "content/public/test/test_browser_context.h"
  10. #include "extensions/browser/extensions_test.h"
  11. #include "extensions/browser/image_loader.h"
  12. #include "extensions/browser/test_image_loader.h"
  13. #include "extensions/common/extension.h"
  14. #include "extensions/common/extension_paths.h"
  15. #include "extensions/common/manifest.h"
  16. #include "extensions/common/manifest_handlers/icons_handler.h"
  17. #include "skia/ext/image_operations.h"
  18. #include "testing/gtest/include/gtest/gtest.h"
  19. #include "ui/base/resource/resource_bundle.h"
  20. #include "ui/gfx/image/image_skia_rep.h"
  21. #include "ui/gfx/image/image_skia_source.h"
  22. #include "ui/gfx/skia_util.h"
  23. using extensions::mojom::ManifestLocation;
  24. namespace extensions {
  25. namespace {
  26. SkBitmap CreateBlankBitmapForScale(int size_dip,
  27. ui::ResourceScaleFactor scale_factor) {
  28. SkBitmap bitmap;
  29. const float scale = ui::GetScaleForResourceScaleFactor(scale_factor);
  30. bitmap.allocN32Pixels(static_cast<int>(size_dip * scale),
  31. static_cast<int>(size_dip * scale));
  32. bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
  33. return bitmap;
  34. }
  35. SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
  36. if (original.width() == size && original.height() == size)
  37. return original;
  38. SkBitmap resized = skia::ImageOperations::Resize(
  39. original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
  40. return resized;
  41. }
  42. // Used to test behavior including images defined by an image skia source.
  43. // |GetImageForScale| simply returns image representation from the image given
  44. // in the ctor.
  45. class MockImageSkiaSource : public gfx::ImageSkiaSource {
  46. public:
  47. explicit MockImageSkiaSource(const gfx::ImageSkia& image)
  48. : image_(image) {
  49. }
  50. ~MockImageSkiaSource() override {}
  51. gfx::ImageSkiaRep GetImageForScale(float scale) override {
  52. return image_.GetRepresentation(scale);
  53. }
  54. private:
  55. gfx::ImageSkia image_;
  56. };
  57. class ExtensionIconImageTest : public ExtensionsTest,
  58. public IconImage::Observer {
  59. public:
  60. ExtensionIconImageTest()
  61. : image_loaded_count_(0), quit_in_image_loaded_(false) {}
  62. ExtensionIconImageTest(const ExtensionIconImageTest&) = delete;
  63. ExtensionIconImageTest& operator=(const ExtensionIconImageTest&) = delete;
  64. ~ExtensionIconImageTest() override {}
  65. void WaitForImageLoad() {
  66. quit_in_image_loaded_ = true;
  67. base::RunLoop().Run();
  68. quit_in_image_loaded_ = false;
  69. }
  70. int ImageLoadedCount() {
  71. int result = image_loaded_count_;
  72. image_loaded_count_ = 0;
  73. return result;
  74. }
  75. scoped_refptr<Extension> CreateExtension(const char* name,
  76. ManifestLocation location) {
  77. // Create and load an extension.
  78. base::FilePath test_file;
  79. if (!base::PathService::Get(DIR_TEST_DATA, &test_file)) {
  80. EXPECT_FALSE(true);
  81. return nullptr;
  82. }
  83. test_file = test_file.AppendASCII(name);
  84. int error_code = 0;
  85. std::string error;
  86. JSONFileValueDeserializer deserializer(
  87. test_file.AppendASCII("manifest.json"));
  88. std::unique_ptr<base::DictionaryValue> valid_value =
  89. base::DictionaryValue::From(
  90. deserializer.Deserialize(&error_code, &error));
  91. EXPECT_EQ(0, error_code) << error;
  92. if (error_code != 0)
  93. return nullptr;
  94. EXPECT_TRUE(valid_value.get());
  95. if (!valid_value)
  96. return nullptr;
  97. return Extension::Create(test_file, location, *valid_value,
  98. Extension::NO_FLAGS, &error);
  99. }
  100. // IconImage::Delegate overrides:
  101. void OnExtensionIconImageChanged(IconImage* image) override {
  102. image_loaded_count_++;
  103. if (quit_in_image_loaded_)
  104. base::RunLoop::QuitCurrentWhenIdleDeprecated();
  105. }
  106. gfx::ImageSkia GetDefaultIcon() {
  107. return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f));
  108. }
  109. private:
  110. int image_loaded_count_;
  111. bool quit_in_image_loaded_;
  112. };
  113. } // namespace
  114. TEST_F(ExtensionIconImageTest, Basic) {
  115. std::vector<ui::ResourceScaleFactor> supported_factors;
  116. supported_factors.push_back(ui::k100Percent);
  117. supported_factors.push_back(ui::k200Percent);
  118. ui::test::ScopedSetSupportedResourceScaleFactors scoped_supported(
  119. supported_factors);
  120. scoped_refptr<Extension> extension(CreateExtension(
  121. "extension_icon_image", ManifestLocation::kInvalidLocation));
  122. ASSERT_TRUE(extension.get() != nullptr);
  123. gfx::ImageSkia default_icon = GetDefaultIcon();
  124. // Load images we expect to find as representations in icon_image, so we
  125. // can later use them to validate icon_image.
  126. SkBitmap bitmap_16 =
  127. TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "16.png", 16);
  128. ASSERT_FALSE(bitmap_16.empty());
  129. // There is no image of size 32 defined in the extension manifest, so we
  130. // should expect manifest image of size 48 resized to size 32.
  131. SkBitmap bitmap_48_resized_to_32 =
  132. TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "48.png", 32);
  133. ASSERT_FALSE(bitmap_48_resized_to_32.empty());
  134. IconImage image(browser_context(),
  135. extension.get(),
  136. IconsInfo::GetIcons(extension.get()),
  137. 16,
  138. default_icon,
  139. this);
  140. // No representations in |image_| yet.
  141. gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
  142. ASSERT_EQ(0u, image_reps.size());
  143. // Gets representation for a scale factor.
  144. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  145. // Before the image representation is loaded, image should contain blank
  146. // image representation.
  147. EXPECT_TRUE(
  148. gfx::BitmapsAreEqual(representation.GetBitmap(),
  149. CreateBlankBitmapForScale(16, ui::k100Percent)));
  150. WaitForImageLoad();
  151. EXPECT_EQ(1, ImageLoadedCount());
  152. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  153. representation = image.image_skia().GetRepresentation(1.0f);
  154. // We should get the right representation now.
  155. EXPECT_TRUE(gfx::BitmapsAreEqual(representation.GetBitmap(), bitmap_16));
  156. EXPECT_EQ(16, representation.pixel_width());
  157. // Gets representation for an additional scale factor.
  158. representation = image.image_skia().GetRepresentation(2.0f);
  159. EXPECT_TRUE(
  160. gfx::BitmapsAreEqual(representation.GetBitmap(),
  161. CreateBlankBitmapForScale(16, ui::k200Percent)));
  162. WaitForImageLoad();
  163. EXPECT_EQ(1, ImageLoadedCount());
  164. ASSERT_EQ(2u, image.image_skia().image_reps().size());
  165. representation = image.image_skia().GetRepresentation(2.0f);
  166. // Image should have been resized.
  167. EXPECT_EQ(32, representation.pixel_width());
  168. EXPECT_TRUE(gfx::BitmapsAreEqual(representation.GetBitmap(),
  169. bitmap_48_resized_to_32));
  170. }
  171. // There is no resource with either exact or bigger size, but there is a smaller
  172. // resource.
  173. TEST_F(ExtensionIconImageTest, FallbackToSmallerWhenNoBigger) {
  174. std::vector<ui::ResourceScaleFactor> supported_factors;
  175. supported_factors.push_back(ui::k100Percent);
  176. supported_factors.push_back(ui::k200Percent);
  177. ui::test::ScopedSetSupportedResourceScaleFactors scoped_supported(
  178. supported_factors);
  179. scoped_refptr<Extension> extension(CreateExtension(
  180. "extension_icon_image", ManifestLocation::kInvalidLocation));
  181. ASSERT_TRUE(extension.get() != nullptr);
  182. gfx::ImageSkia default_icon = GetDefaultIcon();
  183. // Load images we expect to find as representations in icon_image, so we
  184. // can later use them to validate icon_image.
  185. SkBitmap bitmap_48 =
  186. TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "48.png", 48);
  187. ASSERT_FALSE(bitmap_48.empty());
  188. IconImage image(browser_context(),
  189. extension.get(),
  190. IconsInfo::GetIcons(extension.get()),
  191. 32,
  192. default_icon,
  193. this);
  194. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
  195. WaitForImageLoad();
  196. EXPECT_EQ(1, ImageLoadedCount());
  197. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  198. representation = image.image_skia().GetRepresentation(2.0f);
  199. // We should have loaded the biggest smaller resource resized to the actual
  200. // size.
  201. EXPECT_EQ(2.0f, representation.scale());
  202. EXPECT_EQ(64, representation.pixel_width());
  203. EXPECT_TRUE(gfx::BitmapsAreEqual(representation.GetBitmap(),
  204. EnsureBitmapSize(bitmap_48, 64)));
  205. }
  206. // There is no resource with exact size, but there is a smaller and a bigger
  207. // one. The bigger resource should be loaded.
  208. TEST_F(ExtensionIconImageTest, FallbackToBigger) {
  209. scoped_refptr<Extension> extension(CreateExtension(
  210. "extension_icon_image", ManifestLocation::kInvalidLocation));
  211. ASSERT_TRUE(extension.get() != nullptr);
  212. gfx::ImageSkia default_icon = GetDefaultIcon();
  213. // Load images we expect to find as representations in icon_image, so we
  214. // can later use them to validate icon_image.
  215. SkBitmap bitmap_24 =
  216. TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "24.png", 24);
  217. ASSERT_FALSE(bitmap_24.empty());
  218. IconImage image(browser_context(),
  219. extension.get(),
  220. IconsInfo::GetIcons(extension.get()),
  221. 17,
  222. default_icon,
  223. this);
  224. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  225. WaitForImageLoad();
  226. EXPECT_EQ(1, ImageLoadedCount());
  227. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  228. representation = image.image_skia().GetRepresentation(1.0f);
  229. // We should have loaded the smallest bigger (resized) resource.
  230. EXPECT_EQ(1.0f, representation.scale());
  231. EXPECT_EQ(17, representation.pixel_width());
  232. EXPECT_TRUE(gfx::BitmapsAreEqual(representation.GetBitmap(),
  233. EnsureBitmapSize(bitmap_24, 17)));
  234. }
  235. // If resource set is empty, |GetRepresentation| should synchronously return
  236. // default icon, without notifying observer of image change.
  237. TEST_F(ExtensionIconImageTest, NoResources) {
  238. scoped_refptr<Extension> extension(CreateExtension(
  239. "extension_icon_image", ManifestLocation::kInvalidLocation));
  240. ASSERT_TRUE(extension.get() != nullptr);
  241. ExtensionIconSet empty_icon_set;
  242. gfx::ImageSkia default_icon = GetDefaultIcon();
  243. const int kRequestedSize = 24;
  244. IconImage image(browser_context(),
  245. extension.get(),
  246. empty_icon_set,
  247. kRequestedSize,
  248. default_icon,
  249. this);
  250. // Default icon is loaded asynchronously.
  251. image.image_skia().GetRepresentation(1.0f);
  252. base::RunLoop().RunUntilIdle();
  253. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  254. EXPECT_TRUE(gfx::BitmapsAreEqual(
  255. representation.GetBitmap(),
  256. EnsureBitmapSize(default_icon.GetRepresentation(1.0f).GetBitmap(),
  257. kRequestedSize)));
  258. EXPECT_EQ(1, ImageLoadedCount());
  259. // We should have a default icon representation.
  260. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  261. representation = image.image_skia().GetRepresentation(1.0f);
  262. EXPECT_TRUE(gfx::BitmapsAreEqual(
  263. representation.GetBitmap(),
  264. EnsureBitmapSize(default_icon.GetRepresentation(1.0f).GetBitmap(),
  265. kRequestedSize)));
  266. }
  267. // If resource set is invalid, image load should be done asynchronously and
  268. // the observer should be notified when it's done. |GetRepresentation| should
  269. // return the default icon representation once image load is done.
  270. TEST_F(ExtensionIconImageTest, InvalidResource) {
  271. scoped_refptr<Extension> extension(CreateExtension(
  272. "extension_icon_image", ManifestLocation::kInvalidLocation));
  273. ASSERT_TRUE(extension.get() != nullptr);
  274. const int kInvalidIconSize = 24;
  275. ExtensionIconSet invalid_icon_set;
  276. invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
  277. gfx::ImageSkia default_icon = GetDefaultIcon();
  278. IconImage image(browser_context(),
  279. extension.get(),
  280. invalid_icon_set,
  281. kInvalidIconSize,
  282. default_icon,
  283. this);
  284. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  285. EXPECT_TRUE(gfx::BitmapsAreEqual(
  286. representation.GetBitmap(),
  287. CreateBlankBitmapForScale(kInvalidIconSize, ui::k100Percent)));
  288. WaitForImageLoad();
  289. EXPECT_EQ(1, ImageLoadedCount());
  290. // We should have default icon representation now.
  291. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  292. representation = image.image_skia().GetRepresentation(1.0f);
  293. EXPECT_TRUE(gfx::BitmapsAreEqual(
  294. representation.GetBitmap(),
  295. EnsureBitmapSize(default_icon.GetRepresentation(1.0f).GetBitmap(),
  296. kInvalidIconSize)));
  297. }
  298. // Test that IconImage works with lazily (but synchronously) created default
  299. // icon when IconImage returns synchronously.
  300. TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
  301. scoped_refptr<Extension> extension(CreateExtension(
  302. "extension_icon_image", ManifestLocation::kInvalidLocation));
  303. ASSERT_TRUE(extension.get() != nullptr);
  304. gfx::ImageSkia default_icon = GetDefaultIcon();
  305. gfx::ImageSkia lazy_default_icon(
  306. std::make_unique<MockImageSkiaSource>(default_icon), default_icon.size());
  307. ExtensionIconSet empty_icon_set;
  308. const int kRequestedSize = 128;
  309. IconImage image(browser_context(),
  310. extension.get(),
  311. empty_icon_set,
  312. kRequestedSize,
  313. lazy_default_icon,
  314. this);
  315. ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
  316. // Default icon is loaded asynchronously.
  317. image.image_skia().GetRepresentation(1.0f);
  318. base::RunLoop().RunUntilIdle();
  319. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  320. // The resouce set is empty, so we should get the result right away.
  321. EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
  322. EXPECT_TRUE(gfx::BitmapsAreEqual(
  323. representation.GetBitmap(),
  324. EnsureBitmapSize(default_icon.GetRepresentation(1.0f).GetBitmap(),
  325. kRequestedSize)));
  326. // We should have a default icon representation.
  327. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  328. }
  329. // Test that IconImage works with lazily (but synchronously) created default
  330. // icon when IconImage returns asynchronously.
  331. TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
  332. scoped_refptr<Extension> extension(CreateExtension(
  333. "extension_icon_image", ManifestLocation::kInvalidLocation));
  334. ASSERT_TRUE(extension.get() != nullptr);
  335. gfx::ImageSkia default_icon = GetDefaultIcon();
  336. gfx::ImageSkia lazy_default_icon(
  337. std::make_unique<MockImageSkiaSource>(default_icon), default_icon.size());
  338. const int kInvalidIconSize = 24;
  339. ExtensionIconSet invalid_icon_set;
  340. invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
  341. IconImage image(browser_context(),
  342. extension.get(),
  343. invalid_icon_set,
  344. kInvalidIconSize,
  345. lazy_default_icon,
  346. this);
  347. ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
  348. gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  349. WaitForImageLoad();
  350. EXPECT_EQ(1, ImageLoadedCount());
  351. // We should have default icon representation now.
  352. ASSERT_EQ(1u, image.image_skia().image_reps().size());
  353. EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
  354. representation = image.image_skia().GetRepresentation(1.0f);
  355. EXPECT_TRUE(gfx::BitmapsAreEqual(
  356. representation.GetBitmap(),
  357. EnsureBitmapSize(default_icon.GetRepresentation(1.0f).GetBitmap(),
  358. kInvalidIconSize)));
  359. }
  360. // Tests behavior of image created by IconImage after IconImage host goes
  361. // away. The image should still return loaded representations. If requested
  362. // representation was not loaded while IconImage host was around, transparent
  363. // representations should be returned.
  364. TEST_F(ExtensionIconImageTest, IconImageDestruction) {
  365. scoped_refptr<Extension> extension(CreateExtension(
  366. "extension_icon_image", ManifestLocation::kInvalidLocation));
  367. ASSERT_TRUE(extension.get() != nullptr);
  368. gfx::ImageSkia default_icon = GetDefaultIcon();
  369. // Load images we expect to find as representations in icon_image, so we
  370. // can later use them to validate icon_image.
  371. SkBitmap bitmap_16 =
  372. TestImageLoader::LoadAndGetExtensionBitmap(extension.get(), "16.png", 16);
  373. ASSERT_FALSE(bitmap_16.empty());
  374. std::unique_ptr<IconImage> image(new IconImage(
  375. browser_context(), extension.get(), IconsInfo::GetIcons(extension.get()),
  376. 16, default_icon, this));
  377. // Load an image representation.
  378. gfx::ImageSkiaRep representation =
  379. image->image_skia().GetRepresentation(1.0f);
  380. WaitForImageLoad();
  381. EXPECT_EQ(1, ImageLoadedCount());
  382. ASSERT_EQ(1u, image->image_skia().image_reps().size());
  383. // Stash loaded image skia, and destroy |image|.
  384. gfx::ImageSkia image_skia = image->image_skia();
  385. image.reset();
  386. extension = nullptr;
  387. // Image skia should still be able to get previously loaded representation.
  388. representation = image_skia.GetRepresentation(1.0f);
  389. EXPECT_EQ(1.0f, representation.scale());
  390. EXPECT_EQ(16, representation.pixel_width());
  391. EXPECT_TRUE(gfx::BitmapsAreEqual(representation.GetBitmap(), bitmap_16));
  392. // When requesting another representation, we should not crash and return some
  393. // image of the size. It could be blank or a rescale from the existing 1.0f
  394. // icon.
  395. representation = image_skia.GetRepresentation(2.0f);
  396. EXPECT_EQ(16, representation.GetWidth());
  397. EXPECT_EQ(16, representation.GetHeight());
  398. EXPECT_EQ(2.0f, representation.scale());
  399. }
  400. // Test that new representations added to the image of an IconImageSkia are
  401. // cached for future use.
  402. TEST_F(ExtensionIconImageTest, ImageCachesNewRepresentations) {
  403. // Load up an extension and create an icon image.
  404. scoped_refptr<Extension> extension(CreateExtension(
  405. "extension_icon_image", ManifestLocation::kInvalidLocation));
  406. ASSERT_TRUE(extension.get() != nullptr);
  407. gfx::ImageSkia default_icon = GetDefaultIcon();
  408. std::unique_ptr<IconImage> icon_image(new IconImage(
  409. browser_context(), extension.get(), IconsInfo::GetIcons(extension.get()),
  410. 16, default_icon, this));
  411. // Load an blank image representation.
  412. EXPECT_EQ(0, ImageLoadedCount());
  413. icon_image->image_skia().GetRepresentation(1.0f);
  414. EXPECT_EQ(0, ImageLoadedCount());
  415. WaitForImageLoad();
  416. EXPECT_EQ(1, ImageLoadedCount());
  417. icon_image->image_skia().GetRepresentation(1.0f);
  418. base::RunLoop().RunUntilIdle();
  419. EXPECT_EQ(0, ImageLoadedCount());
  420. icon_image->image_skia().GetRepresentation(1.0f);
  421. base::RunLoop().RunUntilIdle();
  422. EXPECT_EQ(0, ImageLoadedCount());
  423. }
  424. } // namespace extensions