Make default apps cache multiprofile friendly
[chromium-blink-merge.git] / chrome / browser / extensions / extension_icon_image_unittest.cc
blob0d9b9a764877ee95f4e73bf2b711a2bbbe284f71
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.h"
13 #include "chrome/common/extensions/extension_constants.h"
14 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
15 #include "chrome/test/base/testing_profile.h"
16 #include "content/public/test/test_browser_thread.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::GetScaleFactorScale(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(
63 ui::ScaleFactor scale_factor) OVERRIDE {
64 return image_.GetRepresentation(scale_factor);
67 private:
68 gfx::ImageSkia image_;
71 // Helper class for synchronously loading extension image resource.
72 class TestImageLoader {
73 public:
74 explicit TestImageLoader(const Extension* extension)
75 : extension_(extension),
76 waiting_(false),
77 image_loaded_(false) {
79 virtual ~TestImageLoader() {}
81 void OnImageLoaded(const gfx::Image& image) {
82 image_ = image;
83 image_loaded_ = true;
84 if (waiting_)
85 base::MessageLoop::current()->Quit();
88 SkBitmap LoadBitmap(const std::string& path,
89 int size) {
90 image_loaded_ = false;
92 image_loader_.LoadImageAsync(
93 extension_, extension_->GetResource(path), gfx::Size(size, size),
94 base::Bind(&TestImageLoader::OnImageLoaded,
95 base::Unretained(this)));
97 // If |image_| still hasn't been loaded (i.e. it is being loaded
98 // asynchronously), wait for it.
99 if (!image_loaded_) {
100 waiting_ = true;
101 base::MessageLoop::current()->Run();
102 waiting_ = false;
105 EXPECT_TRUE(image_loaded_);
107 return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
110 private:
111 const Extension* extension_;
112 bool waiting_;
113 bool image_loaded_;
114 gfx::Image image_;
115 extensions::ImageLoader image_loader_;
117 DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
120 class ExtensionIconImageTest : public testing::Test,
121 public IconImage::Observer {
122 public:
123 ExtensionIconImageTest()
124 : image_loaded_count_(0),
125 quit_in_image_loaded_(false),
126 ui_thread_(BrowserThread::UI, &ui_loop_),
127 file_thread_(BrowserThread::FILE),
128 io_thread_(BrowserThread::IO) {
131 virtual ~ExtensionIconImageTest() {}
133 void WaitForImageLoad() {
134 quit_in_image_loaded_ = true;
135 base::MessageLoop::current()->Run();
136 quit_in_image_loaded_ = false;
139 int ImageLoadedCount() {
140 int result = image_loaded_count_;
141 image_loaded_count_ = 0;
142 return result;
145 scoped_refptr<Extension> CreateExtension(const char* name,
146 Manifest::Location location) {
147 // Create and load an extension.
148 base::FilePath test_file;
149 if (!PathService::Get(chrome::DIR_TEST_DATA, &test_file)) {
150 EXPECT_FALSE(true);
151 return NULL;
153 test_file = test_file.AppendASCII("extensions").AppendASCII(name);
154 int error_code = 0;
155 std::string error;
156 JSONFileValueSerializer serializer(test_file.AppendASCII("app.json"));
157 scoped_ptr<DictionaryValue> valid_value(
158 static_cast<DictionaryValue*>(serializer.Deserialize(&error_code,
159 &error)));
160 EXPECT_EQ(0, error_code) << error;
161 if (error_code != 0)
162 return NULL;
164 EXPECT_TRUE(valid_value.get());
165 if (!valid_value)
166 return NULL;
168 return Extension::Create(test_file, location, *valid_value,
169 Extension::NO_FLAGS, &error);
172 // testing::Test overrides:
173 virtual void SetUp() OVERRIDE {
174 file_thread_.Start();
175 io_thread_.Start();
178 // IconImage::Delegate overrides:
179 virtual void OnExtensionIconImageChanged(IconImage* image) OVERRIDE {
180 image_loaded_count_++;
181 if (quit_in_image_loaded_)
182 base::MessageLoop::current()->Quit();
185 gfx::ImageSkia GetDefaultIcon() {
186 return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
187 IDR_EXTENSIONS_FAVICON);
190 // Loads an image to be used in test from the extension.
191 // The image will be loaded from the relative path |path|.
192 SkBitmap GetTestBitmap(const Extension* extension,
193 const std::string& path,
194 int size) {
195 TestImageLoader image_loader(extension);
196 return image_loader.LoadBitmap(path, size);
199 private:
200 int image_loaded_count_;
201 bool quit_in_image_loaded_;
202 base::MessageLoop ui_loop_;
203 content::TestBrowserThread ui_thread_;
204 content::TestBrowserThread file_thread_;
205 content::TestBrowserThread io_thread_;
207 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
210 } // namespace
212 TEST_F(ExtensionIconImageTest, Basic) {
213 scoped_ptr<Profile> profile(new TestingProfile());
214 scoped_refptr<Extension> extension(CreateExtension(
215 "extension_icon_image", Manifest::INVALID_LOCATION));
216 ASSERT_TRUE(extension.get() != NULL);
218 gfx::ImageSkia default_icon = GetDefaultIcon();
220 // Load images we expect to find as representations in icon_image, so we
221 // can later use them to validate icon_image.
222 SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
223 ASSERT_FALSE(bitmap_16.empty());
225 // There is no image of size 32 defined in the extension manifest, so we
226 // should expect manifest image of size 48 resized to size 32.
227 SkBitmap bitmap_48_resized_to_32 =
228 GetTestBitmap(extension.get(), "48.png", 32);
229 ASSERT_FALSE(bitmap_48_resized_to_32.empty());
231 IconImage image(profile.get(),
232 extension.get(),
233 extensions::IconsInfo::GetIcons(extension.get()),
235 default_icon,
236 this);
238 // No representations in |image_| yet.
239 gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
240 ASSERT_EQ(0u, image_reps.size());
242 // Gets representation for a scale factor.
243 gfx::ImageSkiaRep representation =
244 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
246 // Before the image representation is loaded, image should contain blank
247 // image representation.
248 EXPECT_TRUE(gfx::BitmapsAreEqual(
249 representation.sk_bitmap(),
250 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P)));
252 WaitForImageLoad();
253 EXPECT_EQ(1, ImageLoadedCount());
254 ASSERT_EQ(1u, image.image_skia().image_reps().size());
256 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
258 // We should get the right representation now.
259 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
260 EXPECT_EQ(16, representation.pixel_width());
262 // Gets representation for an additional scale factor.
263 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_200P);
265 EXPECT_TRUE(gfx::BitmapsAreEqual(
266 representation.sk_bitmap(),
267 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
269 WaitForImageLoad();
270 EXPECT_EQ(1, ImageLoadedCount());
271 ASSERT_EQ(2u, image.image_skia().image_reps().size());
273 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_200P);
275 // Image should have been resized.
276 EXPECT_EQ(32, representation.pixel_width());
277 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
278 bitmap_48_resized_to_32));
281 // There is no resource with either exact or bigger size, but there is a smaller
282 // resource.
283 TEST_F(ExtensionIconImageTest, FallbackToSmallerWhenNoBigger) {
284 scoped_ptr<Profile> profile(new TestingProfile());
285 scoped_refptr<Extension> extension(CreateExtension(
286 "extension_icon_image", Manifest::INVALID_LOCATION));
287 ASSERT_TRUE(extension.get() != NULL);
289 gfx::ImageSkia default_icon = GetDefaultIcon();
291 // Load images we expect to find as representations in icon_image, so we
292 // can later use them to validate icon_image.
293 SkBitmap bitmap_48 = GetTestBitmap(extension.get(), "48.png", 48);
294 ASSERT_FALSE(bitmap_48.empty());
296 IconImage image(profile.get(),
297 extension.get(),
298 extensions::IconsInfo::GetIcons(extension.get()),
300 default_icon,
301 this);
303 gfx::ImageSkiaRep representation =
304 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_200P);
306 WaitForImageLoad();
307 EXPECT_EQ(1, ImageLoadedCount());
308 ASSERT_EQ(1u, image.image_skia().image_reps().size());
310 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_200P);
312 // We should have loaded the biggest smaller resource resized to the actual
313 // size.
314 EXPECT_EQ(ui::SCALE_FACTOR_200P, representation.scale_factor());
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. Requested size is smaller than 32 though, so the smaller resource should
322 // be loaded.
323 TEST_F(ExtensionIconImageTest, FallbackToSmaller) {
324 scoped_ptr<Profile> profile(new TestingProfile());
325 scoped_refptr<Extension> extension(CreateExtension(
326 "extension_icon_image", Manifest::INVALID_LOCATION));
327 ASSERT_TRUE(extension.get() != NULL);
329 gfx::ImageSkia default_icon = GetDefaultIcon();
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());
336 IconImage image(profile.get(),
337 extension.get(),
338 extensions::IconsInfo::GetIcons(extension.get()),
340 default_icon,
341 this);
343 gfx::ImageSkiaRep representation =
344 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
346 WaitForImageLoad();
347 EXPECT_EQ(1, ImageLoadedCount());
348 ASSERT_EQ(1u, image.image_skia().image_reps().size());
350 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
352 // We should have loaded smaller (resized) resource.
353 EXPECT_EQ(ui::SCALE_FACTOR_100P, representation.scale_factor());
354 EXPECT_EQ(17, representation.pixel_width());
355 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
356 EnsureBitmapSize(bitmap_16, 17)));
359 // If resource set is empty, |GetRepresentation| should synchronously return
360 // default icon, without notifying observer of image change.
361 TEST_F(ExtensionIconImageTest, NoResources) {
362 scoped_ptr<Profile> profile(new TestingProfile());
363 scoped_refptr<Extension> extension(CreateExtension(
364 "extension_icon_image", Manifest::INVALID_LOCATION));
365 ASSERT_TRUE(extension.get() != NULL);
367 ExtensionIconSet empty_icon_set;
368 gfx::ImageSkia default_icon = GetDefaultIcon();
370 const int kRequestedSize = 24;
371 IconImage image(profile.get(),
372 extension.get(),
373 empty_icon_set,
374 kRequestedSize,
375 default_icon,
376 this);
378 gfx::ImageSkiaRep representation =
379 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
380 EXPECT_TRUE(gfx::BitmapsAreEqual(
381 representation.sk_bitmap(),
382 EnsureBitmapSize(
383 default_icon.GetRepresentation(ui::SCALE_FACTOR_100P).sk_bitmap(),
384 kRequestedSize)));
386 EXPECT_EQ(0, ImageLoadedCount());
387 // We should have a default icon representation.
388 ASSERT_EQ(1u, image.image_skia().image_reps().size());
390 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
391 EXPECT_TRUE(gfx::BitmapsAreEqual(
392 representation.sk_bitmap(),
393 EnsureBitmapSize(
394 default_icon.GetRepresentation(ui::SCALE_FACTOR_100P).sk_bitmap(),
395 kRequestedSize)));
398 // If resource set is invalid, image load should be done asynchronously and
399 // the observer should be notified when it's done. |GetRepresentation| should
400 // return the default icon representation once image load is done.
401 TEST_F(ExtensionIconImageTest, InvalidResource) {
402 scoped_ptr<Profile> profile(new TestingProfile());
403 scoped_refptr<Extension> extension(CreateExtension(
404 "extension_icon_image", Manifest::INVALID_LOCATION));
405 ASSERT_TRUE(extension.get() != NULL);
407 const int kInvalidIconSize = 24;
408 ExtensionIconSet invalid_icon_set;
409 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
411 gfx::ImageSkia default_icon = GetDefaultIcon();
413 IconImage image(profile.get(),
414 extension.get(),
415 invalid_icon_set,
416 kInvalidIconSize,
417 default_icon,
418 this);
420 gfx::ImageSkiaRep representation =
421 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
422 EXPECT_TRUE(gfx::BitmapsAreEqual(
423 representation.sk_bitmap(),
424 CreateBlankBitmapForScale(kInvalidIconSize, ui::SCALE_FACTOR_100P)));
426 WaitForImageLoad();
427 EXPECT_EQ(1, ImageLoadedCount());
428 // We should have default icon representation now.
429 ASSERT_EQ(1u, image.image_skia().image_reps().size());
431 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
432 EXPECT_TRUE(gfx::BitmapsAreEqual(
433 representation.sk_bitmap(),
434 EnsureBitmapSize(
435 default_icon.GetRepresentation(ui::SCALE_FACTOR_100P).sk_bitmap(),
436 kInvalidIconSize)));
439 // Test that IconImage works with lazily (but synchronously) created default
440 // icon when IconImage returns synchronously.
441 TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
442 scoped_ptr<Profile> profile(new TestingProfile());
443 scoped_refptr<Extension> extension(CreateExtension(
444 "extension_icon_image", Manifest::INVALID_LOCATION));
445 ASSERT_TRUE(extension.get() != NULL);
447 gfx::ImageSkia default_icon = GetDefaultIcon();
448 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
449 default_icon.size());
451 ExtensionIconSet empty_icon_set;
453 const int kRequestedSize = 128;
454 IconImage image(profile.get(),
455 extension.get(),
456 empty_icon_set,
457 kRequestedSize,
458 lazy_default_icon,
459 this);
461 ASSERT_FALSE(lazy_default_icon.HasRepresentation(ui::SCALE_FACTOR_100P));
463 gfx::ImageSkiaRep representation =
464 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
466 // The resouce set is empty, so we should get the result right away.
467 EXPECT_TRUE(lazy_default_icon.HasRepresentation(ui::SCALE_FACTOR_100P));
468 EXPECT_TRUE(gfx::BitmapsAreEqual(
469 representation.sk_bitmap(),
470 EnsureBitmapSize(
471 default_icon.GetRepresentation(ui::SCALE_FACTOR_100P).sk_bitmap(),
472 kRequestedSize)));
474 // We should have a default icon representation.
475 ASSERT_EQ(1u, image.image_skia().image_reps().size());
478 // Test that IconImage works with lazily (but synchronously) created default
479 // icon when IconImage returns asynchronously.
480 TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
481 scoped_ptr<Profile> profile(new TestingProfile());
482 scoped_refptr<Extension> extension(CreateExtension(
483 "extension_icon_image", Manifest::INVALID_LOCATION));
484 ASSERT_TRUE(extension.get() != NULL);
486 gfx::ImageSkia default_icon = GetDefaultIcon();
487 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
488 default_icon.size());
490 const int kInvalidIconSize = 24;
491 ExtensionIconSet invalid_icon_set;
492 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
494 IconImage image(profile.get(),
495 extension.get(),
496 invalid_icon_set,
497 kInvalidIconSize,
498 lazy_default_icon,
499 this);
501 ASSERT_FALSE(lazy_default_icon.HasRepresentation(ui::SCALE_FACTOR_100P));
503 gfx::ImageSkiaRep representation =
504 image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
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(ui::SCALE_FACTOR_100P));
513 representation = image.image_skia().GetRepresentation(ui::SCALE_FACTOR_100P);
514 EXPECT_TRUE(gfx::BitmapsAreEqual(
515 representation.sk_bitmap(),
516 EnsureBitmapSize(
517 default_icon.GetRepresentation(ui::SCALE_FACTOR_100P).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(ui::SCALE_FACTOR_100P);
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(ui::SCALE_FACTOR_100P);
562 EXPECT_EQ(ui::SCALE_FACTOR_100P, representation.scale_factor());
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(ui::SCALE_FACTOR_200P);
569 EXPECT_TRUE(gfx::BitmapsAreEqual(
570 representation.sk_bitmap(),
571 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));