Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / extensions / extension_icon_image_unittest.cc
blob53bda301bf021a9b28ed59e6830bf214ad41f46d
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/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/common/extensions/manifest_handlers/icons_handler.h"
14 #include "chrome/test/base/testing_profile.h"
15 #include "content/public/test/test_browser_thread.h"
16 #include "extensions/common/extension.h"
17 #include "extensions/common/manifest.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;
30 namespace {
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;
43 SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
44 if (original.width() == size && original.height() == size)
45 return original;
47 SkBitmap resized = skia::ImageOperations::Resize(
48 original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
49 return resized;
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) {
60 virtual ~MockImageSkiaSource() {}
62 virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
63 return image_.GetRepresentation(scale);
66 private:
67 gfx::ImageSkia image_;
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) {
78 virtual ~TestImageLoader() {}
80 void OnImageLoaded(const gfx::Image& image) {
81 image_ = image;
82 image_loaded_ = true;
83 if (waiting_)
84 base::MessageLoop::current()->Quit();
87 SkBitmap LoadBitmap(const std::string& path,
88 int size) {
89 image_loaded_ = false;
91 image_loader_.LoadImageAsync(
92 extension_, extension_->GetResource(path), gfx::Size(size, size),
93 base::Bind(&TestImageLoader::OnImageLoaded,
94 base::Unretained(this)));
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;
104 EXPECT_TRUE(image_loaded_);
106 return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
109 private:
110 const Extension* extension_;
111 bool waiting_;
112 bool image_loaded_;
113 gfx::Image image_;
114 extensions::ImageLoader image_loader_;
116 DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
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) {
130 virtual ~ExtensionIconImageTest() {}
132 void WaitForImageLoad() {
133 quit_in_image_loaded_ = true;
134 base::MessageLoop::current()->Run();
135 quit_in_image_loaded_ = false;
138 int ImageLoadedCount() {
139 int result = image_loaded_count_;
140 image_loaded_count_ = 0;
141 return result;
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;
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;
163 EXPECT_TRUE(valid_value.get());
164 if (!valid_value)
165 return NULL;
167 return Extension::Create(test_file, location, *valid_value,
168 Extension::NO_FLAGS, &error);
171 // testing::Test overrides:
172 virtual void SetUp() OVERRIDE {
173 file_thread_.Start();
174 io_thread_.Start();
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();
184 gfx::ImageSkia GetDefaultIcon() {
185 return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
186 IDR_EXTENSIONS_FAVICON);
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);
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_;
206 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
209 } // namespace
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);
221 gfx::ImageSkia default_icon = GetDefaultIcon();
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());
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());
234 IconImage image(profile.get(),
235 extension.get(),
236 extensions::IconsInfo::GetIcons(extension.get()),
238 default_icon,
239 this);
241 // No representations in |image_| yet.
242 gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
243 ASSERT_EQ(0u, image_reps.size());
245 // Gets representation for a scale factor.
246 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
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)));
254 WaitForImageLoad();
255 EXPECT_EQ(1, ImageLoadedCount());
256 ASSERT_EQ(1u, image.image_skia().image_reps().size());
258 representation = image.image_skia().GetRepresentation(1.0f);
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());
264 // Gets representation for an additional scale factor.
265 representation = image.image_skia().GetRepresentation(2.0f);
267 EXPECT_TRUE(gfx::BitmapsAreEqual(
268 representation.sk_bitmap(),
269 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
271 WaitForImageLoad();
272 EXPECT_EQ(1, ImageLoadedCount());
273 ASSERT_EQ(2u, image.image_skia().image_reps().size());
275 representation = image.image_skia().GetRepresentation(2.0f);
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));
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);
295 gfx::ImageSkia default_icon = GetDefaultIcon();
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());
302 IconImage image(profile.get(),
303 extension.get(),
304 extensions::IconsInfo::GetIcons(extension.get()),
306 default_icon,
307 this);
309 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
311 WaitForImageLoad();
312 EXPECT_EQ(1, ImageLoadedCount());
313 ASSERT_EQ(1u, image.image_skia().image_reps().size());
315 representation = image.image_skia().GetRepresentation(2.0f);
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)));
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);
334 gfx::ImageSkia default_icon = GetDefaultIcon();
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());
341 IconImage image(profile.get(),
342 extension.get(),
343 extensions::IconsInfo::GetIcons(extension.get()),
345 default_icon,
346 this);
348 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
350 WaitForImageLoad();
351 EXPECT_EQ(1, ImageLoadedCount());
352 ASSERT_EQ(1u, image.image_skia().image_reps().size());
354 representation = image.image_skia().GetRepresentation(1.0f);
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)));
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);
371 ExtensionIconSet empty_icon_set;
372 gfx::ImageSkia default_icon = GetDefaultIcon();
374 const int kRequestedSize = 24;
375 IconImage image(profile.get(),
376 extension.get(),
377 empty_icon_set,
378 kRequestedSize,
379 default_icon,
380 this);
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)));
389 EXPECT_EQ(0, ImageLoadedCount());
390 // We should have a default icon representation.
391 ASSERT_EQ(1u, image.image_skia().image_reps().size());
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)));
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);
410 const int kInvalidIconSize = 24;
411 ExtensionIconSet invalid_icon_set;
412 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
414 gfx::ImageSkia default_icon = GetDefaultIcon();
416 IconImage image(profile.get(),
417 extension.get(),
418 invalid_icon_set,
419 kInvalidIconSize,
420 default_icon,
421 this);
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)));
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());
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)));
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);
449 gfx::ImageSkia default_icon = GetDefaultIcon();
450 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
451 default_icon.size());
453 ExtensionIconSet empty_icon_set;
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);
463 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
465 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
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)));
475 // We should have a default icon representation.
476 ASSERT_EQ(1u, image.image_skia().image_reps().size());
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);
487 gfx::ImageSkia default_icon = GetDefaultIcon();
488 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
489 default_icon.size());
491 const int kInvalidIconSize = 24;
492 ExtensionIconSet invalid_icon_set;
493 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
495 IconImage image(profile.get(),
496 extension.get(),
497 invalid_icon_set,
498 kInvalidIconSize,
499 lazy_default_icon,
500 this);
502 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
504 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
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());
511 EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
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)));
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(),
540 extension.get(),
541 extensions::IconsInfo::GetIcons(extension.get()),
543 default_icon,
544 this));
546 // Load an image representation.
547 gfx::ImageSkiaRep representation =
548 image->image_skia().GetRepresentation(1.0f);
550 WaitForImageLoad();
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();
556 image.reset();
557 extension = NULL;
559 // Image skia should still be able to get previously loaded representation.
560 representation = image_skia.GetRepresentation(1.0f);
562 EXPECT_EQ(1.0f, representation.scale());
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(2.0f);
569 EXPECT_TRUE(gfx::BitmapsAreEqual(
570 representation.sk_bitmap(),
571 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));