Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / extensions / browser / extension_icon_image_unittest.cc
blob57b5c4787c11dded58817eb577fb29dc54ee6ae8
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;
28 namespace {
30 SkBitmap CreateBlankBitmapForScale(int size_dip, ui::ScaleFactor scale_factor) {
31 SkBitmap bitmap;
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));
36 return bitmap;
39 SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
40 if (original.width() == size && original.height() == size)
41 return original;
43 SkBitmap resized = skia::ImageOperations::Resize(
44 original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
45 return resized;
48 // Used to test behavior including images defined by an image skia source.
49 // |GetImageForScale| simply returns image representation from the image given
50 // in the ctor.
51 class MockImageSkiaSource : public gfx::ImageSkiaSource {
52 public:
53 explicit MockImageSkiaSource(const gfx::ImageSkia& image)
54 : image_(image) {
56 virtual ~MockImageSkiaSource() {}
58 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
59 return image_.GetRepresentation(scale);
62 private:
63 gfx::ImageSkia image_;
66 // Helper class for synchronously loading extension image resource.
67 class TestImageLoader {
68 public:
69 explicit TestImageLoader(const Extension* extension)
70 : extension_(extension),
71 waiting_(false),
72 image_loaded_(false) {
74 virtual ~TestImageLoader() {}
76 void OnImageLoaded(const gfx::Image& image) {
77 image_ = image;
78 image_loaded_ = true;
79 if (waiting_)
80 base::MessageLoop::current()->Quit();
83 SkBitmap LoadBitmap(const std::string& path,
84 int size) {
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.
94 if (!image_loaded_) {
95 waiting_ = true;
96 base::MessageLoop::current()->Run();
97 waiting_ = false;
100 EXPECT_TRUE(image_loaded_);
102 return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
105 private:
106 const Extension* extension_;
107 bool waiting_;
108 bool image_loaded_;
109 gfx::Image image_;
110 extensions::ImageLoader image_loader_;
112 DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
115 class ExtensionIconImageTest : public testing::Test,
116 public IconImage::Observer {
117 public:
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;
137 return result;
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)) {
145 EXPECT_FALSE(true);
146 return NULL;
148 test_file = test_file.AppendASCII(name);
149 int error_code = 0;
150 std::string error;
151 JSONFileValueSerializer serializer(test_file.AppendASCII("manifest.json"));
152 scoped_ptr<base::DictionaryValue> valid_value(
153 static_cast<base::DictionaryValue*>(serializer.Deserialize(&error_code,
154 &error)));
155 EXPECT_EQ(0, error_code) << error;
156 if (error_code != 0)
157 return NULL;
159 EXPECT_TRUE(valid_value.get());
160 if (!valid_value)
161 return NULL;
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();
170 io_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,
188 int size) {
189 TestImageLoader image_loader(extension);
190 return image_loader.LoadBitmap(path, size);
193 private:
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);
204 } // namespace
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(),
230 extension.get(),
231 extensions::IconsInfo::GetIcons(extension.get()),
233 default_icon,
234 this);
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)));
249 WaitForImageLoad();
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)));
266 WaitForImageLoad();
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
279 // resource.
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(),
298 extension.get(),
299 extensions::IconsInfo::GetIcons(extension.get()),
301 default_icon,
302 this);
304 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
306 WaitForImageLoad();
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
313 // size.
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(),
336 extension.get(),
337 extensions::IconsInfo::GetIcons(extension.get()),
339 default_icon,
340 this);
342 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
344 WaitForImageLoad();
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(),
370 extension.get(),
371 empty_icon_set,
372 kRequestedSize,
373 default_icon,
374 this);
376 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
377 EXPECT_TRUE(gfx::BitmapsAreEqual(
378 representation.sk_bitmap(),
379 EnsureBitmapSize(
380 default_icon.GetRepresentation(1.0f).sk_bitmap(),
381 kRequestedSize)));
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(),
390 EnsureBitmapSize(
391 default_icon.GetRepresentation(1.0f).sk_bitmap(),
392 kRequestedSize)));
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(),
411 extension.get(),
412 invalid_icon_set,
413 kInvalidIconSize,
414 default_icon,
415 this);
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)));
422 WaitForImageLoad();
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(),
430 EnsureBitmapSize(
431 default_icon.GetRepresentation(1.0f).sk_bitmap(),
432 kInvalidIconSize)));
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(),
451 extension.get(),
452 empty_icon_set,
453 kRequestedSize,
454 lazy_default_icon,
455 this);
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(),
465 EnsureBitmapSize(
466 default_icon.GetRepresentation(1.0f).sk_bitmap(),
467 kRequestedSize)));
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(),
490 extension.get(),
491 invalid_icon_set,
492 kInvalidIconSize,
493 lazy_default_icon,
494 this);
496 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
498 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
500 WaitForImageLoad();
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(),
510 EnsureBitmapSize(
511 default_icon.GetRepresentation(1.0f).sk_bitmap(),
512 kInvalidIconSize)));
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(),
534 extension.get(),
535 extensions::IconsInfo::GetIcons(extension.get()),
537 default_icon,
538 this));
540 // Load an image representation.
541 gfx::ImageSkiaRep representation =
542 image->image_skia().GetRepresentation(1.0f);
544 WaitForImageLoad();
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();
550 image.reset();
551 extension = NULL;
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
562 // icon.
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());