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.
5 #include "extensions/browser/extension_icon_image.h"
9 #include "base/json/json_file_value_serializer.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/path_service.h"
12 #include "content/public/browser/notification_service.h"
13 #include "content/public/test/test_browser_context.h"
14 #include "content/public/test/test_browser_thread.h"
15 #include "extensions/browser/extensions_test.h"
16 #include "extensions/browser/image_loader.h"
17 #include "extensions/browser/test_image_loader.h"
18 #include "extensions/common/extension.h"
19 #include "extensions/common/extension_paths.h"
20 #include "extensions/common/manifest.h"
21 #include "extensions/common/manifest_handlers/icons_handler.h"
22 #include "skia/ext/image_operations.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "ui/base/resource/resource_bundle.h"
25 #include "ui/gfx/image/image_skia_source.h"
26 #include "ui/gfx/skia_util.h"
28 using content::BrowserThread
;
30 namespace extensions
{
33 SkBitmap
CreateBlankBitmapForScale(int size_dip
, ui::ScaleFactor scale_factor
) {
35 const float scale
= ui::GetScaleForScaleFactor(scale_factor
);
36 bitmap
.allocN32Pixels(static_cast<int>(size_dip
* scale
),
37 static_cast<int>(size_dip
* scale
));
38 bitmap
.eraseColor(SkColorSetARGB(0, 0, 0, 0));
42 SkBitmap
EnsureBitmapSize(const SkBitmap
& original
, int size
) {
43 if (original
.width() == size
&& original
.height() == size
)
46 SkBitmap resized
= skia::ImageOperations::Resize(
47 original
, skia::ImageOperations::RESIZE_LANCZOS3
, size
, size
);
51 // Used to test behavior including images defined by an image skia source.
52 // |GetImageForScale| simply returns image representation from the image given
54 class MockImageSkiaSource
: public gfx::ImageSkiaSource
{
56 explicit MockImageSkiaSource(const gfx::ImageSkia
& image
)
59 ~MockImageSkiaSource() override
{}
61 gfx::ImageSkiaRep
GetImageForScale(float scale
) override
{
62 return image_
.GetRepresentation(scale
);
66 gfx::ImageSkia image_
;
69 class ExtensionIconImageTest
: public ExtensionsTest
,
70 public IconImage::Observer
{
72 ExtensionIconImageTest()
73 : image_loaded_count_(0),
74 quit_in_image_loaded_(false),
75 ui_thread_(BrowserThread::UI
, &ui_loop_
),
76 file_thread_(BrowserThread::FILE),
77 io_thread_(BrowserThread::IO
),
78 notification_service_(content::NotificationService::Create()) {}
80 ~ExtensionIconImageTest() override
{}
82 void WaitForImageLoad() {
83 quit_in_image_loaded_
= true;
84 base::MessageLoop::current()->Run();
85 quit_in_image_loaded_
= false;
88 int ImageLoadedCount() {
89 int result
= image_loaded_count_
;
90 image_loaded_count_
= 0;
94 scoped_refptr
<Extension
> CreateExtension(const char* name
,
95 Manifest::Location location
) {
96 // Create and load an extension.
97 base::FilePath test_file
;
98 if (!PathService::Get(DIR_TEST_DATA
, &test_file
)) {
102 test_file
= test_file
.AppendASCII(name
);
105 JSONFileValueDeserializer
deserializer(
106 test_file
.AppendASCII("manifest.json"));
107 scoped_ptr
<base::DictionaryValue
> valid_value(
108 static_cast<base::DictionaryValue
*>(
109 deserializer
.Deserialize(&error_code
, &error
)));
110 EXPECT_EQ(0, error_code
) << error
;
114 EXPECT_TRUE(valid_value
.get());
118 return Extension::Create(test_file
, location
, *valid_value
,
119 Extension::NO_FLAGS
, &error
);
122 // testing::Test overrides:
123 void SetUp() override
{
124 file_thread_
.Start();
128 // IconImage::Delegate overrides:
129 void OnExtensionIconImageChanged(IconImage
* image
) override
{
130 image_loaded_count_
++;
131 if (quit_in_image_loaded_
)
132 base::MessageLoop::current()->Quit();
135 gfx::ImageSkia
GetDefaultIcon() {
136 return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f
));
140 int image_loaded_count_
;
141 bool quit_in_image_loaded_
;
142 base::MessageLoop ui_loop_
;
143 content::TestBrowserThread ui_thread_
;
144 content::TestBrowserThread file_thread_
;
145 content::TestBrowserThread io_thread_
;
146 scoped_ptr
<content::NotificationService
> notification_service_
;
148 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest
);
153 TEST_F(ExtensionIconImageTest
, Basic
) {
154 std::vector
<ui::ScaleFactor
> supported_factors
;
155 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
156 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
157 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
158 scoped_refptr
<Extension
> extension(CreateExtension(
159 "extension_icon_image", Manifest::INVALID_LOCATION
));
160 ASSERT_TRUE(extension
.get() != NULL
);
162 gfx::ImageSkia default_icon
= GetDefaultIcon();
164 // Load images we expect to find as representations in icon_image, so we
165 // can later use them to validate icon_image.
167 TestImageLoader::LoadAndGetExtensionBitmap(extension
.get(), "16.png", 16);
168 ASSERT_FALSE(bitmap_16
.empty());
170 // There is no image of size 32 defined in the extension manifest, so we
171 // should expect manifest image of size 48 resized to size 32.
172 SkBitmap bitmap_48_resized_to_32
=
173 TestImageLoader::LoadAndGetExtensionBitmap(extension
.get(), "48.png", 32);
174 ASSERT_FALSE(bitmap_48_resized_to_32
.empty());
176 IconImage
image(browser_context(),
178 IconsInfo::GetIcons(extension
.get()),
183 // No representations in |image_| yet.
184 gfx::ImageSkia::ImageSkiaReps image_reps
= image
.image_skia().image_reps();
185 ASSERT_EQ(0u, image_reps
.size());
187 // Gets representation for a scale factor.
188 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
190 // Before the image representation is loaded, image should contain blank
191 // image representation.
192 EXPECT_TRUE(gfx::BitmapsAreEqual(
193 representation
.sk_bitmap(),
194 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P
)));
197 EXPECT_EQ(1, ImageLoadedCount());
198 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
200 representation
= image
.image_skia().GetRepresentation(1.0f
);
202 // We should get the right representation now.
203 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
204 EXPECT_EQ(16, representation
.pixel_width());
206 // Gets representation for an additional scale factor.
207 representation
= image
.image_skia().GetRepresentation(2.0f
);
209 EXPECT_TRUE(gfx::BitmapsAreEqual(
210 representation
.sk_bitmap(),
211 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));
214 EXPECT_EQ(1, ImageLoadedCount());
215 ASSERT_EQ(2u, image
.image_skia().image_reps().size());
217 representation
= image
.image_skia().GetRepresentation(2.0f
);
219 // Image should have been resized.
220 EXPECT_EQ(32, representation
.pixel_width());
221 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
222 bitmap_48_resized_to_32
));
225 // There is no resource with either exact or bigger size, but there is a smaller
227 TEST_F(ExtensionIconImageTest
, FallbackToSmallerWhenNoBigger
) {
228 std::vector
<ui::ScaleFactor
> supported_factors
;
229 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
230 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
231 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
232 scoped_refptr
<Extension
> extension(CreateExtension(
233 "extension_icon_image", Manifest::INVALID_LOCATION
));
234 ASSERT_TRUE(extension
.get() != NULL
);
236 gfx::ImageSkia default_icon
= GetDefaultIcon();
238 // Load images we expect to find as representations in icon_image, so we
239 // can later use them to validate icon_image.
241 TestImageLoader::LoadAndGetExtensionBitmap(extension
.get(), "48.png", 48);
242 ASSERT_FALSE(bitmap_48
.empty());
244 IconImage
image(browser_context(),
246 IconsInfo::GetIcons(extension
.get()),
251 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(2.0f
);
254 EXPECT_EQ(1, ImageLoadedCount());
255 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
257 representation
= image
.image_skia().GetRepresentation(2.0f
);
259 // We should have loaded the biggest smaller resource resized to the actual
261 EXPECT_EQ(2.0f
, representation
.scale());
262 EXPECT_EQ(64, representation
.pixel_width());
263 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
264 EnsureBitmapSize(bitmap_48
, 64)));
267 // There is no resource with exact size, but there is a smaller and a bigger
268 // one. The bigger resource should be loaded.
269 TEST_F(ExtensionIconImageTest
, FallbackToBigger
) {
270 scoped_refptr
<Extension
> extension(CreateExtension(
271 "extension_icon_image", Manifest::INVALID_LOCATION
));
272 ASSERT_TRUE(extension
.get() != NULL
);
274 gfx::ImageSkia default_icon
= GetDefaultIcon();
276 // Load images we expect to find as representations in icon_image, so we
277 // can later use them to validate icon_image.
279 TestImageLoader::LoadAndGetExtensionBitmap(extension
.get(), "24.png", 24);
280 ASSERT_FALSE(bitmap_24
.empty());
282 IconImage
image(browser_context(),
284 IconsInfo::GetIcons(extension
.get()),
289 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
292 EXPECT_EQ(1, ImageLoadedCount());
293 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
295 representation
= image
.image_skia().GetRepresentation(1.0f
);
297 // We should have loaded the smallest bigger (resized) resource.
298 EXPECT_EQ(1.0f
, representation
.scale());
299 EXPECT_EQ(17, representation
.pixel_width());
300 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
301 EnsureBitmapSize(bitmap_24
, 17)));
304 // If resource set is empty, |GetRepresentation| should synchronously return
305 // default icon, without notifying observer of image change.
306 TEST_F(ExtensionIconImageTest
, NoResources
) {
307 scoped_refptr
<Extension
> extension(CreateExtension(
308 "extension_icon_image", Manifest::INVALID_LOCATION
));
309 ASSERT_TRUE(extension
.get() != NULL
);
311 ExtensionIconSet empty_icon_set
;
312 gfx::ImageSkia default_icon
= GetDefaultIcon();
314 const int kRequestedSize
= 24;
315 IconImage
image(browser_context(),
322 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
323 EXPECT_TRUE(gfx::BitmapsAreEqual(
324 representation
.sk_bitmap(),
326 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
329 EXPECT_EQ(0, ImageLoadedCount());
330 // We should have a default icon representation.
331 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
333 representation
= image
.image_skia().GetRepresentation(1.0f
);
334 EXPECT_TRUE(gfx::BitmapsAreEqual(
335 representation
.sk_bitmap(),
337 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
341 // If resource set is invalid, image load should be done asynchronously and
342 // the observer should be notified when it's done. |GetRepresentation| should
343 // return the default icon representation once image load is done.
344 TEST_F(ExtensionIconImageTest
, InvalidResource
) {
345 scoped_refptr
<Extension
> extension(CreateExtension(
346 "extension_icon_image", Manifest::INVALID_LOCATION
));
347 ASSERT_TRUE(extension
.get() != NULL
);
349 const int kInvalidIconSize
= 24;
350 ExtensionIconSet invalid_icon_set
;
351 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
353 gfx::ImageSkia default_icon
= GetDefaultIcon();
355 IconImage
image(browser_context(),
362 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
363 EXPECT_TRUE(gfx::BitmapsAreEqual(
364 representation
.sk_bitmap(),
365 CreateBlankBitmapForScale(kInvalidIconSize
, ui::SCALE_FACTOR_100P
)));
368 EXPECT_EQ(1, ImageLoadedCount());
369 // We should have default icon representation now.
370 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
372 representation
= image
.image_skia().GetRepresentation(1.0f
);
373 EXPECT_TRUE(gfx::BitmapsAreEqual(
374 representation
.sk_bitmap(),
376 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
380 // Test that IconImage works with lazily (but synchronously) created default
381 // icon when IconImage returns synchronously.
382 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon
) {
383 scoped_refptr
<Extension
> extension(CreateExtension(
384 "extension_icon_image", Manifest::INVALID_LOCATION
));
385 ASSERT_TRUE(extension
.get() != NULL
);
387 gfx::ImageSkia default_icon
= GetDefaultIcon();
388 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
389 default_icon
.size());
391 ExtensionIconSet empty_icon_set
;
393 const int kRequestedSize
= 128;
394 IconImage
image(browser_context(),
401 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
403 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
405 // The resouce set is empty, so we should get the result right away.
406 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
407 EXPECT_TRUE(gfx::BitmapsAreEqual(
408 representation
.sk_bitmap(),
410 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
413 // We should have a default icon representation.
414 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
417 // Test that IconImage works with lazily (but synchronously) created default
418 // icon when IconImage returns asynchronously.
419 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon_AsyncIconImage
) {
420 scoped_refptr
<Extension
> extension(CreateExtension(
421 "extension_icon_image", Manifest::INVALID_LOCATION
));
422 ASSERT_TRUE(extension
.get() != NULL
);
424 gfx::ImageSkia default_icon
= GetDefaultIcon();
425 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
426 default_icon
.size());
428 const int kInvalidIconSize
= 24;
429 ExtensionIconSet invalid_icon_set
;
430 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
432 IconImage
image(browser_context(),
439 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
441 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
444 EXPECT_EQ(1, ImageLoadedCount());
445 // We should have default icon representation now.
446 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
448 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
450 representation
= image
.image_skia().GetRepresentation(1.0f
);
451 EXPECT_TRUE(gfx::BitmapsAreEqual(
452 representation
.sk_bitmap(),
454 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
458 // Tests behavior of image created by IconImage after IconImage host goes
459 // away. The image should still return loaded representations. If requested
460 // representation was not loaded while IconImage host was around, transparent
461 // representations should be returned.
462 TEST_F(ExtensionIconImageTest
, IconImageDestruction
) {
463 scoped_refptr
<Extension
> extension(CreateExtension(
464 "extension_icon_image", Manifest::INVALID_LOCATION
));
465 ASSERT_TRUE(extension
.get() != NULL
);
467 gfx::ImageSkia default_icon
= GetDefaultIcon();
469 // Load images we expect to find as representations in icon_image, so we
470 // can later use them to validate icon_image.
472 TestImageLoader::LoadAndGetExtensionBitmap(extension
.get(), "16.png", 16);
473 ASSERT_FALSE(bitmap_16
.empty());
475 scoped_ptr
<IconImage
> image(
476 new IconImage(browser_context(),
478 IconsInfo::GetIcons(extension
.get()),
483 // Load an image representation.
484 gfx::ImageSkiaRep representation
=
485 image
->image_skia().GetRepresentation(1.0f
);
488 EXPECT_EQ(1, ImageLoadedCount());
489 ASSERT_EQ(1u, image
->image_skia().image_reps().size());
491 // Stash loaded image skia, and destroy |image|.
492 gfx::ImageSkia image_skia
= image
->image_skia();
496 // Image skia should still be able to get previously loaded representation.
497 representation
= image_skia
.GetRepresentation(1.0f
);
499 EXPECT_EQ(1.0f
, representation
.scale());
500 EXPECT_EQ(16, representation
.pixel_width());
501 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
503 // When requesting another representation, we should not crash and return some
504 // image of the size. It could be blank or a rescale from the existing 1.0f
506 representation
= image_skia
.GetRepresentation(2.0f
);
508 EXPECT_EQ(16, representation
.GetWidth());
509 EXPECT_EQ(16, representation
.GetHeight());
510 EXPECT_EQ(2.0f
, representation
.scale());
513 // Test that new representations added to the image of an IconImage are cached
515 TEST_F(ExtensionIconImageTest
, ImageCachesNewRepresentations
) {
516 // Load up an extension and create an icon image.
517 scoped_refptr
<Extension
> extension(
518 CreateExtension("extension_icon_image", Manifest::INVALID_LOCATION
));
519 ASSERT_TRUE(extension
.get() != NULL
);
520 gfx::ImageSkia default_icon
= GetDefaultIcon();
521 scoped_ptr
<IconImage
> icon_image(
522 new IconImage(browser_context(),
524 IconsInfo::GetIcons(extension
.get()),
529 // Load an image representation.
530 gfx::ImageSkiaRep representation
=
531 icon_image
->image_skia().GetRepresentation(1.0f
);
534 // Cache for later use.
535 gfx::Image prior_image
= icon_image
->image();
537 // Now the fun part: access the image from the IconImage, and create a png
538 // representation of it.
539 gfx::Image image
= icon_image
->image();
540 scoped_refptr
<base::RefCountedMemory
> image_as_png
= image
.As1xPNGBytes();
542 // Access the image from the IconImage again, and get a png representation.
543 // The two png representations should be exactly equal (the same object),
544 // because image storage is shared, so when we created one from the first
545 // image, all other images should also have that representation...
546 gfx::Image image2
= icon_image
->image();
547 EXPECT_EQ(image_as_png
.get(), image2
.As1xPNGBytes().get());
549 // ...even images that were copied before the representation was constructed.
550 EXPECT_EQ(image_as_png
.get(), prior_image
.As1xPNGBytes().get());
553 } // namespace extensions