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.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.h"
13 #include "chrome/common/extensions/extension_constants.h"
14 #include "chrome/common/extensions/manifest.h"
15 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
16 #include "chrome/test/base/testing_profile.h"
17 #include "content/public/test/test_browser_thread.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::GetScaleFactorScale(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(
63 ui::ScaleFactor scale_factor
) OVERRIDE
{
64 return image_
.GetRepresentation(scale_factor
);
68 gfx::ImageSkia image_
;
71 // Helper class for synchronously loading extension image resource.
72 class TestImageLoader
{
74 explicit TestImageLoader(const Extension
* extension
)
75 : extension_(extension
),
77 image_loaded_(false) {
79 virtual ~TestImageLoader() {}
81 void OnImageLoaded(const gfx::Image
& image
) {
85 base::MessageLoop::current()->Quit();
88 SkBitmap
LoadBitmap(const std::string
& path
,
90 image_loaded_
= false;
92 image_loader_
.LoadImageAsync(
93 extension_
, extension_
->GetResource(path
), gfx::Size(size
, size
),
94 base::Bind(&TestImageLoader::OnImageLoaded
,
95 base::Unretained(this)));
97 // If |image_| still hasn't been loaded (i.e. it is being loaded
98 // asynchronously), wait for it.
101 base::MessageLoop::current()->Run();
105 EXPECT_TRUE(image_loaded_
);
107 return image_
.IsEmpty() ? SkBitmap() : *image_
.ToSkBitmap();
111 const Extension
* extension_
;
115 extensions::ImageLoader image_loader_
;
117 DISALLOW_COPY_AND_ASSIGN(TestImageLoader
);
120 class ExtensionIconImageTest
: public testing::Test
,
121 public IconImage::Observer
{
123 ExtensionIconImageTest()
124 : image_loaded_count_(0),
125 quit_in_image_loaded_(false),
126 ui_thread_(BrowserThread::UI
, &ui_loop_
),
127 file_thread_(BrowserThread::FILE),
128 io_thread_(BrowserThread::IO
) {
131 virtual ~ExtensionIconImageTest() {}
133 void WaitForImageLoad() {
134 quit_in_image_loaded_
= true;
135 base::MessageLoop::current()->Run();
136 quit_in_image_loaded_
= false;
139 int ImageLoadedCount() {
140 int result
= image_loaded_count_
;
141 image_loaded_count_
= 0;
145 scoped_refptr
<Extension
> CreateExtension(const char* name
,
146 Manifest::Location location
) {
147 // Create and load an extension.
148 base::FilePath test_file
;
149 if (!PathService::Get(chrome::DIR_TEST_DATA
, &test_file
)) {
153 test_file
= test_file
.AppendASCII("extensions").AppendASCII(name
);
156 JSONFileValueSerializer
serializer(test_file
.AppendASCII("app.json"));
157 scoped_ptr
<DictionaryValue
> valid_value(
158 static_cast<DictionaryValue
*>(serializer
.Deserialize(&error_code
,
160 EXPECT_EQ(0, error_code
) << error
;
164 EXPECT_TRUE(valid_value
.get());
168 return Extension::Create(test_file
, location
, *valid_value
,
169 Extension::NO_FLAGS
, &error
);
172 // testing::Test overrides:
173 virtual void SetUp() OVERRIDE
{
174 file_thread_
.Start();
178 // IconImage::Delegate overrides:
179 virtual void OnExtensionIconImageChanged(IconImage
* image
) OVERRIDE
{
180 image_loaded_count_
++;
181 if (quit_in_image_loaded_
)
182 base::MessageLoop::current()->Quit();
185 gfx::ImageSkia
GetDefaultIcon() {
186 return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
187 IDR_EXTENSIONS_FAVICON
);
190 // Loads an image to be used in test from the extension.
191 // The image will be loaded from the relative path |path|.
192 SkBitmap
GetTestBitmap(const Extension
* extension
,
193 const std::string
& path
,
195 TestImageLoader
image_loader(extension
);
196 return image_loader
.LoadBitmap(path
, size
);
200 int image_loaded_count_
;
201 bool quit_in_image_loaded_
;
202 base::MessageLoop ui_loop_
;
203 content::TestBrowserThread ui_thread_
;
204 content::TestBrowserThread file_thread_
;
205 content::TestBrowserThread io_thread_
;
207 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest
);
212 TEST_F(ExtensionIconImageTest
, Basic
) {
213 scoped_ptr
<Profile
> profile(new TestingProfile());
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(profile
.get(),
233 extensions::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
=
244 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
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(ui::SCALE_FACTOR_100P
);
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(ui::SCALE_FACTOR_200P
);
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(ui::SCALE_FACTOR_200P
);
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 scoped_ptr
<Profile
> profile(new TestingProfile());
285 scoped_refptr
<Extension
> extension(CreateExtension(
286 "extension_icon_image", Manifest::INVALID_LOCATION
));
287 ASSERT_TRUE(extension
.get() != NULL
);
289 gfx::ImageSkia default_icon
= GetDefaultIcon();
291 // Load images we expect to find as representations in icon_image, so we
292 // can later use them to validate icon_image.
293 SkBitmap bitmap_48
= GetTestBitmap(extension
.get(), "48.png", 48);
294 ASSERT_FALSE(bitmap_48
.empty());
296 IconImage
image(profile
.get(),
298 extensions::IconsInfo::GetIcons(extension
.get()),
303 gfx::ImageSkiaRep representation
=
304 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_200P
);
307 EXPECT_EQ(1, ImageLoadedCount());
308 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
310 representation
= image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_200P
);
312 // We should have loaded the biggest smaller resource resized to the actual
314 EXPECT_EQ(ui::SCALE_FACTOR_200P
, representation
.scale_factor());
315 EXPECT_EQ(64, representation
.pixel_width());
316 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
317 EnsureBitmapSize(bitmap_48
, 64)));
320 // There is no resource with exact size, but there is a smaller and a bigger
321 // one. Requested size is smaller than 32 though, so the smaller resource should
323 TEST_F(ExtensionIconImageTest
, FallbackToSmaller
) {
324 scoped_ptr
<Profile
> profile(new TestingProfile());
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_16
= GetTestBitmap(extension
.get(), "16.png", 16);
334 ASSERT_FALSE(bitmap_16
.empty());
336 IconImage
image(profile
.get(),
338 extensions::IconsInfo::GetIcons(extension
.get()),
343 gfx::ImageSkiaRep representation
=
344 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
347 EXPECT_EQ(1, ImageLoadedCount());
348 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
350 representation
= image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
352 // We should have loaded smaller (resized) resource.
353 EXPECT_EQ(ui::SCALE_FACTOR_100P
, representation
.scale_factor());
354 EXPECT_EQ(17, representation
.pixel_width());
355 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
356 EnsureBitmapSize(bitmap_16
, 17)));
359 // If resource set is empty, |GetRepresentation| should synchronously return
360 // default icon, without notifying observer of image change.
361 TEST_F(ExtensionIconImageTest
, NoResources
) {
362 scoped_ptr
<Profile
> profile(new TestingProfile());
363 scoped_refptr
<Extension
> extension(CreateExtension(
364 "extension_icon_image", Manifest::INVALID_LOCATION
));
365 ASSERT_TRUE(extension
.get() != NULL
);
367 ExtensionIconSet empty_icon_set
;
368 gfx::ImageSkia default_icon
= GetDefaultIcon();
370 const int kRequestedSize
= 24;
371 IconImage
image(profile
.get(),
378 gfx::ImageSkiaRep representation
=
379 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
380 EXPECT_TRUE(gfx::BitmapsAreEqual(
381 representation
.sk_bitmap(),
383 default_icon
.GetRepresentation(ui::SCALE_FACTOR_100P
).sk_bitmap(),
386 EXPECT_EQ(0, ImageLoadedCount());
387 // We should have a default icon representation.
388 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
390 representation
= image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
391 EXPECT_TRUE(gfx::BitmapsAreEqual(
392 representation
.sk_bitmap(),
394 default_icon
.GetRepresentation(ui::SCALE_FACTOR_100P
).sk_bitmap(),
398 // If resource set is invalid, image load should be done asynchronously and
399 // the observer should be notified when it's done. |GetRepresentation| should
400 // return the default icon representation once image load is done.
401 TEST_F(ExtensionIconImageTest
, InvalidResource
) {
402 scoped_ptr
<Profile
> profile(new TestingProfile());
403 scoped_refptr
<Extension
> extension(CreateExtension(
404 "extension_icon_image", Manifest::INVALID_LOCATION
));
405 ASSERT_TRUE(extension
.get() != NULL
);
407 const int kInvalidIconSize
= 24;
408 ExtensionIconSet invalid_icon_set
;
409 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
411 gfx::ImageSkia default_icon
= GetDefaultIcon();
413 IconImage
image(profile
.get(),
420 gfx::ImageSkiaRep representation
=
421 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
422 EXPECT_TRUE(gfx::BitmapsAreEqual(
423 representation
.sk_bitmap(),
424 CreateBlankBitmapForScale(kInvalidIconSize
, ui::SCALE_FACTOR_100P
)));
427 EXPECT_EQ(1, ImageLoadedCount());
428 // We should have default icon representation now.
429 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
431 representation
= image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
432 EXPECT_TRUE(gfx::BitmapsAreEqual(
433 representation
.sk_bitmap(),
435 default_icon
.GetRepresentation(ui::SCALE_FACTOR_100P
).sk_bitmap(),
439 // Test that IconImage works with lazily (but synchronously) created default
440 // icon when IconImage returns synchronously.
441 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon
) {
442 scoped_ptr
<Profile
> profile(new TestingProfile());
443 scoped_refptr
<Extension
> extension(CreateExtension(
444 "extension_icon_image", Manifest::INVALID_LOCATION
));
445 ASSERT_TRUE(extension
.get() != NULL
);
447 gfx::ImageSkia default_icon
= GetDefaultIcon();
448 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
449 default_icon
.size());
451 ExtensionIconSet empty_icon_set
;
453 const int kRequestedSize
= 128;
454 IconImage
image(profile
.get(),
461 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(ui::SCALE_FACTOR_100P
));
463 gfx::ImageSkiaRep representation
=
464 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
466 // The resouce set is empty, so we should get the result right away.
467 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(ui::SCALE_FACTOR_100P
));
468 EXPECT_TRUE(gfx::BitmapsAreEqual(
469 representation
.sk_bitmap(),
471 default_icon
.GetRepresentation(ui::SCALE_FACTOR_100P
).sk_bitmap(),
474 // We should have a default icon representation.
475 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
478 // Test that IconImage works with lazily (but synchronously) created default
479 // icon when IconImage returns asynchronously.
480 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon_AsyncIconImage
) {
481 scoped_ptr
<Profile
> profile(new TestingProfile());
482 scoped_refptr
<Extension
> extension(CreateExtension(
483 "extension_icon_image", Manifest::INVALID_LOCATION
));
484 ASSERT_TRUE(extension
.get() != NULL
);
486 gfx::ImageSkia default_icon
= GetDefaultIcon();
487 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
488 default_icon
.size());
490 const int kInvalidIconSize
= 24;
491 ExtensionIconSet invalid_icon_set
;
492 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
494 IconImage
image(profile
.get(),
501 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(ui::SCALE_FACTOR_100P
));
503 gfx::ImageSkiaRep representation
=
504 image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
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(ui::SCALE_FACTOR_100P
));
513 representation
= image
.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P
);
514 EXPECT_TRUE(gfx::BitmapsAreEqual(
515 representation
.sk_bitmap(),
517 default_icon
.GetRepresentation(ui::SCALE_FACTOR_100P
).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(ui::SCALE_FACTOR_100P
);
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(ui::SCALE_FACTOR_100P
);
562 EXPECT_EQ(ui::SCALE_FACTOR_100P
, representation
.scale_factor());
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(ui::SCALE_FACTOR_200P
);
569 EXPECT_TRUE(gfx::BitmapsAreEqual(
570 representation
.sk_bitmap(),
571 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));