1 // Copyright (c) 2012 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 "chrome/browser/extensions/extension_icon_image.h"
7 #include "base/json/json_file_value_serializer.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/path_service.h"
10 #include "chrome/browser/extensions/image_loader.h"
11 #include "chrome/common/chrome_paths.h"
12 #include "chrome/common/extensions/extension_constants.h"
13 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
14 #include "chrome/test/base/testing_profile.h"
15 #include "content/public/test/test_browser_thread.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/manifest.h"
18 #include "grit/theme_resources.h"
19 #include "skia/ext/image_operations.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/gfx/image/image_skia_source.h"
23 #include "ui/gfx/skia_util.h"
25 using content::BrowserThread
;
26 using extensions::Extension
;
27 using extensions::IconImage
;
28 using extensions::Manifest
;
32 SkBitmap
CreateBlankBitmapForScale(int size_dip
, ui::ScaleFactor scale_factor
) {
34 const float scale
= ui::GetImageScale(scale_factor
);
35 bitmap
.setConfig(SkBitmap::kARGB_8888_Config
,
36 static_cast<int>(size_dip
* scale
),
37 static_cast<int>(size_dip
* scale
));
39 bitmap
.eraseColor(SkColorSetARGB(0, 0, 0, 0));
43 SkBitmap
EnsureBitmapSize(const SkBitmap
& original
, int size
) {
44 if (original
.width() == size
&& original
.height() == size
)
47 SkBitmap resized
= skia::ImageOperations::Resize(
48 original
, skia::ImageOperations::RESIZE_LANCZOS3
, size
, size
);
52 // Used to test behavior including images defined by an image skia source.
53 // |GetImageForScale| simply returns image representation from the image given
55 class MockImageSkiaSource
: public gfx::ImageSkiaSource
{
57 explicit MockImageSkiaSource(const gfx::ImageSkia
& image
)
60 virtual ~MockImageSkiaSource() {}
62 virtual gfx::ImageSkiaRep
GetImageForScale(float scale
) OVERRIDE
{
63 return image_
.GetRepresentation(scale
);
67 gfx::ImageSkia image_
;
70 // Helper class for synchronously loading extension image resource.
71 class TestImageLoader
{
73 explicit TestImageLoader(const Extension
* extension
)
74 : extension_(extension
),
76 image_loaded_(false) {
78 virtual ~TestImageLoader() {}
80 void OnImageLoaded(const gfx::Image
& image
) {
84 base::MessageLoop::current()->Quit();
87 SkBitmap
LoadBitmap(const std::string
& path
,
89 image_loaded_
= false;
91 image_loader_
.LoadImageAsync(
92 extension_
, extension_
->GetResource(path
), gfx::Size(size
, size
),
93 base::Bind(&TestImageLoader::OnImageLoaded
,
94 base::Unretained(this)));
96 // If |image_| still hasn't been loaded (i.e. it is being loaded
97 // asynchronously), wait for it.
100 base::MessageLoop::current()->Run();
104 EXPECT_TRUE(image_loaded_
);
106 return image_
.IsEmpty() ? SkBitmap() : *image_
.ToSkBitmap();
110 const Extension
* extension_
;
114 extensions::ImageLoader image_loader_
;
116 DISALLOW_COPY_AND_ASSIGN(TestImageLoader
);
119 class ExtensionIconImageTest
: public testing::Test
,
120 public IconImage::Observer
{
122 ExtensionIconImageTest()
123 : image_loaded_count_(0),
124 quit_in_image_loaded_(false),
125 ui_thread_(BrowserThread::UI
, &ui_loop_
),
126 file_thread_(BrowserThread::FILE),
127 io_thread_(BrowserThread::IO
) {
130 virtual ~ExtensionIconImageTest() {}
132 void WaitForImageLoad() {
133 quit_in_image_loaded_
= true;
134 base::MessageLoop::current()->Run();
135 quit_in_image_loaded_
= false;
138 int ImageLoadedCount() {
139 int result
= image_loaded_count_
;
140 image_loaded_count_
= 0;
144 scoped_refptr
<Extension
> CreateExtension(const char* name
,
145 Manifest::Location location
) {
146 // Create and load an extension.
147 base::FilePath test_file
;
148 if (!PathService::Get(chrome::DIR_TEST_DATA
, &test_file
)) {
152 test_file
= test_file
.AppendASCII("extensions").AppendASCII(name
);
155 JSONFileValueSerializer
serializer(test_file
.AppendASCII("app.json"));
156 scoped_ptr
<base::DictionaryValue
> valid_value(
157 static_cast<base::DictionaryValue
*>(serializer
.Deserialize(&error_code
,
159 EXPECT_EQ(0, error_code
) << error
;
163 EXPECT_TRUE(valid_value
.get());
167 return Extension::Create(test_file
, location
, *valid_value
,
168 Extension::NO_FLAGS
, &error
);
171 // testing::Test overrides:
172 virtual void SetUp() OVERRIDE
{
173 file_thread_
.Start();
177 // IconImage::Delegate overrides:
178 virtual void OnExtensionIconImageChanged(IconImage
* image
) OVERRIDE
{
179 image_loaded_count_
++;
180 if (quit_in_image_loaded_
)
181 base::MessageLoop::current()->Quit();
184 gfx::ImageSkia
GetDefaultIcon() {
185 return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
186 IDR_EXTENSIONS_FAVICON
);
189 // Loads an image to be used in test from the extension.
190 // The image will be loaded from the relative path |path|.
191 SkBitmap
GetTestBitmap(const Extension
* extension
,
192 const std::string
& path
,
194 TestImageLoader
image_loader(extension
);
195 return image_loader
.LoadBitmap(path
, size
);
199 int image_loaded_count_
;
200 bool quit_in_image_loaded_
;
201 base::MessageLoop ui_loop_
;
202 content::TestBrowserThread ui_thread_
;
203 content::TestBrowserThread file_thread_
;
204 content::TestBrowserThread io_thread_
;
206 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest
);
211 TEST_F(ExtensionIconImageTest
, Basic
) {
212 std::vector
<ui::ScaleFactor
> supported_factors
;
213 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
214 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
215 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
216 scoped_ptr
<Profile
> profile(new TestingProfile());
217 scoped_refptr
<Extension
> extension(CreateExtension(
218 "extension_icon_image", Manifest::INVALID_LOCATION
));
219 ASSERT_TRUE(extension
.get() != NULL
);
221 gfx::ImageSkia default_icon
= GetDefaultIcon();
223 // Load images we expect to find as representations in icon_image, so we
224 // can later use them to validate icon_image.
225 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
226 ASSERT_FALSE(bitmap_16
.empty());
228 // There is no image of size 32 defined in the extension manifest, so we
229 // should expect manifest image of size 48 resized to size 32.
230 SkBitmap bitmap_48_resized_to_32
=
231 GetTestBitmap(extension
.get(), "48.png", 32);
232 ASSERT_FALSE(bitmap_48_resized_to_32
.empty());
234 IconImage
image(profile
.get(),
236 extensions::IconsInfo::GetIcons(extension
.get()),
241 // No representations in |image_| yet.
242 gfx::ImageSkia::ImageSkiaReps image_reps
= image
.image_skia().image_reps();
243 ASSERT_EQ(0u, image_reps
.size());
245 // Gets representation for a scale factor.
246 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
248 // Before the image representation is loaded, image should contain blank
249 // image representation.
250 EXPECT_TRUE(gfx::BitmapsAreEqual(
251 representation
.sk_bitmap(),
252 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P
)));
255 EXPECT_EQ(1, ImageLoadedCount());
256 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
258 representation
= image
.image_skia().GetRepresentation(1.0f
);
260 // We should get the right representation now.
261 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
262 EXPECT_EQ(16, representation
.pixel_width());
264 // Gets representation for an additional scale factor.
265 representation
= image
.image_skia().GetRepresentation(2.0f
);
267 EXPECT_TRUE(gfx::BitmapsAreEqual(
268 representation
.sk_bitmap(),
269 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));
272 EXPECT_EQ(1, ImageLoadedCount());
273 ASSERT_EQ(2u, image
.image_skia().image_reps().size());
275 representation
= image
.image_skia().GetRepresentation(2.0f
);
277 // Image should have been resized.
278 EXPECT_EQ(32, representation
.pixel_width());
279 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
280 bitmap_48_resized_to_32
));
283 // There is no resource with either exact or bigger size, but there is a smaller
285 TEST_F(ExtensionIconImageTest
, FallbackToSmallerWhenNoBigger
) {
286 std::vector
<ui::ScaleFactor
> supported_factors
;
287 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
288 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
289 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
290 scoped_ptr
<Profile
> profile(new TestingProfile());
291 scoped_refptr
<Extension
> extension(CreateExtension(
292 "extension_icon_image", Manifest::INVALID_LOCATION
));
293 ASSERT_TRUE(extension
.get() != NULL
);
295 gfx::ImageSkia default_icon
= GetDefaultIcon();
297 // Load images we expect to find as representations in icon_image, so we
298 // can later use them to validate icon_image.
299 SkBitmap bitmap_48
= GetTestBitmap(extension
.get(), "48.png", 48);
300 ASSERT_FALSE(bitmap_48
.empty());
302 IconImage
image(profile
.get(),
304 extensions::IconsInfo::GetIcons(extension
.get()),
309 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(2.0f
);
312 EXPECT_EQ(1, ImageLoadedCount());
313 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
315 representation
= image
.image_skia().GetRepresentation(2.0f
);
317 // We should have loaded the biggest smaller resource resized to the actual
319 EXPECT_EQ(2.0f
, representation
.scale());
320 EXPECT_EQ(64, representation
.pixel_width());
321 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
322 EnsureBitmapSize(bitmap_48
, 64)));
325 // There is no resource with exact size, but there is a smaller and a bigger
326 // one. Requested size is smaller than 32 though, so the smaller resource should
328 TEST_F(ExtensionIconImageTest
, FallbackToSmaller
) {
329 scoped_ptr
<Profile
> profile(new TestingProfile());
330 scoped_refptr
<Extension
> extension(CreateExtension(
331 "extension_icon_image", Manifest::INVALID_LOCATION
));
332 ASSERT_TRUE(extension
.get() != NULL
);
334 gfx::ImageSkia default_icon
= GetDefaultIcon();
336 // Load images we expect to find as representations in icon_image, so we
337 // can later use them to validate icon_image.
338 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
339 ASSERT_FALSE(bitmap_16
.empty());
341 IconImage
image(profile
.get(),
343 extensions::IconsInfo::GetIcons(extension
.get()),
348 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
351 EXPECT_EQ(1, ImageLoadedCount());
352 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
354 representation
= image
.image_skia().GetRepresentation(1.0f
);
356 // We should have loaded smaller (resized) resource.
357 EXPECT_EQ(1.0f
, representation
.scale());
358 EXPECT_EQ(17, representation
.pixel_width());
359 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
360 EnsureBitmapSize(bitmap_16
, 17)));
363 // If resource set is empty, |GetRepresentation| should synchronously return
364 // default icon, without notifying observer of image change.
365 TEST_F(ExtensionIconImageTest
, NoResources
) {
366 scoped_ptr
<Profile
> profile(new TestingProfile());
367 scoped_refptr
<Extension
> extension(CreateExtension(
368 "extension_icon_image", Manifest::INVALID_LOCATION
));
369 ASSERT_TRUE(extension
.get() != NULL
);
371 ExtensionIconSet empty_icon_set
;
372 gfx::ImageSkia default_icon
= GetDefaultIcon();
374 const int kRequestedSize
= 24;
375 IconImage
image(profile
.get(),
382 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
383 EXPECT_TRUE(gfx::BitmapsAreEqual(
384 representation
.sk_bitmap(),
386 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
389 EXPECT_EQ(0, ImageLoadedCount());
390 // We should have a default icon representation.
391 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
393 representation
= image
.image_skia().GetRepresentation(1.0f
);
394 EXPECT_TRUE(gfx::BitmapsAreEqual(
395 representation
.sk_bitmap(),
397 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
401 // If resource set is invalid, image load should be done asynchronously and
402 // the observer should be notified when it's done. |GetRepresentation| should
403 // return the default icon representation once image load is done.
404 TEST_F(ExtensionIconImageTest
, InvalidResource
) {
405 scoped_ptr
<Profile
> profile(new TestingProfile());
406 scoped_refptr
<Extension
> extension(CreateExtension(
407 "extension_icon_image", Manifest::INVALID_LOCATION
));
408 ASSERT_TRUE(extension
.get() != NULL
);
410 const int kInvalidIconSize
= 24;
411 ExtensionIconSet invalid_icon_set
;
412 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
414 gfx::ImageSkia default_icon
= GetDefaultIcon();
416 IconImage
image(profile
.get(),
423 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
424 EXPECT_TRUE(gfx::BitmapsAreEqual(
425 representation
.sk_bitmap(),
426 CreateBlankBitmapForScale(kInvalidIconSize
, ui::SCALE_FACTOR_100P
)));
429 EXPECT_EQ(1, ImageLoadedCount());
430 // We should have default icon representation now.
431 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
433 representation
= image
.image_skia().GetRepresentation(1.0f
);
434 EXPECT_TRUE(gfx::BitmapsAreEqual(
435 representation
.sk_bitmap(),
437 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
441 // Test that IconImage works with lazily (but synchronously) created default
442 // icon when IconImage returns synchronously.
443 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon
) {
444 scoped_ptr
<Profile
> profile(new TestingProfile());
445 scoped_refptr
<Extension
> extension(CreateExtension(
446 "extension_icon_image", Manifest::INVALID_LOCATION
));
447 ASSERT_TRUE(extension
.get() != NULL
);
449 gfx::ImageSkia default_icon
= GetDefaultIcon();
450 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
451 default_icon
.size());
453 ExtensionIconSet empty_icon_set
;
455 const int kRequestedSize
= 128;
456 IconImage
image(profile
.get(),
463 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
465 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
467 // The resouce set is empty, so we should get the result right away.
468 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
469 EXPECT_TRUE(gfx::BitmapsAreEqual(
470 representation
.sk_bitmap(),
472 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
475 // We should have a default icon representation.
476 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
479 // Test that IconImage works with lazily (but synchronously) created default
480 // icon when IconImage returns asynchronously.
481 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon_AsyncIconImage
) {
482 scoped_ptr
<Profile
> profile(new TestingProfile());
483 scoped_refptr
<Extension
> extension(CreateExtension(
484 "extension_icon_image", Manifest::INVALID_LOCATION
));
485 ASSERT_TRUE(extension
.get() != NULL
);
487 gfx::ImageSkia default_icon
= GetDefaultIcon();
488 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
489 default_icon
.size());
491 const int kInvalidIconSize
= 24;
492 ExtensionIconSet invalid_icon_set
;
493 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
495 IconImage
image(profile
.get(),
502 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
504 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
507 EXPECT_EQ(1, ImageLoadedCount());
508 // We should have default icon representation now.
509 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
511 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
513 representation
= image
.image_skia().GetRepresentation(1.0f
);
514 EXPECT_TRUE(gfx::BitmapsAreEqual(
515 representation
.sk_bitmap(),
517 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
521 // Tests behavior of image created by IconImage after IconImage host goes
522 // away. The image should still return loaded representations. If requested
523 // representation was not loaded while IconImage host was around, transparent
524 // representations should be returned.
525 TEST_F(ExtensionIconImageTest
, IconImageDestruction
) {
526 scoped_ptr
<Profile
> profile(new TestingProfile());
527 scoped_refptr
<Extension
> extension(CreateExtension(
528 "extension_icon_image", Manifest::INVALID_LOCATION
));
529 ASSERT_TRUE(extension
.get() != NULL
);
531 gfx::ImageSkia default_icon
= GetDefaultIcon();
533 // Load images we expect to find as representations in icon_image, so we
534 // can later use them to validate icon_image.
535 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
536 ASSERT_FALSE(bitmap_16
.empty());
538 scoped_ptr
<IconImage
> image(
539 new IconImage(profile
.get(),
541 extensions::IconsInfo::GetIcons(extension
.get()),
546 // Load an image representation.
547 gfx::ImageSkiaRep representation
=
548 image
->image_skia().GetRepresentation(1.0f
);
551 EXPECT_EQ(1, ImageLoadedCount());
552 ASSERT_EQ(1u, image
->image_skia().image_reps().size());
554 // Stash loaded image skia, and destroy |image|.
555 gfx::ImageSkia image_skia
= image
->image_skia();
559 // Image skia should still be able to get previously loaded representation.
560 representation
= image_skia
.GetRepresentation(1.0f
);
562 EXPECT_EQ(1.0f
, representation
.scale());
563 EXPECT_EQ(16, representation
.pixel_width());
564 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
566 // When requesting another representation, we should get blank image.
567 representation
= image_skia
.GetRepresentation(2.0f
);
569 EXPECT_TRUE(gfx::BitmapsAreEqual(
570 representation
.sk_bitmap(),
571 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));