Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / extension_icon_image_unittest.cc
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.
4
5 #include "chrome/browser/extensions/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/browser/extensions/image_loader.h"
11 #include "chrome/common/chrome_paths.h"
12 #include "chrome/common/extensions/extension_constants.h"
13 #include "chrome/test/base/testing_profile.h"
14 #include "content/public/test/test_browser_thread.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/manifest.h"
17 #include "extensions/common/manifest_handlers/icons_handler.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"
24
25 using content::BrowserThread;
26 using extensions::Extension;
27 using extensions::IconImage;
28 using extensions::Manifest;
29
30 namespace {
31
32 SkBitmap CreateBlankBitmapForScale(int size_dip, ui::ScaleFactor scale_factor) {
33   SkBitmap bitmap;
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));
38   bitmap.allocPixels();
39   bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
40   return bitmap;
41 }
42
43 SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
44   if (original.width() == size && original.height() == size)
45     return original;
46
47   SkBitmap resized = skia::ImageOperations::Resize(
48       original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
49   return resized;
50 }
51
52 // Used to test behavior including images defined by an image skia source.
53 // |GetImageForScale| simply returns image representation from the image given
54 // in the ctor.
55 class MockImageSkiaSource : public gfx::ImageSkiaSource {
56  public:
57   explicit MockImageSkiaSource(const gfx::ImageSkia& image)
58       : image_(image) {
59   }
60   virtual ~MockImageSkiaSource() {}
61
62   virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
63     return image_.GetRepresentation(scale);
64   }
65
66  private:
67   gfx::ImageSkia image_;
68 };
69
70 // Helper class for synchronously loading extension image resource.
71 class TestImageLoader {
72  public:
73   explicit TestImageLoader(const Extension* extension)
74       : extension_(extension),
75         waiting_(false),
76         image_loaded_(false) {
77   }
78   virtual ~TestImageLoader() {}
79
80   void OnImageLoaded(const gfx::Image& image) {
81     image_ = image;
82     image_loaded_ = true;
83     if (waiting_)
84       base::MessageLoop::current()->Quit();
85   }
86
87   SkBitmap LoadBitmap(const std::string& path,
88                       int size) {
89     image_loaded_ = false;
90
91     image_loader_.LoadImageAsync(
92         extension_, extension_->GetResource(path), gfx::Size(size, size),
93         base::Bind(&TestImageLoader::OnImageLoaded,
94                    base::Unretained(this)));
95
96     // If |image_| still hasn't been loaded (i.e. it is being loaded
97     // asynchronously), wait for it.
98     if (!image_loaded_) {
99       waiting_ = true;
100       base::MessageLoop::current()->Run();
101       waiting_ = false;
102     }
103
104     EXPECT_TRUE(image_loaded_);
105
106     return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
107   }
108
109  private:
110   const Extension* extension_;
111   bool waiting_;
112   bool image_loaded_;
113   gfx::Image image_;
114   extensions::ImageLoader image_loader_;
115
116   DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
117 };
118
119 class ExtensionIconImageTest : public testing::Test,
120                                public IconImage::Observer {
121  public:
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) {
128   }
129
130   virtual ~ExtensionIconImageTest() {}
131
132   void WaitForImageLoad() {
133     quit_in_image_loaded_ = true;
134     base::MessageLoop::current()->Run();
135     quit_in_image_loaded_ = false;
136   }
137
138   int ImageLoadedCount() {
139     int result = image_loaded_count_;
140     image_loaded_count_ = 0;
141     return result;
142   }
143
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)) {
149       EXPECT_FALSE(true);
150       return NULL;
151     }
152     test_file = test_file.AppendASCII("extensions").AppendASCII(name);
153     int error_code = 0;
154     std::string error;
155     JSONFileValueSerializer serializer(test_file.AppendASCII("app.json"));
156     scoped_ptr<base::DictionaryValue> valid_value(
157         static_cast<base::DictionaryValue*>(serializer.Deserialize(&error_code,
158                                                                    &error)));
159     EXPECT_EQ(0, error_code) << error;
160     if (error_code != 0)
161       return NULL;
162
163     EXPECT_TRUE(valid_value.get());
164     if (!valid_value)
165       return NULL;
166
167     return Extension::Create(test_file, location, *valid_value,
168                              Extension::NO_FLAGS, &error);
169   }
170
171   // testing::Test overrides:
172   virtual void SetUp() OVERRIDE {
173     file_thread_.Start();
174     io_thread_.Start();
175   }
176
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();
182   }
183
184   gfx::ImageSkia GetDefaultIcon() {
185     return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
186         IDR_EXTENSIONS_FAVICON);
187   }
188
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,
193                          int size) {
194     TestImageLoader image_loader(extension);
195     return image_loader.LoadBitmap(path, size);
196   }
197
198  private:
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_;
205
206   DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
207 };
208
209 }  // namespace
210
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);
220
221   gfx::ImageSkia default_icon = GetDefaultIcon();
222
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());
227
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());
233
234   IconImage image(profile.get(),
235                   extension.get(),
236                   extensions::IconsInfo::GetIcons(extension.get()),
237                   16,
238                   default_icon,
239                   this);
240
241   // No representations in |image_| yet.
242   gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
243   ASSERT_EQ(0u, image_reps.size());
244
245   // Gets representation for a scale factor.
246   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
247
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)));
253
254   WaitForImageLoad();
255   EXPECT_EQ(1, ImageLoadedCount());
256   ASSERT_EQ(1u, image.image_skia().image_reps().size());
257
258   representation = image.image_skia().GetRepresentation(1.0f);
259
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());
263
264   // Gets representation for an additional scale factor.
265   representation = image.image_skia().GetRepresentation(2.0f);
266
267   EXPECT_TRUE(gfx::BitmapsAreEqual(
268       representation.sk_bitmap(),
269       CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
270
271   WaitForImageLoad();
272   EXPECT_EQ(1, ImageLoadedCount());
273   ASSERT_EQ(2u, image.image_skia().image_reps().size());
274
275   representation = image.image_skia().GetRepresentation(2.0f);
276
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));
281 }
282
283 // There is no resource with either exact or bigger size, but there is a smaller
284 // resource.
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);
294
295   gfx::ImageSkia default_icon = GetDefaultIcon();
296
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());
301
302   IconImage image(profile.get(),
303                   extension.get(),
304                   extensions::IconsInfo::GetIcons(extension.get()),
305                   32,
306                   default_icon,
307                   this);
308
309   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
310
311   WaitForImageLoad();
312   EXPECT_EQ(1, ImageLoadedCount());
313   ASSERT_EQ(1u, image.image_skia().image_reps().size());
314
315   representation = image.image_skia().GetRepresentation(2.0f);
316
317   // We should have loaded the biggest smaller resource resized to the actual
318   // size.
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)));
323 }
324
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
327 // be loaded.
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);
333
334   gfx::ImageSkia default_icon = GetDefaultIcon();
335
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());
340
341   IconImage image(profile.get(),
342                   extension.get(),
343                   extensions::IconsInfo::GetIcons(extension.get()),
344                   17,
345                   default_icon,
346                   this);
347
348   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
349
350   WaitForImageLoad();
351   EXPECT_EQ(1, ImageLoadedCount());
352   ASSERT_EQ(1u, image.image_skia().image_reps().size());
353
354   representation = image.image_skia().GetRepresentation(1.0f);
355
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)));
361 }
362
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);
370
371   ExtensionIconSet empty_icon_set;
372   gfx::ImageSkia default_icon = GetDefaultIcon();
373
374   const int kRequestedSize = 24;
375   IconImage image(profile.get(),
376                   extension.get(),
377                   empty_icon_set,
378                   kRequestedSize,
379                   default_icon,
380                   this);
381
382   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
383   EXPECT_TRUE(gfx::BitmapsAreEqual(
384       representation.sk_bitmap(),
385       EnsureBitmapSize(
386           default_icon.GetRepresentation(1.0f).sk_bitmap(),
387           kRequestedSize)));
388
389   EXPECT_EQ(0, ImageLoadedCount());
390   // We should have a default icon representation.
391   ASSERT_EQ(1u, image.image_skia().image_reps().size());
392
393   representation = image.image_skia().GetRepresentation(1.0f);
394   EXPECT_TRUE(gfx::BitmapsAreEqual(
395       representation.sk_bitmap(),
396       EnsureBitmapSize(
397           default_icon.GetRepresentation(1.0f).sk_bitmap(),
398           kRequestedSize)));
399 }
400
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);
409
410   const int kInvalidIconSize = 24;
411   ExtensionIconSet invalid_icon_set;
412   invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
413
414   gfx::ImageSkia default_icon = GetDefaultIcon();
415
416   IconImage image(profile.get(),
417                   extension.get(),
418                   invalid_icon_set,
419                   kInvalidIconSize,
420                   default_icon,
421                   this);
422
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)));
427
428   WaitForImageLoad();
429   EXPECT_EQ(1, ImageLoadedCount());
430   // We should have default icon representation now.
431   ASSERT_EQ(1u, image.image_skia().image_reps().size());
432
433   representation = image.image_skia().GetRepresentation(1.0f);
434   EXPECT_TRUE(gfx::BitmapsAreEqual(
435       representation.sk_bitmap(),
436       EnsureBitmapSize(
437           default_icon.GetRepresentation(1.0f).sk_bitmap(),
438           kInvalidIconSize)));
439 }
440
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);
448
449   gfx::ImageSkia default_icon = GetDefaultIcon();
450   gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
451                                     default_icon.size());
452
453   ExtensionIconSet empty_icon_set;
454
455   const int kRequestedSize = 128;
456   IconImage image(profile.get(),
457                   extension.get(),
458                   empty_icon_set,
459                   kRequestedSize,
460                   lazy_default_icon,
461                   this);
462
463   ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
464
465   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
466
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(),
471       EnsureBitmapSize(
472           default_icon.GetRepresentation(1.0f).sk_bitmap(),
473           kRequestedSize)));
474
475   // We should have a default icon representation.
476   ASSERT_EQ(1u, image.image_skia().image_reps().size());
477 }
478
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);
486
487   gfx::ImageSkia default_icon = GetDefaultIcon();
488   gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
489                                     default_icon.size());
490
491   const int kInvalidIconSize = 24;
492   ExtensionIconSet invalid_icon_set;
493   invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
494
495   IconImage image(profile.get(),
496                   extension.get(),
497                   invalid_icon_set,
498                   kInvalidIconSize,
499                   lazy_default_icon,
500                   this);
501
502   ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
503
504   gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
505
506   WaitForImageLoad();
507   EXPECT_EQ(1, ImageLoadedCount());
508   // We should have default icon representation now.
509   ASSERT_EQ(1u, image.image_skia().image_reps().size());
510
511   EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
512
513   representation = image.image_skia().GetRepresentation(1.0f);
514   EXPECT_TRUE(gfx::BitmapsAreEqual(
515       representation.sk_bitmap(),
516       EnsureBitmapSize(
517           default_icon.GetRepresentation(1.0f).sk_bitmap(),
518           kInvalidIconSize)));
519 }
520
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);
530
531   gfx::ImageSkia default_icon = GetDefaultIcon();
532
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());
537
538   scoped_ptr<IconImage> image(
539       new IconImage(profile.get(),
540                     extension.get(),
541                     extensions::IconsInfo::GetIcons(extension.get()),
542                     16,
543                     default_icon,
544                     this));
545
546   // Load an image representation.
547   gfx::ImageSkiaRep representation =
548       image->image_skia().GetRepresentation(1.0f);
549
550   WaitForImageLoad();
551   EXPECT_EQ(1, ImageLoadedCount());
552   ASSERT_EQ(1u, image->image_skia().image_reps().size());
553
554   // Stash loaded image skia, and destroy |image|.
555   gfx::ImageSkia image_skia = image->image_skia();
556   image.reset();
557   extension = NULL;
558
559   // Image skia should still be able to get previously loaded representation.
560   representation = image_skia.GetRepresentation(1.0f);
561
562   EXPECT_EQ(1.0f, representation.scale());
563   EXPECT_EQ(16, representation.pixel_width());
564   EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
565
566   // When requesting another representation, we should get blank image.
567   representation = image_skia.GetRepresentation(2.0f);
568
569   EXPECT_TRUE(gfx::BitmapsAreEqual(
570       representation.sk_bitmap(),
571       CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
572 }