Suppress a Valgrind report.
[chromium-blink-merge.git] / extensions / browser / extension_icon_image_unittest.cc
blob24d0ac2e4a4c46ad925cacb3dea48f3c95c1d283
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 JSONFileValueSerializer serializer(test_file.AppendASCII("manifest.json"));
154 scoped_ptr<base::DictionaryValue> valid_value(
155 static_cast<base::DictionaryValue*>(serializer.Deserialize(&error_code,
156 &error)));
157 EXPECT_EQ(0, error_code) << error;
158 if (error_code != 0)
159 return NULL;
161 EXPECT_TRUE(valid_value.get());
162 if (!valid_value)
163 return NULL;
165 return Extension::Create(test_file, location, *valid_value,
166 Extension::NO_FLAGS, &error);
169 // testing::Test overrides:
170 void SetUp() override {
171 file_thread_.Start();
172 io_thread_.Start();
175 // IconImage::Delegate overrides:
176 void OnExtensionIconImageChanged(IconImage* image) override {
177 image_loaded_count_++;
178 if (quit_in_image_loaded_)
179 base::MessageLoop::current()->Quit();
182 gfx::ImageSkia GetDefaultIcon() {
183 return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f));
186 // Loads an image to be used in test from the extension.
187 // The image will be loaded from the relative path |path|.
188 SkBitmap GetTestBitmap(const Extension* extension,
189 const std::string& path,
190 int size) {
191 TestImageLoader image_loader(extension);
192 return image_loader.LoadBitmap(path, size);
195 private:
196 int image_loaded_count_;
197 bool quit_in_image_loaded_;
198 base::MessageLoop ui_loop_;
199 content::TestBrowserThread ui_thread_;
200 content::TestBrowserThread file_thread_;
201 content::TestBrowserThread io_thread_;
202 scoped_ptr<content::NotificationService> notification_service_;
204 DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
207 } // namespace
209 TEST_F(ExtensionIconImageTest, Basic) {
210 std::vector<ui::ScaleFactor> supported_factors;
211 supported_factors.push_back(ui::SCALE_FACTOR_100P);
212 supported_factors.push_back(ui::SCALE_FACTOR_200P);
213 ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
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(browser_context(),
232 extension.get(),
233 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 = image.image_skia().GetRepresentation(1.0f);
245 // Before the image representation is loaded, image should contain blank
246 // image representation.
247 EXPECT_TRUE(gfx::BitmapsAreEqual(
248 representation.sk_bitmap(),
249 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P)));
251 WaitForImageLoad();
252 EXPECT_EQ(1, ImageLoadedCount());
253 ASSERT_EQ(1u, image.image_skia().image_reps().size());
255 representation = image.image_skia().GetRepresentation(1.0f);
257 // We should get the right representation now.
258 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
259 EXPECT_EQ(16, representation.pixel_width());
261 // Gets representation for an additional scale factor.
262 representation = image.image_skia().GetRepresentation(2.0f);
264 EXPECT_TRUE(gfx::BitmapsAreEqual(
265 representation.sk_bitmap(),
266 CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));
268 WaitForImageLoad();
269 EXPECT_EQ(1, ImageLoadedCount());
270 ASSERT_EQ(2u, image.image_skia().image_reps().size());
272 representation = image.image_skia().GetRepresentation(2.0f);
274 // Image should have been resized.
275 EXPECT_EQ(32, representation.pixel_width());
276 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
277 bitmap_48_resized_to_32));
280 // There is no resource with either exact or bigger size, but there is a smaller
281 // resource.
282 TEST_F(ExtensionIconImageTest, FallbackToSmallerWhenNoBigger) {
283 std::vector<ui::ScaleFactor> supported_factors;
284 supported_factors.push_back(ui::SCALE_FACTOR_100P);
285 supported_factors.push_back(ui::SCALE_FACTOR_200P);
286 ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
287 scoped_refptr<Extension> extension(CreateExtension(
288 "extension_icon_image", Manifest::INVALID_LOCATION));
289 ASSERT_TRUE(extension.get() != NULL);
291 gfx::ImageSkia default_icon = GetDefaultIcon();
293 // Load images we expect to find as representations in icon_image, so we
294 // can later use them to validate icon_image.
295 SkBitmap bitmap_48 = GetTestBitmap(extension.get(), "48.png", 48);
296 ASSERT_FALSE(bitmap_48.empty());
298 IconImage image(browser_context(),
299 extension.get(),
300 IconsInfo::GetIcons(extension.get()),
302 default_icon,
303 this);
305 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);
307 WaitForImageLoad();
308 EXPECT_EQ(1, ImageLoadedCount());
309 ASSERT_EQ(1u, image.image_skia().image_reps().size());
311 representation = image.image_skia().GetRepresentation(2.0f);
313 // We should have loaded the biggest smaller resource resized to the actual
314 // size.
315 EXPECT_EQ(2.0f, representation.scale());
316 EXPECT_EQ(64, representation.pixel_width());
317 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
318 EnsureBitmapSize(bitmap_48, 64)));
321 // There is no resource with exact size, but there is a smaller and a bigger
322 // one. The bigger resource should be loaded.
323 TEST_F(ExtensionIconImageTest, FallbackToBigger) {
324 scoped_refptr<Extension> extension(CreateExtension(
325 "extension_icon_image", Manifest::INVALID_LOCATION));
326 ASSERT_TRUE(extension.get() != NULL);
328 gfx::ImageSkia default_icon = GetDefaultIcon();
330 // Load images we expect to find as representations in icon_image, so we
331 // can later use them to validate icon_image.
332 SkBitmap bitmap_24 = GetTestBitmap(extension.get(), "24.png", 24);
333 ASSERT_FALSE(bitmap_24.empty());
335 IconImage image(browser_context(),
336 extension.get(),
337 IconsInfo::GetIcons(extension.get()),
339 default_icon,
340 this);
342 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
344 WaitForImageLoad();
345 EXPECT_EQ(1, ImageLoadedCount());
346 ASSERT_EQ(1u, image.image_skia().image_reps().size());
348 representation = image.image_skia().GetRepresentation(1.0f);
350 // We should have loaded the smallest bigger (resized) resource.
351 EXPECT_EQ(1.0f, representation.scale());
352 EXPECT_EQ(17, representation.pixel_width());
353 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
354 EnsureBitmapSize(bitmap_24, 17)));
357 // If resource set is empty, |GetRepresentation| should synchronously return
358 // default icon, without notifying observer of image change.
359 TEST_F(ExtensionIconImageTest, NoResources) {
360 scoped_refptr<Extension> extension(CreateExtension(
361 "extension_icon_image", Manifest::INVALID_LOCATION));
362 ASSERT_TRUE(extension.get() != NULL);
364 ExtensionIconSet empty_icon_set;
365 gfx::ImageSkia default_icon = GetDefaultIcon();
367 const int kRequestedSize = 24;
368 IconImage image(browser_context(),
369 extension.get(),
370 empty_icon_set,
371 kRequestedSize,
372 default_icon,
373 this);
375 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
376 EXPECT_TRUE(gfx::BitmapsAreEqual(
377 representation.sk_bitmap(),
378 EnsureBitmapSize(
379 default_icon.GetRepresentation(1.0f).sk_bitmap(),
380 kRequestedSize)));
382 EXPECT_EQ(0, ImageLoadedCount());
383 // We should have a default icon representation.
384 ASSERT_EQ(1u, image.image_skia().image_reps().size());
386 representation = image.image_skia().GetRepresentation(1.0f);
387 EXPECT_TRUE(gfx::BitmapsAreEqual(
388 representation.sk_bitmap(),
389 EnsureBitmapSize(
390 default_icon.GetRepresentation(1.0f).sk_bitmap(),
391 kRequestedSize)));
394 // If resource set is invalid, image load should be done asynchronously and
395 // the observer should be notified when it's done. |GetRepresentation| should
396 // return the default icon representation once image load is done.
397 TEST_F(ExtensionIconImageTest, InvalidResource) {
398 scoped_refptr<Extension> extension(CreateExtension(
399 "extension_icon_image", Manifest::INVALID_LOCATION));
400 ASSERT_TRUE(extension.get() != NULL);
402 const int kInvalidIconSize = 24;
403 ExtensionIconSet invalid_icon_set;
404 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
406 gfx::ImageSkia default_icon = GetDefaultIcon();
408 IconImage image(browser_context(),
409 extension.get(),
410 invalid_icon_set,
411 kInvalidIconSize,
412 default_icon,
413 this);
415 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
416 EXPECT_TRUE(gfx::BitmapsAreEqual(
417 representation.sk_bitmap(),
418 CreateBlankBitmapForScale(kInvalidIconSize, ui::SCALE_FACTOR_100P)));
420 WaitForImageLoad();
421 EXPECT_EQ(1, ImageLoadedCount());
422 // We should have default icon representation now.
423 ASSERT_EQ(1u, image.image_skia().image_reps().size());
425 representation = image.image_skia().GetRepresentation(1.0f);
426 EXPECT_TRUE(gfx::BitmapsAreEqual(
427 representation.sk_bitmap(),
428 EnsureBitmapSize(
429 default_icon.GetRepresentation(1.0f).sk_bitmap(),
430 kInvalidIconSize)));
433 // Test that IconImage works with lazily (but synchronously) created default
434 // icon when IconImage returns synchronously.
435 TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
436 scoped_refptr<Extension> extension(CreateExtension(
437 "extension_icon_image", Manifest::INVALID_LOCATION));
438 ASSERT_TRUE(extension.get() != NULL);
440 gfx::ImageSkia default_icon = GetDefaultIcon();
441 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
442 default_icon.size());
444 ExtensionIconSet empty_icon_set;
446 const int kRequestedSize = 128;
447 IconImage image(browser_context(),
448 extension.get(),
449 empty_icon_set,
450 kRequestedSize,
451 lazy_default_icon,
452 this);
454 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
456 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
458 // The resouce set is empty, so we should get the result right away.
459 EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
460 EXPECT_TRUE(gfx::BitmapsAreEqual(
461 representation.sk_bitmap(),
462 EnsureBitmapSize(
463 default_icon.GetRepresentation(1.0f).sk_bitmap(),
464 kRequestedSize)));
466 // We should have a default icon representation.
467 ASSERT_EQ(1u, image.image_skia().image_reps().size());
470 // Test that IconImage works with lazily (but synchronously) created default
471 // icon when IconImage returns asynchronously.
472 TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
473 scoped_refptr<Extension> extension(CreateExtension(
474 "extension_icon_image", Manifest::INVALID_LOCATION));
475 ASSERT_TRUE(extension.get() != NULL);
477 gfx::ImageSkia default_icon = GetDefaultIcon();
478 gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
479 default_icon.size());
481 const int kInvalidIconSize = 24;
482 ExtensionIconSet invalid_icon_set;
483 invalid_icon_set.Add(kInvalidIconSize, "invalid.png");
485 IconImage image(browser_context(),
486 extension.get(),
487 invalid_icon_set,
488 kInvalidIconSize,
489 lazy_default_icon,
490 this);
492 ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));
494 gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
496 WaitForImageLoad();
497 EXPECT_EQ(1, ImageLoadedCount());
498 // We should have default icon representation now.
499 ASSERT_EQ(1u, image.image_skia().image_reps().size());
501 EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
503 representation = image.image_skia().GetRepresentation(1.0f);
504 EXPECT_TRUE(gfx::BitmapsAreEqual(
505 representation.sk_bitmap(),
506 EnsureBitmapSize(
507 default_icon.GetRepresentation(1.0f).sk_bitmap(),
508 kInvalidIconSize)));
511 // Tests behavior of image created by IconImage after IconImage host goes
512 // away. The image should still return loaded representations. If requested
513 // representation was not loaded while IconImage host was around, transparent
514 // representations should be returned.
515 TEST_F(ExtensionIconImageTest, IconImageDestruction) {
516 scoped_refptr<Extension> extension(CreateExtension(
517 "extension_icon_image", Manifest::INVALID_LOCATION));
518 ASSERT_TRUE(extension.get() != NULL);
520 gfx::ImageSkia default_icon = GetDefaultIcon();
522 // Load images we expect to find as representations in icon_image, so we
523 // can later use them to validate icon_image.
524 SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
525 ASSERT_FALSE(bitmap_16.empty());
527 scoped_ptr<IconImage> image(
528 new IconImage(browser_context(),
529 extension.get(),
530 IconsInfo::GetIcons(extension.get()),
532 default_icon,
533 this));
535 // Load an image representation.
536 gfx::ImageSkiaRep representation =
537 image->image_skia().GetRepresentation(1.0f);
539 WaitForImageLoad();
540 EXPECT_EQ(1, ImageLoadedCount());
541 ASSERT_EQ(1u, image->image_skia().image_reps().size());
543 // Stash loaded image skia, and destroy |image|.
544 gfx::ImageSkia image_skia = image->image_skia();
545 image.reset();
546 extension = NULL;
548 // Image skia should still be able to get previously loaded representation.
549 representation = image_skia.GetRepresentation(1.0f);
551 EXPECT_EQ(1.0f, representation.scale());
552 EXPECT_EQ(16, representation.pixel_width());
553 EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
555 // When requesting another representation, we should not crash and return some
556 // image of the size. It could be blank or a rescale from the existing 1.0f
557 // icon.
558 representation = image_skia.GetRepresentation(2.0f);
560 EXPECT_EQ(16, representation.GetWidth());
561 EXPECT_EQ(16, representation.GetHeight());
562 EXPECT_EQ(2.0f, representation.scale());
565 // Test that new representations added to the image of an IconImage are cached
566 // for future use.
567 TEST_F(ExtensionIconImageTest, ImageCachesNewRepresentations) {
568 // Load up an extension and create an icon image.
569 scoped_refptr<Extension> extension(
570 CreateExtension("extension_icon_image", Manifest::INVALID_LOCATION));
571 ASSERT_TRUE(extension.get() != NULL);
572 gfx::ImageSkia default_icon = GetDefaultIcon();
573 scoped_ptr<IconImage> icon_image(
574 new IconImage(browser_context(),
575 extension.get(),
576 IconsInfo::GetIcons(extension.get()),
578 default_icon,
579 this));
581 // Load an image representation.
582 gfx::ImageSkiaRep representation =
583 icon_image->image_skia().GetRepresentation(1.0f);
584 WaitForImageLoad();
586 // Cache for later use.
587 gfx::Image prior_image = icon_image->image();
589 // Now the fun part: access the image from the IconImage, and create a png
590 // representation of it.
591 gfx::Image image = icon_image->image();
592 scoped_refptr<base::RefCountedMemory> image_as_png = image.As1xPNGBytes();
594 // Access the image from the IconImage again, and get a png representation.
595 // The two png representations should be exactly equal (the same object),
596 // because image storage is shared, so when we created one from the first
597 // image, all other images should also have that representation...
598 gfx::Image image2 = icon_image->image();
599 EXPECT_EQ(image_as_png.get(), image2.As1xPNGBytes().get());
601 // ...even images that were copied before the representation was constructed.
602 EXPECT_EQ(image_as_png.get(), prior_image.As1xPNGBytes().get());
605 } // namespace extensions