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 JSONFileValueDeserializer
deserializer(
154 test_file
.AppendASCII("manifest.json"));
155 scoped_ptr
<base::DictionaryValue
> valid_value(
156 static_cast<base::DictionaryValue
*>(
157 deserializer
.Deserialize(&error_code
, &error
)));
158 EXPECT_EQ(0, error_code
) << error
;
162 EXPECT_TRUE(valid_value
.get());
166 return Extension::Create(test_file
, location
, *valid_value
,
167 Extension::NO_FLAGS
, &error
);
170 // testing::Test overrides:
171 void SetUp() override
{
172 file_thread_
.Start();
176 // IconImage::Delegate overrides:
177 void OnExtensionIconImageChanged(IconImage
* image
) override
{
178 image_loaded_count_
++;
179 if (quit_in_image_loaded_
)
180 base::MessageLoop::current()->Quit();
183 gfx::ImageSkia
GetDefaultIcon() {
184 return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f
));
187 // Loads an image to be used in test from the extension.
188 // The image will be loaded from the relative path |path|.
189 SkBitmap
GetTestBitmap(const Extension
* extension
,
190 const std::string
& path
,
192 TestImageLoader
image_loader(extension
);
193 return image_loader
.LoadBitmap(path
, size
);
197 int image_loaded_count_
;
198 bool quit_in_image_loaded_
;
199 base::MessageLoop ui_loop_
;
200 content::TestBrowserThread ui_thread_
;
201 content::TestBrowserThread file_thread_
;
202 content::TestBrowserThread io_thread_
;
203 scoped_ptr
<content::NotificationService
> notification_service_
;
205 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest
);
210 TEST_F(ExtensionIconImageTest
, Basic
) {
211 std::vector
<ui::ScaleFactor
> supported_factors
;
212 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
213 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
214 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
215 scoped_refptr
<Extension
> extension(CreateExtension(
216 "extension_icon_image", Manifest::INVALID_LOCATION
));
217 ASSERT_TRUE(extension
.get() != NULL
);
219 gfx::ImageSkia default_icon
= GetDefaultIcon();
221 // Load images we expect to find as representations in icon_image, so we
222 // can later use them to validate icon_image.
223 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
224 ASSERT_FALSE(bitmap_16
.empty());
226 // There is no image of size 32 defined in the extension manifest, so we
227 // should expect manifest image of size 48 resized to size 32.
228 SkBitmap bitmap_48_resized_to_32
=
229 GetTestBitmap(extension
.get(), "48.png", 32);
230 ASSERT_FALSE(bitmap_48_resized_to_32
.empty());
232 IconImage
image(browser_context(),
234 IconsInfo::GetIcons(extension
.get()),
239 // No representations in |image_| yet.
240 gfx::ImageSkia::ImageSkiaReps image_reps
= image
.image_skia().image_reps();
241 ASSERT_EQ(0u, image_reps
.size());
243 // Gets representation for a scale factor.
244 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
246 // Before the image representation is loaded, image should contain blank
247 // image representation.
248 EXPECT_TRUE(gfx::BitmapsAreEqual(
249 representation
.sk_bitmap(),
250 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P
)));
253 EXPECT_EQ(1, ImageLoadedCount());
254 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
256 representation
= image
.image_skia().GetRepresentation(1.0f
);
258 // We should get the right representation now.
259 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
260 EXPECT_EQ(16, representation
.pixel_width());
262 // Gets representation for an additional scale factor.
263 representation
= image
.image_skia().GetRepresentation(2.0f
);
265 EXPECT_TRUE(gfx::BitmapsAreEqual(
266 representation
.sk_bitmap(),
267 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));
270 EXPECT_EQ(1, ImageLoadedCount());
271 ASSERT_EQ(2u, image
.image_skia().image_reps().size());
273 representation
= image
.image_skia().GetRepresentation(2.0f
);
275 // Image should have been resized.
276 EXPECT_EQ(32, representation
.pixel_width());
277 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
278 bitmap_48_resized_to_32
));
281 // There is no resource with either exact or bigger size, but there is a smaller
283 TEST_F(ExtensionIconImageTest
, FallbackToSmallerWhenNoBigger
) {
284 std::vector
<ui::ScaleFactor
> supported_factors
;
285 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
286 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
287 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
288 scoped_refptr
<Extension
> extension(CreateExtension(
289 "extension_icon_image", Manifest::INVALID_LOCATION
));
290 ASSERT_TRUE(extension
.get() != NULL
);
292 gfx::ImageSkia default_icon
= GetDefaultIcon();
294 // Load images we expect to find as representations in icon_image, so we
295 // can later use them to validate icon_image.
296 SkBitmap bitmap_48
= GetTestBitmap(extension
.get(), "48.png", 48);
297 ASSERT_FALSE(bitmap_48
.empty());
299 IconImage
image(browser_context(),
301 IconsInfo::GetIcons(extension
.get()),
306 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(2.0f
);
309 EXPECT_EQ(1, ImageLoadedCount());
310 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
312 representation
= image
.image_skia().GetRepresentation(2.0f
);
314 // We should have loaded the biggest smaller resource resized to the actual
316 EXPECT_EQ(2.0f
, representation
.scale());
317 EXPECT_EQ(64, representation
.pixel_width());
318 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
319 EnsureBitmapSize(bitmap_48
, 64)));
322 // There is no resource with exact size, but there is a smaller and a bigger
323 // one. The bigger resource should be loaded.
324 TEST_F(ExtensionIconImageTest
, FallbackToBigger
) {
325 scoped_refptr
<Extension
> extension(CreateExtension(
326 "extension_icon_image", Manifest::INVALID_LOCATION
));
327 ASSERT_TRUE(extension
.get() != NULL
);
329 gfx::ImageSkia default_icon
= GetDefaultIcon();
331 // Load images we expect to find as representations in icon_image, so we
332 // can later use them to validate icon_image.
333 SkBitmap bitmap_24
= GetTestBitmap(extension
.get(), "24.png", 24);
334 ASSERT_FALSE(bitmap_24
.empty());
336 IconImage
image(browser_context(),
338 IconsInfo::GetIcons(extension
.get()),
343 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
346 EXPECT_EQ(1, ImageLoadedCount());
347 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
349 representation
= image
.image_skia().GetRepresentation(1.0f
);
351 // We should have loaded the smallest bigger (resized) resource.
352 EXPECT_EQ(1.0f
, representation
.scale());
353 EXPECT_EQ(17, representation
.pixel_width());
354 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
355 EnsureBitmapSize(bitmap_24
, 17)));
358 // If resource set is empty, |GetRepresentation| should synchronously return
359 // default icon, without notifying observer of image change.
360 TEST_F(ExtensionIconImageTest
, NoResources
) {
361 scoped_refptr
<Extension
> extension(CreateExtension(
362 "extension_icon_image", Manifest::INVALID_LOCATION
));
363 ASSERT_TRUE(extension
.get() != NULL
);
365 ExtensionIconSet empty_icon_set
;
366 gfx::ImageSkia default_icon
= GetDefaultIcon();
368 const int kRequestedSize
= 24;
369 IconImage
image(browser_context(),
376 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
377 EXPECT_TRUE(gfx::BitmapsAreEqual(
378 representation
.sk_bitmap(),
380 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
383 EXPECT_EQ(0, ImageLoadedCount());
384 // We should have a default icon representation.
385 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
387 representation
= image
.image_skia().GetRepresentation(1.0f
);
388 EXPECT_TRUE(gfx::BitmapsAreEqual(
389 representation
.sk_bitmap(),
391 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
395 // If resource set is invalid, image load should be done asynchronously and
396 // the observer should be notified when it's done. |GetRepresentation| should
397 // return the default icon representation once image load is done.
398 TEST_F(ExtensionIconImageTest
, InvalidResource
) {
399 scoped_refptr
<Extension
> extension(CreateExtension(
400 "extension_icon_image", Manifest::INVALID_LOCATION
));
401 ASSERT_TRUE(extension
.get() != NULL
);
403 const int kInvalidIconSize
= 24;
404 ExtensionIconSet invalid_icon_set
;
405 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
407 gfx::ImageSkia default_icon
= GetDefaultIcon();
409 IconImage
image(browser_context(),
416 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
417 EXPECT_TRUE(gfx::BitmapsAreEqual(
418 representation
.sk_bitmap(),
419 CreateBlankBitmapForScale(kInvalidIconSize
, ui::SCALE_FACTOR_100P
)));
422 EXPECT_EQ(1, ImageLoadedCount());
423 // We should have default icon representation now.
424 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
426 representation
= image
.image_skia().GetRepresentation(1.0f
);
427 EXPECT_TRUE(gfx::BitmapsAreEqual(
428 representation
.sk_bitmap(),
430 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
434 // Test that IconImage works with lazily (but synchronously) created default
435 // icon when IconImage returns synchronously.
436 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon
) {
437 scoped_refptr
<Extension
> extension(CreateExtension(
438 "extension_icon_image", Manifest::INVALID_LOCATION
));
439 ASSERT_TRUE(extension
.get() != NULL
);
441 gfx::ImageSkia default_icon
= GetDefaultIcon();
442 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
443 default_icon
.size());
445 ExtensionIconSet empty_icon_set
;
447 const int kRequestedSize
= 128;
448 IconImage
image(browser_context(),
455 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
457 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
459 // The resouce set is empty, so we should get the result right away.
460 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
461 EXPECT_TRUE(gfx::BitmapsAreEqual(
462 representation
.sk_bitmap(),
464 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
467 // We should have a default icon representation.
468 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
471 // Test that IconImage works with lazily (but synchronously) created default
472 // icon when IconImage returns asynchronously.
473 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon_AsyncIconImage
) {
474 scoped_refptr
<Extension
> extension(CreateExtension(
475 "extension_icon_image", Manifest::INVALID_LOCATION
));
476 ASSERT_TRUE(extension
.get() != NULL
);
478 gfx::ImageSkia default_icon
= GetDefaultIcon();
479 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
480 default_icon
.size());
482 const int kInvalidIconSize
= 24;
483 ExtensionIconSet invalid_icon_set
;
484 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
486 IconImage
image(browser_context(),
493 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
495 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
498 EXPECT_EQ(1, ImageLoadedCount());
499 // We should have default icon representation now.
500 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
502 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
504 representation
= image
.image_skia().GetRepresentation(1.0f
);
505 EXPECT_TRUE(gfx::BitmapsAreEqual(
506 representation
.sk_bitmap(),
508 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
512 // Tests behavior of image created by IconImage after IconImage host goes
513 // away. The image should still return loaded representations. If requested
514 // representation was not loaded while IconImage host was around, transparent
515 // representations should be returned.
516 TEST_F(ExtensionIconImageTest
, IconImageDestruction
) {
517 scoped_refptr
<Extension
> extension(CreateExtension(
518 "extension_icon_image", Manifest::INVALID_LOCATION
));
519 ASSERT_TRUE(extension
.get() != NULL
);
521 gfx::ImageSkia default_icon
= GetDefaultIcon();
523 // Load images we expect to find as representations in icon_image, so we
524 // can later use them to validate icon_image.
525 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
526 ASSERT_FALSE(bitmap_16
.empty());
528 scoped_ptr
<IconImage
> image(
529 new IconImage(browser_context(),
531 IconsInfo::GetIcons(extension
.get()),
536 // Load an image representation.
537 gfx::ImageSkiaRep representation
=
538 image
->image_skia().GetRepresentation(1.0f
);
541 EXPECT_EQ(1, ImageLoadedCount());
542 ASSERT_EQ(1u, image
->image_skia().image_reps().size());
544 // Stash loaded image skia, and destroy |image|.
545 gfx::ImageSkia image_skia
= image
->image_skia();
549 // Image skia should still be able to get previously loaded representation.
550 representation
= image_skia
.GetRepresentation(1.0f
);
552 EXPECT_EQ(1.0f
, representation
.scale());
553 EXPECT_EQ(16, representation
.pixel_width());
554 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
556 // When requesting another representation, we should not crash and return some
557 // image of the size. It could be blank or a rescale from the existing 1.0f
559 representation
= image_skia
.GetRepresentation(2.0f
);
561 EXPECT_EQ(16, representation
.GetWidth());
562 EXPECT_EQ(16, representation
.GetHeight());
563 EXPECT_EQ(2.0f
, representation
.scale());
566 // Test that new representations added to the image of an IconImage are cached
568 TEST_F(ExtensionIconImageTest
, ImageCachesNewRepresentations
) {
569 // Load up an extension and create an icon image.
570 scoped_refptr
<Extension
> extension(
571 CreateExtension("extension_icon_image", Manifest::INVALID_LOCATION
));
572 ASSERT_TRUE(extension
.get() != NULL
);
573 gfx::ImageSkia default_icon
= GetDefaultIcon();
574 scoped_ptr
<IconImage
> icon_image(
575 new IconImage(browser_context(),
577 IconsInfo::GetIcons(extension
.get()),
582 // Load an image representation.
583 gfx::ImageSkiaRep representation
=
584 icon_image
->image_skia().GetRepresentation(1.0f
);
587 // Cache for later use.
588 gfx::Image prior_image
= icon_image
->image();
590 // Now the fun part: access the image from the IconImage, and create a png
591 // representation of it.
592 gfx::Image image
= icon_image
->image();
593 scoped_refptr
<base::RefCountedMemory
> image_as_png
= image
.As1xPNGBytes();
595 // Access the image from the IconImage again, and get a png representation.
596 // The two png representations should be exactly equal (the same object),
597 // because image storage is shared, so when we created one from the first
598 // image, all other images should also have that representation...
599 gfx::Image image2
= icon_image
->image();
600 EXPECT_EQ(image_as_png
.get(), image2
.As1xPNGBytes().get());
602 // ...even images that were copied before the representation was constructed.
603 EXPECT_EQ(image_as_png
.get(), prior_image
.As1xPNGBytes().get());
606 } // namespace extensions