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/common/extension.h"
18 #include "extensions/common/extension_paths.h"
19 #include "extensions/common/manifest.h"
20 #include "extensions/common/manifest_handlers/icons_handler.h"
21 #include "skia/ext/image_operations.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/image/image_skia_source.h"
25 #include "ui/gfx/skia_util.h"
27 using content::BrowserThread
;
29 namespace extensions
{
32 SkBitmap
CreateBlankBitmapForScale(int size_dip
, ui::ScaleFactor scale_factor
) {
34 const float scale
= ui::GetScaleForScaleFactor(scale_factor
);
35 bitmap
.allocN32Pixels(static_cast<int>(size_dip
* scale
),
36 static_cast<int>(size_dip
* scale
));
37 bitmap
.eraseColor(SkColorSetARGB(0, 0, 0, 0));
41 SkBitmap
EnsureBitmapSize(const SkBitmap
& original
, int size
) {
42 if (original
.width() == size
&& original
.height() == size
)
45 SkBitmap resized
= skia::ImageOperations::Resize(
46 original
, skia::ImageOperations::RESIZE_LANCZOS3
, size
, size
);
50 // Used to test behavior including images defined by an image skia source.
51 // |GetImageForScale| simply returns image representation from the image given
53 class MockImageSkiaSource
: public gfx::ImageSkiaSource
{
55 explicit MockImageSkiaSource(const gfx::ImageSkia
& image
)
58 ~MockImageSkiaSource() override
{}
60 gfx::ImageSkiaRep
GetImageForScale(float scale
) override
{
61 return image_
.GetRepresentation(scale
);
65 gfx::ImageSkia image_
;
68 // Helper class for synchronously loading extension image resource.
69 class TestImageLoader
{
71 explicit TestImageLoader(const Extension
* extension
)
72 : extension_(extension
),
74 image_loaded_(false) {
76 virtual ~TestImageLoader() {}
78 void OnImageLoaded(const gfx::Image
& image
) {
82 base::MessageLoop::current()->Quit();
85 SkBitmap
LoadBitmap(const std::string
& path
,
87 image_loaded_
= false;
89 image_loader_
.LoadImageAsync(
90 extension_
, extension_
->GetResource(path
), gfx::Size(size
, size
),
91 base::Bind(&TestImageLoader::OnImageLoaded
,
92 base::Unretained(this)));
94 // If |image_| still hasn't been loaded (i.e. it is being loaded
95 // asynchronously), wait for it.
98 base::MessageLoop::current()->Run();
102 EXPECT_TRUE(image_loaded_
);
104 return image_
.IsEmpty() ? SkBitmap() : *image_
.ToSkBitmap();
108 const Extension
* extension_
;
112 ImageLoader image_loader_
;
114 DISALLOW_COPY_AND_ASSIGN(TestImageLoader
);
117 class ExtensionIconImageTest
: public ExtensionsTest
,
118 public IconImage::Observer
{
120 ExtensionIconImageTest()
121 : image_loaded_count_(0),
122 quit_in_image_loaded_(false),
123 ui_thread_(BrowserThread::UI
, &ui_loop_
),
124 file_thread_(BrowserThread::FILE),
125 io_thread_(BrowserThread::IO
),
126 notification_service_(content::NotificationService::Create()) {}
128 ~ExtensionIconImageTest() override
{}
130 void WaitForImageLoad() {
131 quit_in_image_loaded_
= true;
132 base::MessageLoop::current()->Run();
133 quit_in_image_loaded_
= false;
136 int ImageLoadedCount() {
137 int result
= image_loaded_count_
;
138 image_loaded_count_
= 0;
142 scoped_refptr
<Extension
> CreateExtension(const char* name
,
143 Manifest::Location location
) {
144 // Create and load an extension.
145 base::FilePath test_file
;
146 if (!PathService::Get(DIR_TEST_DATA
, &test_file
)) {
150 test_file
= test_file
.AppendASCII(name
);
153 JSONFileValueSerializer
serializer(test_file
.AppendASCII("manifest.json"));
154 scoped_ptr
<base::DictionaryValue
> valid_value(
155 static_cast<base::DictionaryValue
*>(serializer
.Deserialize(&error_code
,
157 EXPECT_EQ(0, error_code
) << error
;
161 EXPECT_TRUE(valid_value
.get());
165 return Extension::Create(test_file
, location
, *valid_value
,
166 Extension::NO_FLAGS
, &error
);
169 // testing::Test overrides:
170 void SetUp() override
{
171 file_thread_
.Start();
175 // IconImage::Delegate overrides:
176 void OnExtensionIconImageChanged(IconImage
* image
) override
{
177 image_loaded_count_
++;
178 if (quit_in_image_loaded_
)
179 base::MessageLoop::current()->Quit();
182 gfx::ImageSkia
GetDefaultIcon() {
183 return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f
));
186 // Loads an image to be used in test from the extension.
187 // The image will be loaded from the relative path |path|.
188 SkBitmap
GetTestBitmap(const Extension
* extension
,
189 const std::string
& path
,
191 TestImageLoader
image_loader(extension
);
192 return image_loader
.LoadBitmap(path
, size
);
196 int image_loaded_count_
;
197 bool quit_in_image_loaded_
;
198 base::MessageLoop ui_loop_
;
199 content::TestBrowserThread ui_thread_
;
200 content::TestBrowserThread file_thread_
;
201 content::TestBrowserThread io_thread_
;
202 scoped_ptr
<content::NotificationService
> notification_service_
;
204 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest
);
209 TEST_F(ExtensionIconImageTest
, Basic
) {
210 std::vector
<ui::ScaleFactor
> supported_factors
;
211 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
212 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
213 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
214 scoped_refptr
<Extension
> extension(CreateExtension(
215 "extension_icon_image", Manifest::INVALID_LOCATION
));
216 ASSERT_TRUE(extension
.get() != NULL
);
218 gfx::ImageSkia default_icon
= GetDefaultIcon();
220 // Load images we expect to find as representations in icon_image, so we
221 // can later use them to validate icon_image.
222 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
223 ASSERT_FALSE(bitmap_16
.empty());
225 // There is no image of size 32 defined in the extension manifest, so we
226 // should expect manifest image of size 48 resized to size 32.
227 SkBitmap bitmap_48_resized_to_32
=
228 GetTestBitmap(extension
.get(), "48.png", 32);
229 ASSERT_FALSE(bitmap_48_resized_to_32
.empty());
231 IconImage
image(browser_context(),
233 IconsInfo::GetIcons(extension
.get()),
238 // No representations in |image_| yet.
239 gfx::ImageSkia::ImageSkiaReps image_reps
= image
.image_skia().image_reps();
240 ASSERT_EQ(0u, image_reps
.size());
242 // Gets representation for a scale factor.
243 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
245 // Before the image representation is loaded, image should contain blank
246 // image representation.
247 EXPECT_TRUE(gfx::BitmapsAreEqual(
248 representation
.sk_bitmap(),
249 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P
)));
252 EXPECT_EQ(1, ImageLoadedCount());
253 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
255 representation
= image
.image_skia().GetRepresentation(1.0f
);
257 // We should get the right representation now.
258 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
259 EXPECT_EQ(16, representation
.pixel_width());
261 // Gets representation for an additional scale factor.
262 representation
= image
.image_skia().GetRepresentation(2.0f
);
264 EXPECT_TRUE(gfx::BitmapsAreEqual(
265 representation
.sk_bitmap(),
266 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));
269 EXPECT_EQ(1, ImageLoadedCount());
270 ASSERT_EQ(2u, image
.image_skia().image_reps().size());
272 representation
= image
.image_skia().GetRepresentation(2.0f
);
274 // Image should have been resized.
275 EXPECT_EQ(32, representation
.pixel_width());
276 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
277 bitmap_48_resized_to_32
));
280 // There is no resource with either exact or bigger size, but there is a smaller
282 TEST_F(ExtensionIconImageTest
, FallbackToSmallerWhenNoBigger
) {
283 std::vector
<ui::ScaleFactor
> supported_factors
;
284 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
285 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
286 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
287 scoped_refptr
<Extension
> extension(CreateExtension(
288 "extension_icon_image", Manifest::INVALID_LOCATION
));
289 ASSERT_TRUE(extension
.get() != NULL
);
291 gfx::ImageSkia default_icon
= GetDefaultIcon();
293 // Load images we expect to find as representations in icon_image, so we
294 // can later use them to validate icon_image.
295 SkBitmap bitmap_48
= GetTestBitmap(extension
.get(), "48.png", 48);
296 ASSERT_FALSE(bitmap_48
.empty());
298 IconImage
image(browser_context(),
300 IconsInfo::GetIcons(extension
.get()),
305 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(2.0f
);
308 EXPECT_EQ(1, ImageLoadedCount());
309 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
311 representation
= image
.image_skia().GetRepresentation(2.0f
);
313 // We should have loaded the biggest smaller resource resized to the actual
315 EXPECT_EQ(2.0f
, representation
.scale());
316 EXPECT_EQ(64, representation
.pixel_width());
317 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
318 EnsureBitmapSize(bitmap_48
, 64)));
321 // There is no resource with exact size, but there is a smaller and a bigger
322 // one. The bigger resource should be loaded.
323 TEST_F(ExtensionIconImageTest
, FallbackToBigger
) {
324 scoped_refptr
<Extension
> extension(CreateExtension(
325 "extension_icon_image", Manifest::INVALID_LOCATION
));
326 ASSERT_TRUE(extension
.get() != NULL
);
328 gfx::ImageSkia default_icon
= GetDefaultIcon();
330 // Load images we expect to find as representations in icon_image, so we
331 // can later use them to validate icon_image.
332 SkBitmap bitmap_24
= GetTestBitmap(extension
.get(), "24.png", 24);
333 ASSERT_FALSE(bitmap_24
.empty());
335 IconImage
image(browser_context(),
337 IconsInfo::GetIcons(extension
.get()),
342 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
345 EXPECT_EQ(1, ImageLoadedCount());
346 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
348 representation
= image
.image_skia().GetRepresentation(1.0f
);
350 // We should have loaded the smallest bigger (resized) resource.
351 EXPECT_EQ(1.0f
, representation
.scale());
352 EXPECT_EQ(17, representation
.pixel_width());
353 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
354 EnsureBitmapSize(bitmap_24
, 17)));
357 // If resource set is empty, |GetRepresentation| should synchronously return
358 // default icon, without notifying observer of image change.
359 TEST_F(ExtensionIconImageTest
, NoResources
) {
360 scoped_refptr
<Extension
> extension(CreateExtension(
361 "extension_icon_image", Manifest::INVALID_LOCATION
));
362 ASSERT_TRUE(extension
.get() != NULL
);
364 ExtensionIconSet empty_icon_set
;
365 gfx::ImageSkia default_icon
= GetDefaultIcon();
367 const int kRequestedSize
= 24;
368 IconImage
image(browser_context(),
375 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
376 EXPECT_TRUE(gfx::BitmapsAreEqual(
377 representation
.sk_bitmap(),
379 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
382 EXPECT_EQ(0, ImageLoadedCount());
383 // We should have a default icon representation.
384 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
386 representation
= image
.image_skia().GetRepresentation(1.0f
);
387 EXPECT_TRUE(gfx::BitmapsAreEqual(
388 representation
.sk_bitmap(),
390 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
394 // If resource set is invalid, image load should be done asynchronously and
395 // the observer should be notified when it's done. |GetRepresentation| should
396 // return the default icon representation once image load is done.
397 TEST_F(ExtensionIconImageTest
, InvalidResource
) {
398 scoped_refptr
<Extension
> extension(CreateExtension(
399 "extension_icon_image", Manifest::INVALID_LOCATION
));
400 ASSERT_TRUE(extension
.get() != NULL
);
402 const int kInvalidIconSize
= 24;
403 ExtensionIconSet invalid_icon_set
;
404 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
406 gfx::ImageSkia default_icon
= GetDefaultIcon();
408 IconImage
image(browser_context(),
415 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
416 EXPECT_TRUE(gfx::BitmapsAreEqual(
417 representation
.sk_bitmap(),
418 CreateBlankBitmapForScale(kInvalidIconSize
, ui::SCALE_FACTOR_100P
)));
421 EXPECT_EQ(1, ImageLoadedCount());
422 // We should have default icon representation now.
423 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
425 representation
= image
.image_skia().GetRepresentation(1.0f
);
426 EXPECT_TRUE(gfx::BitmapsAreEqual(
427 representation
.sk_bitmap(),
429 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
433 // Test that IconImage works with lazily (but synchronously) created default
434 // icon when IconImage returns synchronously.
435 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon
) {
436 scoped_refptr
<Extension
> extension(CreateExtension(
437 "extension_icon_image", Manifest::INVALID_LOCATION
));
438 ASSERT_TRUE(extension
.get() != NULL
);
440 gfx::ImageSkia default_icon
= GetDefaultIcon();
441 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
442 default_icon
.size());
444 ExtensionIconSet empty_icon_set
;
446 const int kRequestedSize
= 128;
447 IconImage
image(browser_context(),
454 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
456 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
458 // The resouce set is empty, so we should get the result right away.
459 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
460 EXPECT_TRUE(gfx::BitmapsAreEqual(
461 representation
.sk_bitmap(),
463 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
466 // We should have a default icon representation.
467 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
470 // Test that IconImage works with lazily (but synchronously) created default
471 // icon when IconImage returns asynchronously.
472 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon_AsyncIconImage
) {
473 scoped_refptr
<Extension
> extension(CreateExtension(
474 "extension_icon_image", Manifest::INVALID_LOCATION
));
475 ASSERT_TRUE(extension
.get() != NULL
);
477 gfx::ImageSkia default_icon
= GetDefaultIcon();
478 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
479 default_icon
.size());
481 const int kInvalidIconSize
= 24;
482 ExtensionIconSet invalid_icon_set
;
483 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
485 IconImage
image(browser_context(),
492 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
494 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
497 EXPECT_EQ(1, ImageLoadedCount());
498 // We should have default icon representation now.
499 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
501 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
503 representation
= image
.image_skia().GetRepresentation(1.0f
);
504 EXPECT_TRUE(gfx::BitmapsAreEqual(
505 representation
.sk_bitmap(),
507 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
511 // Tests behavior of image created by IconImage after IconImage host goes
512 // away. The image should still return loaded representations. If requested
513 // representation was not loaded while IconImage host was around, transparent
514 // representations should be returned.
515 TEST_F(ExtensionIconImageTest
, IconImageDestruction
) {
516 scoped_refptr
<Extension
> extension(CreateExtension(
517 "extension_icon_image", Manifest::INVALID_LOCATION
));
518 ASSERT_TRUE(extension
.get() != NULL
);
520 gfx::ImageSkia default_icon
= GetDefaultIcon();
522 // Load images we expect to find as representations in icon_image, so we
523 // can later use them to validate icon_image.
524 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
525 ASSERT_FALSE(bitmap_16
.empty());
527 scoped_ptr
<IconImage
> image(
528 new IconImage(browser_context(),
530 IconsInfo::GetIcons(extension
.get()),
535 // Load an image representation.
536 gfx::ImageSkiaRep representation
=
537 image
->image_skia().GetRepresentation(1.0f
);
540 EXPECT_EQ(1, ImageLoadedCount());
541 ASSERT_EQ(1u, image
->image_skia().image_reps().size());
543 // Stash loaded image skia, and destroy |image|.
544 gfx::ImageSkia image_skia
= image
->image_skia();
548 // Image skia should still be able to get previously loaded representation.
549 representation
= image_skia
.GetRepresentation(1.0f
);
551 EXPECT_EQ(1.0f
, representation
.scale());
552 EXPECT_EQ(16, representation
.pixel_width());
553 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
555 // When requesting another representation, we should not crash and return some
556 // image of the size. It could be blank or a rescale from the existing 1.0f
558 representation
= image_skia
.GetRepresentation(2.0f
);
560 EXPECT_EQ(16, representation
.GetWidth());
561 EXPECT_EQ(16, representation
.GetHeight());
562 EXPECT_EQ(2.0f
, representation
.scale());
565 // Test that new representations added to the image of an IconImage are cached
567 TEST_F(ExtensionIconImageTest
, ImageCachesNewRepresentations
) {
568 // Load up an extension and create an icon image.
569 scoped_refptr
<Extension
> extension(
570 CreateExtension("extension_icon_image", Manifest::INVALID_LOCATION
));
571 ASSERT_TRUE(extension
.get() != NULL
);
572 gfx::ImageSkia default_icon
= GetDefaultIcon();
573 scoped_ptr
<IconImage
> icon_image(
574 new IconImage(browser_context(),
576 IconsInfo::GetIcons(extension
.get()),
581 // Load an image representation.
582 gfx::ImageSkiaRep representation
=
583 icon_image
->image_skia().GetRepresentation(1.0f
);
586 // Cache for later use.
587 gfx::Image prior_image
= icon_image
->image();
589 // Now the fun part: access the image from the IconImage, and create a png
590 // representation of it.
591 gfx::Image image
= icon_image
->image();
592 scoped_refptr
<base::RefCountedMemory
> image_as_png
= image
.As1xPNGBytes();
594 // Access the image from the IconImage again, and get a png representation.
595 // The two png representations should be exactly equal (the same object),
596 // because image storage is shared, so when we created one from the first
597 // image, all other images should also have that representation...
598 gfx::Image image2
= icon_image
->image();
599 EXPECT_EQ(image_as_png
.get(), image2
.As1xPNGBytes().get());
601 // ...even images that were copied before the representation was constructed.
602 EXPECT_EQ(image_as_png
.get(), prior_image
.As1xPNGBytes().get());
605 } // namespace extensions