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"
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/test/base/testing_profile.h"
11 #include "content/public/test/test_browser_thread.h"
12 #include "extensions/browser/image_loader.h"
13 #include "extensions/common/extension.h"
14 #include "extensions/common/extension_paths.h"
15 #include "extensions/common/manifest.h"
16 #include "extensions/common/manifest_handlers/icons_handler.h"
17 #include "skia/ext/image_operations.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/gfx/image/image_skia_source.h"
21 #include "ui/gfx/skia_util.h"
23 using content::BrowserThread
;
24 using extensions::Extension
;
25 using extensions::IconImage
;
26 using extensions::Manifest
;
30 SkBitmap
CreateBlankBitmapForScale(int size_dip
, ui::ScaleFactor scale_factor
) {
32 const float scale
= ui::GetScaleForScaleFactor(scale_factor
);
33 bitmap
.allocN32Pixels(static_cast<int>(size_dip
* scale
),
34 static_cast<int>(size_dip
* scale
));
35 bitmap
.eraseColor(SkColorSetARGB(0, 0, 0, 0));
39 SkBitmap
EnsureBitmapSize(const SkBitmap
& original
, int size
) {
40 if (original
.width() == size
&& original
.height() == size
)
43 SkBitmap resized
= skia::ImageOperations::Resize(
44 original
, skia::ImageOperations::RESIZE_LANCZOS3
, size
, size
);
48 // Used to test behavior including images defined by an image skia source.
49 // |GetImageForScale| simply returns image representation from the image given
51 class MockImageSkiaSource
: public gfx::ImageSkiaSource
{
53 explicit MockImageSkiaSource(const gfx::ImageSkia
& image
)
56 virtual ~MockImageSkiaSource() {}
58 virtual gfx::ImageSkiaRep
GetImageForScale(float scale
) OVERRIDE
{
59 return image_
.GetRepresentation(scale
);
63 gfx::ImageSkia image_
;
66 // Helper class for synchronously loading extension image resource.
67 class TestImageLoader
{
69 explicit TestImageLoader(const Extension
* extension
)
70 : extension_(extension
),
72 image_loaded_(false) {
74 virtual ~TestImageLoader() {}
76 void OnImageLoaded(const gfx::Image
& image
) {
80 base::MessageLoop::current()->Quit();
83 SkBitmap
LoadBitmap(const std::string
& path
,
85 image_loaded_
= false;
87 image_loader_
.LoadImageAsync(
88 extension_
, extension_
->GetResource(path
), gfx::Size(size
, size
),
89 base::Bind(&TestImageLoader::OnImageLoaded
,
90 base::Unretained(this)));
92 // If |image_| still hasn't been loaded (i.e. it is being loaded
93 // asynchronously), wait for it.
96 base::MessageLoop::current()->Run();
100 EXPECT_TRUE(image_loaded_
);
102 return image_
.IsEmpty() ? SkBitmap() : *image_
.ToSkBitmap();
106 const Extension
* extension_
;
110 extensions::ImageLoader image_loader_
;
112 DISALLOW_COPY_AND_ASSIGN(TestImageLoader
);
115 class ExtensionIconImageTest
: public testing::Test
,
116 public IconImage::Observer
{
118 ExtensionIconImageTest()
119 : image_loaded_count_(0),
120 quit_in_image_loaded_(false),
121 ui_thread_(BrowserThread::UI
, &ui_loop_
),
122 file_thread_(BrowserThread::FILE),
123 io_thread_(BrowserThread::IO
) {
126 virtual ~ExtensionIconImageTest() {}
128 void WaitForImageLoad() {
129 quit_in_image_loaded_
= true;
130 base::MessageLoop::current()->Run();
131 quit_in_image_loaded_
= false;
134 int ImageLoadedCount() {
135 int result
= image_loaded_count_
;
136 image_loaded_count_
= 0;
140 scoped_refptr
<Extension
> CreateExtension(const char* name
,
141 Manifest::Location location
) {
142 // Create and load an extension.
143 base::FilePath test_file
;
144 if (!PathService::Get(extensions::DIR_TEST_DATA
, &test_file
)) {
148 test_file
= test_file
.AppendASCII(name
);
151 JSONFileValueSerializer
serializer(test_file
.AppendASCII("manifest.json"));
152 scoped_ptr
<base::DictionaryValue
> valid_value(
153 static_cast<base::DictionaryValue
*>(serializer
.Deserialize(&error_code
,
155 EXPECT_EQ(0, error_code
) << error
;
159 EXPECT_TRUE(valid_value
.get());
163 return Extension::Create(test_file
, location
, *valid_value
,
164 Extension::NO_FLAGS
, &error
);
167 // testing::Test overrides:
168 virtual void SetUp() OVERRIDE
{
169 file_thread_
.Start();
173 // IconImage::Delegate overrides:
174 virtual void OnExtensionIconImageChanged(IconImage
* image
) OVERRIDE
{
175 image_loaded_count_
++;
176 if (quit_in_image_loaded_
)
177 base::MessageLoop::current()->Quit();
180 gfx::ImageSkia
GetDefaultIcon() {
181 return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f
));
184 // Loads an image to be used in test from the extension.
185 // The image will be loaded from the relative path |path|.
186 SkBitmap
GetTestBitmap(const Extension
* extension
,
187 const std::string
& path
,
189 TestImageLoader
image_loader(extension
);
190 return image_loader
.LoadBitmap(path
, size
);
194 int image_loaded_count_
;
195 bool quit_in_image_loaded_
;
196 base::MessageLoop ui_loop_
;
197 content::TestBrowserThread ui_thread_
;
198 content::TestBrowserThread file_thread_
;
199 content::TestBrowserThread io_thread_
;
201 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest
);
206 TEST_F(ExtensionIconImageTest
, Basic
) {
207 std::vector
<ui::ScaleFactor
> supported_factors
;
208 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
209 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
210 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
211 scoped_ptr
<content::BrowserContext
> profile(new TestingProfile());
212 scoped_refptr
<Extension
> extension(CreateExtension(
213 "extension_icon_image", Manifest::INVALID_LOCATION
));
214 ASSERT_TRUE(extension
.get() != NULL
);
216 gfx::ImageSkia default_icon
= GetDefaultIcon();
218 // Load images we expect to find as representations in icon_image, so we
219 // can later use them to validate icon_image.
220 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
221 ASSERT_FALSE(bitmap_16
.empty());
223 // There is no image of size 32 defined in the extension manifest, so we
224 // should expect manifest image of size 48 resized to size 32.
225 SkBitmap bitmap_48_resized_to_32
=
226 GetTestBitmap(extension
.get(), "48.png", 32);
227 ASSERT_FALSE(bitmap_48_resized_to_32
.empty());
229 IconImage
image(profile
.get(),
231 extensions::IconsInfo::GetIcons(extension
.get()),
236 // No representations in |image_| yet.
237 gfx::ImageSkia::ImageSkiaReps image_reps
= image
.image_skia().image_reps();
238 ASSERT_EQ(0u, image_reps
.size());
240 // Gets representation for a scale factor.
241 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
243 // Before the image representation is loaded, image should contain blank
244 // image representation.
245 EXPECT_TRUE(gfx::BitmapsAreEqual(
246 representation
.sk_bitmap(),
247 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P
)));
250 EXPECT_EQ(1, ImageLoadedCount());
251 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
253 representation
= image
.image_skia().GetRepresentation(1.0f
);
255 // We should get the right representation now.
256 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
257 EXPECT_EQ(16, representation
.pixel_width());
259 // Gets representation for an additional scale factor.
260 representation
= image
.image_skia().GetRepresentation(2.0f
);
262 EXPECT_TRUE(gfx::BitmapsAreEqual(
263 representation
.sk_bitmap(),
264 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P
)));
267 EXPECT_EQ(1, ImageLoadedCount());
268 ASSERT_EQ(2u, image
.image_skia().image_reps().size());
270 representation
= image
.image_skia().GetRepresentation(2.0f
);
272 // Image should have been resized.
273 EXPECT_EQ(32, representation
.pixel_width());
274 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(),
275 bitmap_48_resized_to_32
));
278 // There is no resource with either exact or bigger size, but there is a smaller
280 TEST_F(ExtensionIconImageTest
, FallbackToSmallerWhenNoBigger
) {
281 std::vector
<ui::ScaleFactor
> supported_factors
;
282 supported_factors
.push_back(ui::SCALE_FACTOR_100P
);
283 supported_factors
.push_back(ui::SCALE_FACTOR_200P
);
284 ui::test::ScopedSetSupportedScaleFactors
scoped_supported(supported_factors
);
285 scoped_ptr
<content::BrowserContext
> profile(new TestingProfile());
286 scoped_refptr
<Extension
> extension(CreateExtension(
287 "extension_icon_image", Manifest::INVALID_LOCATION
));
288 ASSERT_TRUE(extension
.get() != NULL
);
290 gfx::ImageSkia default_icon
= GetDefaultIcon();
292 // Load images we expect to find as representations in icon_image, so we
293 // can later use them to validate icon_image.
294 SkBitmap bitmap_48
= GetTestBitmap(extension
.get(), "48.png", 48);
295 ASSERT_FALSE(bitmap_48
.empty());
297 IconImage
image(profile
.get(),
299 extensions::IconsInfo::GetIcons(extension
.get()),
304 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(2.0f
);
307 EXPECT_EQ(1, ImageLoadedCount());
308 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
310 representation
= image
.image_skia().GetRepresentation(2.0f
);
312 // We should have loaded the biggest smaller resource resized to the actual
314 EXPECT_EQ(2.0f
, representation
.scale());
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. The bigger resource should be loaded.
322 TEST_F(ExtensionIconImageTest
, FallbackToBigger
) {
323 scoped_ptr
<content::BrowserContext
> profile(new TestingProfile());
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(profile
.get(),
337 extensions::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_ptr
<content::BrowserContext
> profile(new TestingProfile());
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(profile
.get(),
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_ptr
<content::BrowserContext
> profile(new TestingProfile());
400 scoped_refptr
<Extension
> extension(CreateExtension(
401 "extension_icon_image", Manifest::INVALID_LOCATION
));
402 ASSERT_TRUE(extension
.get() != NULL
);
404 const int kInvalidIconSize
= 24;
405 ExtensionIconSet invalid_icon_set
;
406 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
408 gfx::ImageSkia default_icon
= GetDefaultIcon();
410 IconImage
image(profile
.get(),
417 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
418 EXPECT_TRUE(gfx::BitmapsAreEqual(
419 representation
.sk_bitmap(),
420 CreateBlankBitmapForScale(kInvalidIconSize
, ui::SCALE_FACTOR_100P
)));
423 EXPECT_EQ(1, ImageLoadedCount());
424 // We should have default icon representation now.
425 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
427 representation
= image
.image_skia().GetRepresentation(1.0f
);
428 EXPECT_TRUE(gfx::BitmapsAreEqual(
429 representation
.sk_bitmap(),
431 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
435 // Test that IconImage works with lazily (but synchronously) created default
436 // icon when IconImage returns synchronously.
437 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon
) {
438 scoped_ptr
<content::BrowserContext
> profile(new TestingProfile());
439 scoped_refptr
<Extension
> extension(CreateExtension(
440 "extension_icon_image", Manifest::INVALID_LOCATION
));
441 ASSERT_TRUE(extension
.get() != NULL
);
443 gfx::ImageSkia default_icon
= GetDefaultIcon();
444 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
445 default_icon
.size());
447 ExtensionIconSet empty_icon_set
;
449 const int kRequestedSize
= 128;
450 IconImage
image(profile
.get(),
457 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
459 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
461 // The resouce set is empty, so we should get the result right away.
462 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
463 EXPECT_TRUE(gfx::BitmapsAreEqual(
464 representation
.sk_bitmap(),
466 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
469 // We should have a default icon representation.
470 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
473 // Test that IconImage works with lazily (but synchronously) created default
474 // icon when IconImage returns asynchronously.
475 TEST_F(ExtensionIconImageTest
, LazyDefaultIcon_AsyncIconImage
) {
476 scoped_ptr
<content::BrowserContext
> profile(new TestingProfile());
477 scoped_refptr
<Extension
> extension(CreateExtension(
478 "extension_icon_image", Manifest::INVALID_LOCATION
));
479 ASSERT_TRUE(extension
.get() != NULL
);
481 gfx::ImageSkia default_icon
= GetDefaultIcon();
482 gfx::ImageSkia
lazy_default_icon(new MockImageSkiaSource(default_icon
),
483 default_icon
.size());
485 const int kInvalidIconSize
= 24;
486 ExtensionIconSet invalid_icon_set
;
487 invalid_icon_set
.Add(kInvalidIconSize
, "invalid.png");
489 IconImage
image(profile
.get(),
496 ASSERT_FALSE(lazy_default_icon
.HasRepresentation(1.0f
));
498 gfx::ImageSkiaRep representation
= image
.image_skia().GetRepresentation(1.0f
);
501 EXPECT_EQ(1, ImageLoadedCount());
502 // We should have default icon representation now.
503 ASSERT_EQ(1u, image
.image_skia().image_reps().size());
505 EXPECT_TRUE(lazy_default_icon
.HasRepresentation(1.0f
));
507 representation
= image
.image_skia().GetRepresentation(1.0f
);
508 EXPECT_TRUE(gfx::BitmapsAreEqual(
509 representation
.sk_bitmap(),
511 default_icon
.GetRepresentation(1.0f
).sk_bitmap(),
515 // Tests behavior of image created by IconImage after IconImage host goes
516 // away. The image should still return loaded representations. If requested
517 // representation was not loaded while IconImage host was around, transparent
518 // representations should be returned.
519 TEST_F(ExtensionIconImageTest
, IconImageDestruction
) {
520 scoped_ptr
<content::BrowserContext
> profile(new TestingProfile());
521 scoped_refptr
<Extension
> extension(CreateExtension(
522 "extension_icon_image", Manifest::INVALID_LOCATION
));
523 ASSERT_TRUE(extension
.get() != NULL
);
525 gfx::ImageSkia default_icon
= GetDefaultIcon();
527 // Load images we expect to find as representations in icon_image, so we
528 // can later use them to validate icon_image.
529 SkBitmap bitmap_16
= GetTestBitmap(extension
.get(), "16.png", 16);
530 ASSERT_FALSE(bitmap_16
.empty());
532 scoped_ptr
<IconImage
> image(
533 new IconImage(profile
.get(),
535 extensions::IconsInfo::GetIcons(extension
.get()),
540 // Load an image representation.
541 gfx::ImageSkiaRep representation
=
542 image
->image_skia().GetRepresentation(1.0f
);
545 EXPECT_EQ(1, ImageLoadedCount());
546 ASSERT_EQ(1u, image
->image_skia().image_reps().size());
548 // Stash loaded image skia, and destroy |image|.
549 gfx::ImageSkia image_skia
= image
->image_skia();
553 // Image skia should still be able to get previously loaded representation.
554 representation
= image_skia
.GetRepresentation(1.0f
);
556 EXPECT_EQ(1.0f
, representation
.scale());
557 EXPECT_EQ(16, representation
.pixel_width());
558 EXPECT_TRUE(gfx::BitmapsAreEqual(representation
.sk_bitmap(), bitmap_16
));
560 // When requesting another representation, we should not crash and return some
561 // image of the size. It could be blank or a rescale from the existing 1.0f
563 representation
= image_skia
.GetRepresentation(2.0f
);
565 EXPECT_EQ(16, representation
.GetWidth());
566 EXPECT_EQ(16, representation
.GetHeight());
567 EXPECT_EQ(2.0f
, representation
.scale());