ead9dc04eabfebc9ea3f7e7ea2e79cb5544d4234
[platform/framework/web/crosswalk.git] / src / extensions / browser / extension_icon_image_unittest.cc
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.
4
5 #include "extensions/browser/extension_icon_image.h"
6
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"
22
23 using content::BrowserThread;
24 using extensions::Extension;
25 using extensions::IconImage;
26 using extensions::Manifest;
27
28 namespace {
29
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;
37 }
38
39 SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
40   if (original.width() == size && original.height() == size)
41     return original;
42
43   SkBitmap resized = skia::ImageOperations::Resize(
44       original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
45   return resized;
46 }
47
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) {
55   }
56   virtual ~MockImageSkiaSource() {}
57
58   virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
59     return image_.GetRepresentation(scale);
60   }
61
62  private:
63   gfx::ImageSkia image_;
64 };
65
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) {
73   }
74   virtual ~TestImageLoader() {}
75
76   void OnImageLoaded(const gfx::Image& image) {
77     image_ = image;
78     image_loaded_ = true;
79     if (waiting_)
80       base::MessageLoop::current()->Quit();
81   }
82
83   SkBitmap LoadBitmap(const std::string& path,
84                       int size) {
85     image_loaded_ = false;
86
87     image_loader_.LoadImageAsync(
88         extension_, extension_->GetResource(path), gfx::Size(size, size),
89         base::Bind(&TestImageLoader::OnImageLoaded,
90                    base::Unretained(this)));
91
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;
98     }
99
100     EXPECT_TRUE(image_loaded_);
101
102     return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
103   }
104
105  private:
106   const Extension* extension_;
107   bool waiting_;
108   bool image_loaded_;
109   gfx::Image image_;
110   extensions::ImageLoader image_loader_;
111
112   DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
113 };
114
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) {
124   }
125
126   virtual ~ExtensionIconImageTest() {}
127
128   void WaitForImageLoad() {
129     quit_in_image_loaded_ = true;
130     base::MessageLoop::current()->Run();
131     quit_in_image_loaded_ = false;
132   }
133
134   int ImageLoadedCount() {
135     int result = image_loaded_count_;
136     image_loaded_count_ = 0;
137     return result;
138   }
139
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;
147     }
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;
158
159     EXPECT_TRUE(valid_value.get());
160     if (!valid_value)
161       return NULL;
162
163     return Extension::Create(test_file, location, *valid_value,
164                              Extension::NO_FLAGS, &error);
165   }
166
167   // testing::Test overrides:
168   virtual void SetUp() OVERRIDE {
169     file_thread_.Start();
170     io_thread_.Start();
171   }
172
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();
178   }
179
180   gfx::ImageSkia GetDefaultIcon() {
181     return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f));
182   }
183
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);
191   }
192
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_;
200
201   DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
202 };
203
204 }  // namespace
205
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);
215
216   gfx::ImageSkia default_icon = GetDefaultIcon();
217
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());
222
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());
228
229   IconImage image(profile.get(),
230                   extension.get(),
231                   extensions::IconsInfo::GetIcons(extension.get()),
232                   16,
233                   default_icon,
234                   this);
235
236   // No representations in |image_| yet.
237   gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
238   ASSERT_EQ(0u, image_reps.size());
239
240   // Gets representation for a scale factor.
241   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
242
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)));
248
249   WaitForImageLoad();
250   EXPECT_EQ(1, ImageLoadedCount());
251   ASSERT_EQ(1u, image.image_skia().image_reps().size());
252
253   representation = image.image_skia().GetRepresentation(1.0f);
254
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());
258
259   // Gets representation for an additional scale factor.
260   representation = image.image_skia().GetRepresentation(2.0f);
261
262   EXPECT_TRUE(gfx::BitmapsAreEqual(
263       representation.sk_bitmap(),
264       CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
265
266   WaitForImageLoad();
267   EXPECT_EQ(1, ImageLoadedCount());
268   ASSERT_EQ(2u, image.image_skia().image_reps().size());
269
270   representation = image.image_skia().GetRepresentation(2.0f);
271
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));
276 }
277
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);
289
290   gfx::ImageSkia default_icon = GetDefaultIcon();
291
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());
296
297   IconImage image(profile.get(),
298                   extension.get(),
299                   extensions::IconsInfo::GetIcons(extension.get()),
300                   32,
301                   default_icon,
302                   this);
303
304   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
305
306   WaitForImageLoad();
307   EXPECT_EQ(1, ImageLoadedCount());
308   ASSERT_EQ(1u, image.image_skia().image_reps().size());
309
310   representation = image.image_skia().GetRepresentation(2.0f);
311
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)));
318 }
319
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
322 // be loaded.
323 TEST_F(ExtensionIconImageTest, FallbackToSmaller) {
324   scoped_ptr<content::BrowserContext> profile(new TestingProfile());
325   scoped_refptr<Extension> extension(CreateExtension(
326       "extension_icon_image", Manifest::INVALID_LOCATION));
327   ASSERT_TRUE(extension.get() != NULL);
328
329   gfx::ImageSkia default_icon = GetDefaultIcon();
330
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());
335
336   IconImage image(profile.get(),
337                   extension.get(),
338                   extensions::IconsInfo::GetIcons(extension.get()),
339                   17,
340                   default_icon,
341                   this);
342
343   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
344
345   WaitForImageLoad();
346   EXPECT_EQ(1, ImageLoadedCount());
347   ASSERT_EQ(1u, image.image_skia().image_reps().size());
348
349   representation = image.image_skia().GetRepresentation(1.0f);
350
351   // We should have loaded smaller (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_16, 17)));
356 }
357
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_ptr<content::BrowserContext> profile(new TestingProfile());
362   scoped_refptr<Extension> extension(CreateExtension(
363       "extension_icon_image", Manifest::INVALID_LOCATION));
364   ASSERT_TRUE(extension.get() != NULL);
365
366   ExtensionIconSet empty_icon_set;
367   gfx::ImageSkia default_icon = GetDefaultIcon();
368
369   const int kRequestedSize = 24;
370   IconImage image(profile.get(),
371                   extension.get(),
372                   empty_icon_set,
373                   kRequestedSize,
374                   default_icon,
375                   this);
376
377   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
378   EXPECT_TRUE(gfx::BitmapsAreEqual(
379       representation.sk_bitmap(),
380       EnsureBitmapSize(
381           default_icon.GetRepresentation(1.0f).sk_bitmap(),
382           kRequestedSize)));
383
384   EXPECT_EQ(0, ImageLoadedCount());
385   // We should have a default icon representation.
386   ASSERT_EQ(1u, image.image_skia().image_reps().size());
387
388   representation = image.image_skia().GetRepresentation(1.0f);
389   EXPECT_TRUE(gfx::BitmapsAreEqual(
390       representation.sk_bitmap(),
391       EnsureBitmapSize(
392           default_icon.GetRepresentation(1.0f).sk_bitmap(),
393           kRequestedSize)));
394 }
395
396 // If resource set is invalid, image load should be done asynchronously and
397 // the observer should be notified when it's done. |GetRepresentation| should
398 // return the default icon representation once image load is done.
399 TEST_F(ExtensionIconImageTest, InvalidResource) {
400   scoped_ptr<content::BrowserContext> profile(new TestingProfile());
401   scoped_refptr<Extension> extension(CreateExtension(
402       "extension_icon_image", Manifest::INVALID_LOCATION));
403   ASSERT_TRUE(extension.get() != NULL);
404
405   const int kInvalidIconSize = 24;
406   ExtensionIconSet invalid_icon_set;
407   invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
408
409   gfx::ImageSkia default_icon = GetDefaultIcon();
410
411   IconImage image(profile.get(),
412                   extension.get(),
413                   invalid_icon_set,
414                   kInvalidIconSize,
415                   default_icon,
416                   this);
417
418   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
419   EXPECT_TRUE(gfx::BitmapsAreEqual(
420       representation.sk_bitmap(),
421       CreateBlankBitmapForScale(kInvalidIconSize, ui::SCALE_FACTOR_100P)));
422
423   WaitForImageLoad();
424   EXPECT_EQ(1, ImageLoadedCount());
425   // We should have default icon representation now.
426   ASSERT_EQ(1u, image.image_skia().image_reps().size());
427
428   representation = image.image_skia().GetRepresentation(1.0f);
429   EXPECT_TRUE(gfx::BitmapsAreEqual(
430       representation.sk_bitmap(),
431       EnsureBitmapSize(
432           default_icon.GetRepresentation(1.0f).sk_bitmap(),
433           kInvalidIconSize)));
434 }
435
436 // Test that IconImage works with lazily (but synchronously) created default
437 // icon when IconImage returns synchronously.
438 TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
439   scoped_ptr<content::BrowserContext> profile(new TestingProfile());
440   scoped_refptr<Extension> extension(CreateExtension(
441       "extension_icon_image", Manifest::INVALID_LOCATION));
442   ASSERT_TRUE(extension.get() != NULL);
443
444   gfx::ImageSkia default_icon = GetDefaultIcon();
445   gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
446                                     default_icon.size());
447
448   ExtensionIconSet empty_icon_set;
449
450   const int kRequestedSize = 128;
451   IconImage image(profile.get(),
452                   extension.get(),
453                   empty_icon_set,
454                   kRequestedSize,
455                   lazy_default_icon,
456                   this);
457
458   ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
459
460   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
461
462   // The resouce set is empty, so we should get the result right away.
463   EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
464   EXPECT_TRUE(gfx::BitmapsAreEqual(
465       representation.sk_bitmap(),
466       EnsureBitmapSize(
467           default_icon.GetRepresentation(1.0f).sk_bitmap(),
468           kRequestedSize)));
469
470   // We should have a default icon representation.
471   ASSERT_EQ(1u, image.image_skia().image_reps().size());
472 }
473
474 // Test that IconImage works with lazily (but synchronously) created default
475 // icon when IconImage returns asynchronously.
476 TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
477   scoped_ptr<content::BrowserContext> profile(new TestingProfile());
478   scoped_refptr<Extension> extension(CreateExtension(
479       "extension_icon_image", Manifest::INVALID_LOCATION));
480   ASSERT_TRUE(extension.get() != NULL);
481
482   gfx::ImageSkia default_icon = GetDefaultIcon();
483   gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
484                                     default_icon.size());
485
486   const int kInvalidIconSize = 24;
487   ExtensionIconSet invalid_icon_set;
488   invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
489
490   IconImage image(profile.get(),
491                   extension.get(),
492                   invalid_icon_set,
493                   kInvalidIconSize,
494                   lazy_default_icon,
495                   this);
496
497   ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
498
499   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
500
501   WaitForImageLoad();
502   EXPECT_EQ(1, ImageLoadedCount());
503   // We should have default icon representation now.
504   ASSERT_EQ(1u, image.image_skia().image_reps().size());
505
506   EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
507
508   representation = image.image_skia().GetRepresentation(1.0f);
509   EXPECT_TRUE(gfx::BitmapsAreEqual(
510       representation.sk_bitmap(),
511       EnsureBitmapSize(
512           default_icon.GetRepresentation(1.0f).sk_bitmap(),
513           kInvalidIconSize)));
514 }
515
516 // Tests behavior of image created by IconImage after IconImage host goes
517 // away. The image should still return loaded representations. If requested
518 // representation was not loaded while IconImage host was around, transparent
519 // representations should be returned.
520 TEST_F(ExtensionIconImageTest, IconImageDestruction) {
521   scoped_ptr<content::BrowserContext> profile(new TestingProfile());
522   scoped_refptr<Extension> extension(CreateExtension(
523       "extension_icon_image", Manifest::INVALID_LOCATION));
524   ASSERT_TRUE(extension.get() != NULL);
525
526   gfx::ImageSkia default_icon = GetDefaultIcon();
527
528   // Load images we expect to find as representations in icon_image, so we
529   // can later use them to validate icon_image.
530   SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
531   ASSERT_FALSE(bitmap_16.empty());
532
533   scoped_ptr<IconImage> image(
534       new IconImage(profile.get(),
535                     extension.get(),
536                     extensions::IconsInfo::GetIcons(extension.get()),
537                     16,
538                     default_icon,
539                     this));
540
541   // Load an image representation.
542   gfx::ImageSkiaRep representation =
543       image->image_skia().GetRepresentation(1.0f);
544
545   WaitForImageLoad();
546   EXPECT_EQ(1, ImageLoadedCount());
547   ASSERT_EQ(1u, image->image_skia().image_reps().size());
548
549   // Stash loaded image skia, and destroy |image|.
550   gfx::ImageSkia image_skia = image->image_skia();
551   image.reset();
552   extension = NULL;
553
554   // Image skia should still be able to get previously loaded representation.
555   representation = image_skia.GetRepresentation(1.0f);
556
557   EXPECT_EQ(1.0f, representation.scale());
558   EXPECT_EQ(16, representation.pixel_width());
559   EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
560
561   // When requesting another representation, we should not crash and return some
562   // image of the size. It could be blank or a rescale from the existing 1.0f
563   // icon.
564   representation = image_skia.GetRepresentation(2.0f);
565
566   EXPECT_EQ(16, representation.GetWidth());
567   EXPECT_EQ(16, representation.GetHeight());
568   EXPECT_EQ(2.0f, representation.scale());
569 }