Fix search results being clipped in app list.
[chromium-blink-merge.git] / extensions / browser / extension_icon_image_unittest.cc
blob3fb4e691b711b001f44de60ee3f7fea1eeac34e3
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 <vector>
9 #include "base/json/json_file_value_serializer.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/path_service.h"
12 #include "content/public/browser/notification_service.h"
13 #include "content/public/test/test_browser_context.h"
14 #include "content/public/test/test_browser_thread.h"
15 #include "extensions/browser/extensions_test.h"
16 #include "extensions/browser/image_loader.h"
17 #include "extensions/common/extension.h"
18 #include "extensions/common/extension_paths.h"
19 #include "extensions/common/manifest.h"
20 #include "extensions/common/manifest_handlers/icons_handler.h"
21 #include "skia/ext/image_operations.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "ui/base/resource/resource_bundle.h"
24 #include "ui/gfx/image/image_skia_source.h"
25 #include "ui/gfx/skia_util.h"
27 using content::BrowserThread;
29 namespace extensions {
30 namespace {
32 SkBitmap CreateBlankBitmapForScale(int size_dip, ui::ScaleFactor scale_factor) {
33 SkBitmap bitmap;
34 const float scale = ui::GetScaleForScaleFactor(scale_factor);
35 bitmap.allocN32Pixels(static_cast<int>(size_dip * scale),
36 static_cast<int>(size_dip * scale));
37 bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
38 return bitmap;
41 SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
42 if (original.width() == size && original.height() == size)
43 return original;
45 SkBitmap resized = skia::ImageOperations::Resize(
46 original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
47 return resized;
50 // Used to test behavior including images defined by an image skia source.
51 // |GetImageForScale| simply returns image representation from the image given
52 // in the ctor.
53 class MockImageSkiaSource : public gfx::ImageSkiaSource {
54 public:
55 explicit MockImageSkiaSource(const gfx::ImageSkia& image)
56 : image_(image) {
58 ~MockImageSkiaSource() override {}
60 gfx::ImageSkiaRep GetImageForScale(float scale) override {
61 return image_.GetRepresentation(scale);
64 private:
65 gfx::ImageSkia image_;
68 // Helper class for synchronously loading extension image resource.
69 class TestImageLoader {
70 public:
71 explicit TestImageLoader(const Extension* extension)
72 : extension_(extension),
73 waiting_(false),
74 image_loaded_(false) {
76 virtual ~TestImageLoader() {}
78 void OnImageLoaded(const gfx::Image& image) {
79 image_ = image;
80 image_loaded_ = true;
81 if (waiting_)
82 base::MessageLoop::current()->Quit();
85 SkBitmap LoadBitmap(const std::string& path,
86 int size) {
87 image_loaded_ = false;
89 image_loader_.LoadImageAsync(
90 extension_, extension_->GetResource(path), gfx::Size(size, size),
91 base::Bind(&TestImageLoader::OnImageLoaded,
92 base::Unretained(this)));
94 // If |image_| still hasn't been loaded (i.e. it is being loaded
95 // asynchronously), wait for it.
96 if (!image_loaded_) {
97 waiting_ = true;
98 base::MessageLoop::current()->Run();
99 waiting_ = false;
102 EXPECT_TRUE(image_loaded_);
104 return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
107 private:
108 const Extension* extension_;
109 bool waiting_;
110 bool image_loaded_;
111 gfx::Image image_;
112 ImageLoader image_loader_;
114 DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
117 class ExtensionIconImageTest : public ExtensionsTest,
118 public IconImage::Observer {
119 public:
120 ExtensionIconImageTest()
121 : image_loaded_count_(0),
122 quit_in_image_loaded_(false),
123 ui_thread_(BrowserThread::UI, &ui_loop_),
124 file_thread_(BrowserThread::FILE),
125 io_thread_(BrowserThread::IO),
126 notification_service_(content::NotificationService::Create()) {}
128 ~ExtensionIconImageTest() override {}
130 void WaitForImageLoad() {
131 quit_in_image_loaded_ = true;
132 base::MessageLoop::current()->Run();
133 quit_in_image_loaded_ = false;
136 int ImageLoadedCount() {
137 int result = image_loaded_count_;
138 image_loaded_count_ = 0;
139 return result;
142 scoped_refptr<Extension> CreateExtension(const char* name,
143 Manifest::Location location) {
144 // Create and load an extension.
145 base::FilePath test_file;
146 if (!PathService::Get(DIR_TEST_DATA, &test_file)) {
147 EXPECT_FALSE(true);
148 return NULL;
150 test_file = test_file.AppendASCII(name);
151 int error_code = 0;
152 std::string error;
153 JSONFileValueDeserializer deserializer(
154 test_file.AppendASCII("manifest.json"));
155 scoped_ptr<base::DictionaryValue> valid_value(
156 static_cast<base::DictionaryValue*>(
157 deserializer.Deserialize(&error_code, &error)));
158 EXPECT_EQ(0, error_code) << error;
159 if (error_code != 0)
160 return NULL;
162 EXPECT_TRUE(valid_value.get());
163 if (!valid_value)
164 return NULL;
166 return Extension::Create(test_file, location, *valid_value,
167 Extension::NO_FLAGS, &error);
170 // testing::Test overrides:
171 void SetUp() override {
172 file_thread_.Start();
173 io_thread_.Start();
176 // IconImage::Delegate overrides:
177 void OnExtensionIconImageChanged(IconImage* image) override {
178 image_loaded_count_++;
179 if (quit_in_image_loaded_)
180 base::MessageLoop::current()->Quit();
183 gfx::ImageSkia GetDefaultIcon() {
184 return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f));
187 // Loads an image to be used in test from the extension.
188 // The image will be loaded from the relative path |path|.
189 SkBitmap GetTestBitmap(const Extension* extension,
190 const std::string& path,
191 int size) {
192 TestImageLoader image_loader(extension);
193 return image_loader.LoadBitmap(path, size);
196 private:
197 int image_loaded_count_;
198 bool quit_in_image_loaded_;
199 base::MessageLoop ui_loop_;
200 content::TestBrowserThread ui_thread_;
201 content::TestBrowserThread file_thread_;
202 content::TestBrowserThread io_thread_;
203 scoped_ptr<content::NotificationService> notification_service_;
205 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
208 } // namespace
210 TEST_F(ExtensionIconImageTest, Basic) {
211 std::vector<ui::ScaleFactor> supported_factors;
212 supported_factors.push_back(ui::SCALE_FACTOR_100P);
213 supported_factors.push_back(ui::SCALE_FACTOR_200P);
214 ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
215 scoped_refptr<Extension> extension(CreateExtension(
216 "extension_icon_image", Manifest::INVALID_LOCATION));
217 ASSERT_TRUE(extension.get() != NULL);
219 gfx::ImageSkia default_icon = GetDefaultIcon();
221 // Load images we expect to find as representations in icon_image, so we
222 // can later use them to validate icon_image.
223 SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
224 ASSERT_FALSE(bitmap_16.empty());
226 // There is no image of size 32 defined in the extension manifest, so we
227 // should expect manifest image of size 48 resized to size 32.
228 SkBitmap bitmap_48_resized_to_32 =
229 GetTestBitmap(extension.get(), "48.png", 32);
230 ASSERT_FALSE(bitmap_48_resized_to_32.empty());
232 IconImage image(browser_context(),
233 extension.get(),
234 IconsInfo::GetIcons(extension.get()),
236 default_icon,
237 this);
239 // No representations in |image_| yet.
240 gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
241 ASSERT_EQ(0u, image_reps.size());
243 // Gets representation for a scale factor.
244 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
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(1.0f);
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(2.0f);
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(2.0f);
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 std::vector<ui::ScaleFactor> supported_factors;
285 supported_factors.push_back(ui::SCALE_FACTOR_100P);
286 supported_factors.push_back(ui::SCALE_FACTOR_200P);
287 ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
288 scoped_refptr<Extension> extension(CreateExtension(
289 "extension_icon_image", Manifest::INVALID_LOCATION));
290 ASSERT_TRUE(extension.get() != NULL);
292 gfx::ImageSkia default_icon = GetDefaultIcon();
294 // Load images we expect to find as representations in icon_image, so we
295 // can later use them to validate icon_image.
296 SkBitmap bitmap_48 = GetTestBitmap(extension.get(), "48.png", 48);
297 ASSERT_FALSE(bitmap_48.empty());
299 IconImage image(browser_context(),
300 extension.get(),
301 IconsInfo::GetIcons(extension.get()),
303 default_icon,
304 this);
306 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
308 WaitForImageLoad();
309 EXPECT_EQ(1, ImageLoadedCount());
310 ASSERT_EQ(1u, image.image_skia().image_reps().size());
312 representation = image.image_skia().GetRepresentation(2.0f);
314 // We should have loaded the biggest smaller resource resized to the actual
315 // size.
316 EXPECT_EQ(2.0f, representation.scale());
317 EXPECT_EQ(64, representation.pixel_width());
318 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
319 EnsureBitmapSize(bitmap_48, 64)));
322 // There is no resource with exact size, but there is a smaller and a bigger
323 // one. The bigger resource should be loaded.
324 TEST_F(ExtensionIconImageTest, FallbackToBigger) {
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_24 = GetTestBitmap(extension.get(), "24.png", 24);
334 ASSERT_FALSE(bitmap_24.empty());
336 IconImage image(browser_context(),
337 extension.get(),
338 IconsInfo::GetIcons(extension.get()),
340 default_icon,
341 this);
343 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
345 WaitForImageLoad();
346 EXPECT_EQ(1, ImageLoadedCount());
347 ASSERT_EQ(1u, image.image_skia().image_reps().size());
349 representation = image.image_skia().GetRepresentation(1.0f);
351 // We should have loaded the smallest bigger (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_24, 17)));
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_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(browser_context(),
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_refptr<Extension> extension(CreateExtension(
400 "extension_icon_image", Manifest::INVALID_LOCATION));
401 ASSERT_TRUE(extension.get() != NULL);
403 const int kInvalidIconSize = 24;
404 ExtensionIconSet invalid_icon_set;
405 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
407 gfx::ImageSkia default_icon = GetDefaultIcon();
409 IconImage image(browser_context(),
410 extension.get(),
411 invalid_icon_set,
412 kInvalidIconSize,
413 default_icon,
414 this);
416 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
417 EXPECT_TRUE(gfx::BitmapsAreEqual(
418 representation.sk_bitmap(),
419 CreateBlankBitmapForScale(kInvalidIconSize, ui::SCALE_FACTOR_100P)));
421 WaitForImageLoad();
422 EXPECT_EQ(1, ImageLoadedCount());
423 // We should have default icon representation now.
424 ASSERT_EQ(1u, image.image_skia().image_reps().size());
426 representation = image.image_skia().GetRepresentation(1.0f);
427 EXPECT_TRUE(gfx::BitmapsAreEqual(
428 representation.sk_bitmap(),
429 EnsureBitmapSize(
430 default_icon.GetRepresentation(1.0f).sk_bitmap(),
431 kInvalidIconSize)));
434 // Test that IconImage works with lazily (but synchronously) created default
435 // icon when IconImage returns synchronously.
436 TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
437 scoped_refptr<Extension> extension(CreateExtension(
438 "extension_icon_image", Manifest::INVALID_LOCATION));
439 ASSERT_TRUE(extension.get() != NULL);
441 gfx::ImageSkia default_icon = GetDefaultIcon();
442 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
443 default_icon.size());
445 ExtensionIconSet empty_icon_set;
447 const int kRequestedSize = 128;
448 IconImage image(browser_context(),
449 extension.get(),
450 empty_icon_set,
451 kRequestedSize,
452 lazy_default_icon,
453 this);
455 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
457 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
459 // The resouce set is empty, so we should get the result right away.
460 EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
461 EXPECT_TRUE(gfx::BitmapsAreEqual(
462 representation.sk_bitmap(),
463 EnsureBitmapSize(
464 default_icon.GetRepresentation(1.0f).sk_bitmap(),
465 kRequestedSize)));
467 // We should have a default icon representation.
468 ASSERT_EQ(1u, image.image_skia().image_reps().size());
471 // Test that IconImage works with lazily (but synchronously) created default
472 // icon when IconImage returns asynchronously.
473 TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
474 scoped_refptr<Extension> extension(CreateExtension(
475 "extension_icon_image", Manifest::INVALID_LOCATION));
476 ASSERT_TRUE(extension.get() != NULL);
478 gfx::ImageSkia default_icon = GetDefaultIcon();
479 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
480 default_icon.size());
482 const int kInvalidIconSize = 24;
483 ExtensionIconSet invalid_icon_set;
484 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
486 IconImage image(browser_context(),
487 extension.get(),
488 invalid_icon_set,
489 kInvalidIconSize,
490 lazy_default_icon,
491 this);
493 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
495 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
497 WaitForImageLoad();
498 EXPECT_EQ(1, ImageLoadedCount());
499 // We should have default icon representation now.
500 ASSERT_EQ(1u, image.image_skia().image_reps().size());
502 EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
504 representation = image.image_skia().GetRepresentation(1.0f);
505 EXPECT_TRUE(gfx::BitmapsAreEqual(
506 representation.sk_bitmap(),
507 EnsureBitmapSize(
508 default_icon.GetRepresentation(1.0f).sk_bitmap(),
509 kInvalidIconSize)));
512 // Tests behavior of image created by IconImage after IconImage host goes
513 // away. The image should still return loaded representations. If requested
514 // representation was not loaded while IconImage host was around, transparent
515 // representations should be returned.
516 TEST_F(ExtensionIconImageTest, IconImageDestruction) {
517 scoped_refptr<Extension> extension(CreateExtension(
518 "extension_icon_image", Manifest::INVALID_LOCATION));
519 ASSERT_TRUE(extension.get() != NULL);
521 gfx::ImageSkia default_icon = GetDefaultIcon();
523 // Load images we expect to find as representations in icon_image, so we
524 // can later use them to validate icon_image.
525 SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
526 ASSERT_FALSE(bitmap_16.empty());
528 scoped_ptr<IconImage> image(
529 new IconImage(browser_context(),
530 extension.get(),
531 IconsInfo::GetIcons(extension.get()),
533 default_icon,
534 this));
536 // Load an image representation.
537 gfx::ImageSkiaRep representation =
538 image->image_skia().GetRepresentation(1.0f);
540 WaitForImageLoad();
541 EXPECT_EQ(1, ImageLoadedCount());
542 ASSERT_EQ(1u, image->image_skia().image_reps().size());
544 // Stash loaded image skia, and destroy |image|.
545 gfx::ImageSkia image_skia = image->image_skia();
546 image.reset();
547 extension = NULL;
549 // Image skia should still be able to get previously loaded representation.
550 representation = image_skia.GetRepresentation(1.0f);
552 EXPECT_EQ(1.0f, representation.scale());
553 EXPECT_EQ(16, representation.pixel_width());
554 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
556 // When requesting another representation, we should not crash and return some
557 // image of the size. It could be blank or a rescale from the existing 1.0f
558 // icon.
559 representation = image_skia.GetRepresentation(2.0f);
561 EXPECT_EQ(16, representation.GetWidth());
562 EXPECT_EQ(16, representation.GetHeight());
563 EXPECT_EQ(2.0f, representation.scale());
566 // Test that new representations added to the image of an IconImage are cached
567 // for future use.
568 TEST_F(ExtensionIconImageTest, ImageCachesNewRepresentations) {
569 // Load up an extension and create an icon image.
570 scoped_refptr<Extension> extension(
571 CreateExtension("extension_icon_image", Manifest::INVALID_LOCATION));
572 ASSERT_TRUE(extension.get() != NULL);
573 gfx::ImageSkia default_icon = GetDefaultIcon();
574 scoped_ptr<IconImage> icon_image(
575 new IconImage(browser_context(),
576 extension.get(),
577 IconsInfo::GetIcons(extension.get()),
579 default_icon,
580 this));
582 // Load an image representation.
583 gfx::ImageSkiaRep representation =
584 icon_image->image_skia().GetRepresentation(1.0f);
585 WaitForImageLoad();
587 // Cache for later use.
588 gfx::Image prior_image = icon_image->image();
590 // Now the fun part: access the image from the IconImage, and create a png
591 // representation of it.
592 gfx::Image image = icon_image->image();
593 scoped_refptr<base::RefCountedMemory> image_as_png = image.As1xPNGBytes();
595 // Access the image from the IconImage again, and get a png representation.
596 // The two png representations should be exactly equal (the same object),
597 // because image storage is shared, so when we created one from the first
598 // image, all other images should also have that representation...
599 gfx::Image image2 = icon_image->image();
600 EXPECT_EQ(image_as_png.get(), image2.As1xPNGBytes().get());
602 // ...even images that were copied before the representation was constructed.
603 EXPECT_EQ(image_as_png.get(), prior_image.As1xPNGBytes().get());
606 } // namespace extensions