普通文本  |  566行  |  19.31 KB

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/extension_icon_image.h"

#include <vector>

#include "base/json/json_file_value_serializer.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_paths.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "skia/ext/image_operations.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/image/image_skia_source.h"
#include "ui/gfx/skia_util.h"

using content::BrowserThread;

namespace extensions {
namespace {

SkBitmap CreateBlankBitmapForScale(int size_dip, ui::ScaleFactor scale_factor) {
  SkBitmap bitmap;
  const float scale = ui::GetScaleForScaleFactor(scale_factor);
  bitmap.allocN32Pixels(static_cast<int>(size_dip * scale),
                        static_cast<int>(size_dip * scale));
  bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
  return bitmap;
}

SkBitmap EnsureBitmapSize(const SkBitmap& original, int size) {
  if (original.width() == size && original.height() == size)
    return original;

  SkBitmap resized = skia::ImageOperations::Resize(
      original, skia::ImageOperations::RESIZE_LANCZOS3, size, size);
  return resized;
}

// Used to test behavior including images defined by an image skia source.
// |GetImageForScale| simply returns image representation from the image given
// in the ctor.
class MockImageSkiaSource : public gfx::ImageSkiaSource {
 public:
  explicit MockImageSkiaSource(const gfx::ImageSkia& image)
      : image_(image) {
  }
  virtual ~MockImageSkiaSource() {}

  virtual gfx::ImageSkiaRep GetImageForScale(float scale) OVERRIDE {
    return image_.GetRepresentation(scale);
  }

 private:
  gfx::ImageSkia image_;
};

// Helper class for synchronously loading extension image resource.
class TestImageLoader {
 public:
  explicit TestImageLoader(const Extension* extension)
      : extension_(extension),
        waiting_(false),
        image_loaded_(false) {
  }
  virtual ~TestImageLoader() {}

  void OnImageLoaded(const gfx::Image& image) {
    image_ = image;
    image_loaded_ = true;
    if (waiting_)
      base::MessageLoop::current()->Quit();
  }

  SkBitmap LoadBitmap(const std::string& path,
                      int size) {
    image_loaded_ = false;

    image_loader_.LoadImageAsync(
        extension_, extension_->GetResource(path), gfx::Size(size, size),
        base::Bind(&TestImageLoader::OnImageLoaded,
                   base::Unretained(this)));

    // If |image_| still hasn't been loaded (i.e. it is being loaded
    // asynchronously), wait for it.
    if (!image_loaded_) {
      waiting_ = true;
      base::MessageLoop::current()->Run();
      waiting_ = false;
    }

    EXPECT_TRUE(image_loaded_);

    return image_.IsEmpty() ? SkBitmap() : *image_.ToSkBitmap();
  }

 private:
  const Extension* extension_;
  bool waiting_;
  bool image_loaded_;
  gfx::Image image_;
  ImageLoader image_loader_;

  DISALLOW_COPY_AND_ASSIGN(TestImageLoader);
};

class ExtensionIconImageTest : public ExtensionsTest,
                               public IconImage::Observer {
 public:
  ExtensionIconImageTest()
      : image_loaded_count_(0),
        quit_in_image_loaded_(false),
        ui_thread_(BrowserThread::UI, &ui_loop_),
        file_thread_(BrowserThread::FILE),
        io_thread_(BrowserThread::IO),
        notification_service_(content::NotificationService::Create()) {}

  virtual ~ExtensionIconImageTest() {}

  void WaitForImageLoad() {
    quit_in_image_loaded_ = true;
    base::MessageLoop::current()->Run();
    quit_in_image_loaded_ = false;
  }

  int ImageLoadedCount() {
    int result = image_loaded_count_;
    image_loaded_count_ = 0;
    return result;
  }

  scoped_refptr<Extension> CreateExtension(const char* name,
                                           Manifest::Location location) {
    // Create and load an extension.
    base::FilePath test_file;
    if (!PathService::Get(DIR_TEST_DATA, &test_file)) {
      EXPECT_FALSE(true);
      return NULL;
    }
    test_file = test_file.AppendASCII(name);
    int error_code = 0;
    std::string error;
    JSONFileValueSerializer serializer(test_file.AppendASCII("manifest.json"));
    scoped_ptr<base::DictionaryValue> valid_value(
        static_cast<base::DictionaryValue*>(serializer.Deserialize(&error_code,
                                                                   &error)));
    EXPECT_EQ(0, error_code) << error;
    if (error_code != 0)
      return NULL;

    EXPECT_TRUE(valid_value.get());
    if (!valid_value)
      return NULL;

    return Extension::Create(test_file, location, *valid_value,
                             Extension::NO_FLAGS, &error);
  }

  // testing::Test overrides:
  virtual void SetUp() OVERRIDE {
    file_thread_.Start();
    io_thread_.Start();
  }

  // IconImage::Delegate overrides:
  virtual void OnExtensionIconImageChanged(IconImage* image) OVERRIDE {
    image_loaded_count_++;
    if (quit_in_image_loaded_)
      base::MessageLoop::current()->Quit();
  }

  gfx::ImageSkia GetDefaultIcon() {
    return gfx::ImageSkia(gfx::ImageSkiaRep(gfx::Size(16, 16), 1.0f));
  }

  // Loads an image to be used in test from the extension.
  // The image will be loaded from the relative path |path|.
  SkBitmap GetTestBitmap(const Extension* extension,
                         const std::string& path,
                         int size) {
    TestImageLoader image_loader(extension);
    return image_loader.LoadBitmap(path, size);
  }

 private:
  int image_loaded_count_;
  bool quit_in_image_loaded_;
  base::MessageLoop ui_loop_;
  content::TestBrowserThread ui_thread_;
  content::TestBrowserThread file_thread_;
  content::TestBrowserThread io_thread_;
  scoped_ptr<content::NotificationService> notification_service_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionIconImageTest);
};

}  // namespace

TEST_F(ExtensionIconImageTest, Basic) {
  std::vector<ui::ScaleFactor> supported_factors;
  supported_factors.push_back(ui::SCALE_FACTOR_100P);
  supported_factors.push_back(ui::SCALE_FACTOR_200P);
  ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  gfx::ImageSkia default_icon = GetDefaultIcon();

  // Load images we expect to find as representations in icon_image, so we
  // can later use them to validate icon_image.
  SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
  ASSERT_FALSE(bitmap_16.empty());

  // There is no image of size 32 defined in the extension manifest, so we
  // should expect manifest image of size 48 resized to size 32.
  SkBitmap bitmap_48_resized_to_32 =
      GetTestBitmap(extension.get(), "48.png", 32);
  ASSERT_FALSE(bitmap_48_resized_to_32.empty());

  IconImage image(browser_context(),
                  extension.get(),
                  IconsInfo::GetIcons(extension.get()),
                  16,
                  default_icon,
                  this);

  // No representations in |image_| yet.
  gfx::ImageSkia::ImageSkiaReps image_reps = image.image_skia().image_reps();
  ASSERT_EQ(0u, image_reps.size());

  // Gets representation for a scale factor.
  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);

  // Before the image representation is loaded, image should contain blank
  // image representation.
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_100P)));

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  ASSERT_EQ(1u, image.image_skia().image_reps().size());

  representation = image.image_skia().GetRepresentation(1.0f);

  // We should get the right representation now.
  EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));
  EXPECT_EQ(16, representation.pixel_width());

  // Gets representation for an additional scale factor.
  representation = image.image_skia().GetRepresentation(2.0f);

  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      CreateBlankBitmapForScale(16, ui::SCALE_FACTOR_200P)));

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  ASSERT_EQ(2u, image.image_skia().image_reps().size());

  representation = image.image_skia().GetRepresentation(2.0f);

  // Image should have been resized.
  EXPECT_EQ(32, representation.pixel_width());
  EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
                                   bitmap_48_resized_to_32));
}

// There is no resource with either exact or bigger size, but there is a smaller
// resource.
TEST_F(ExtensionIconImageTest, FallbackToSmallerWhenNoBigger) {
  std::vector<ui::ScaleFactor> supported_factors;
  supported_factors.push_back(ui::SCALE_FACTOR_100P);
  supported_factors.push_back(ui::SCALE_FACTOR_200P);
  ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  gfx::ImageSkia default_icon = GetDefaultIcon();

  // Load images we expect to find as representations in icon_image, so we
  // can later use them to validate icon_image.
  SkBitmap bitmap_48 = GetTestBitmap(extension.get(), "48.png", 48);
  ASSERT_FALSE(bitmap_48.empty());

  IconImage image(browser_context(),
                  extension.get(),
                  IconsInfo::GetIcons(extension.get()),
                  32,
                  default_icon,
                  this);

  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(2.0f);

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  ASSERT_EQ(1u, image.image_skia().image_reps().size());

  representation = image.image_skia().GetRepresentation(2.0f);

  // We should have loaded the biggest smaller resource resized to the actual
  // size.
  EXPECT_EQ(2.0f, representation.scale());
  EXPECT_EQ(64, representation.pixel_width());
  EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
                                   EnsureBitmapSize(bitmap_48, 64)));
}

// There is no resource with exact size, but there is a smaller and a bigger
// one. The bigger resource should be loaded.
TEST_F(ExtensionIconImageTest, FallbackToBigger) {
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  gfx::ImageSkia default_icon = GetDefaultIcon();

  // Load images we expect to find as representations in icon_image, so we
  // can later use them to validate icon_image.
  SkBitmap bitmap_24 = GetTestBitmap(extension.get(), "24.png", 24);
  ASSERT_FALSE(bitmap_24.empty());

  IconImage image(browser_context(),
                  extension.get(),
                  IconsInfo::GetIcons(extension.get()),
                  17,
                  default_icon,
                  this);

  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  ASSERT_EQ(1u, image.image_skia().image_reps().size());

  representation = image.image_skia().GetRepresentation(1.0f);

  // We should have loaded the smallest bigger (resized) resource.
  EXPECT_EQ(1.0f, representation.scale());
  EXPECT_EQ(17, representation.pixel_width());
  EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(),
                                   EnsureBitmapSize(bitmap_24, 17)));
}

// If resource set is empty, |GetRepresentation| should synchronously return
// default icon, without notifying observer of image change.
TEST_F(ExtensionIconImageTest, NoResources) {
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  ExtensionIconSet empty_icon_set;
  gfx::ImageSkia default_icon = GetDefaultIcon();

  const int kRequestedSize = 24;
  IconImage image(browser_context(),
                  extension.get(),
                  empty_icon_set,
                  kRequestedSize,
                  default_icon,
                  this);

  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      EnsureBitmapSize(
          default_icon.GetRepresentation(1.0f).sk_bitmap(),
          kRequestedSize)));

  EXPECT_EQ(0, ImageLoadedCount());
  // We should have a default icon representation.
  ASSERT_EQ(1u, image.image_skia().image_reps().size());

  representation = image.image_skia().GetRepresentation(1.0f);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      EnsureBitmapSize(
          default_icon.GetRepresentation(1.0f).sk_bitmap(),
          kRequestedSize)));
}

// If resource set is invalid, image load should be done asynchronously and
// the observer should be notified when it's done. |GetRepresentation| should
// return the default icon representation once image load is done.
TEST_F(ExtensionIconImageTest, InvalidResource) {
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  const int kInvalidIconSize = 24;
  ExtensionIconSet invalid_icon_set;
  invalid_icon_set.Add(kInvalidIconSize, "invalid.png");

  gfx::ImageSkia default_icon = GetDefaultIcon();

  IconImage image(browser_context(),
                  extension.get(),
                  invalid_icon_set,
                  kInvalidIconSize,
                  default_icon,
                  this);

  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      CreateBlankBitmapForScale(kInvalidIconSize, ui::SCALE_FACTOR_100P)));

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  // We should have default icon representation now.
  ASSERT_EQ(1u, image.image_skia().image_reps().size());

  representation = image.image_skia().GetRepresentation(1.0f);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      EnsureBitmapSize(
          default_icon.GetRepresentation(1.0f).sk_bitmap(),
          kInvalidIconSize)));
}

// Test that IconImage works with lazily (but synchronously) created default
// icon when IconImage returns synchronously.
TEST_F(ExtensionIconImageTest, LazyDefaultIcon) {
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  gfx::ImageSkia default_icon = GetDefaultIcon();
  gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
                                    default_icon.size());

  ExtensionIconSet empty_icon_set;

  const int kRequestedSize = 128;
  IconImage image(browser_context(),
                  extension.get(),
                  empty_icon_set,
                  kRequestedSize,
                  lazy_default_icon,
                  this);

  ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));

  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);

  // The resouce set is empty, so we should get the result right away.
  EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      EnsureBitmapSize(
          default_icon.GetRepresentation(1.0f).sk_bitmap(),
          kRequestedSize)));

  // We should have a default icon representation.
  ASSERT_EQ(1u, image.image_skia().image_reps().size());
}

// Test that IconImage works with lazily (but synchronously) created default
// icon when IconImage returns asynchronously.
TEST_F(ExtensionIconImageTest, LazyDefaultIcon_AsyncIconImage) {
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  gfx::ImageSkia default_icon = GetDefaultIcon();
  gfx::ImageSkia lazy_default_icon(new MockImageSkiaSource(default_icon),
                                    default_icon.size());

  const int kInvalidIconSize = 24;
  ExtensionIconSet invalid_icon_set;
  invalid_icon_set.Add(kInvalidIconSize, "invalid.png");

  IconImage image(browser_context(),
                  extension.get(),
                  invalid_icon_set,
                  kInvalidIconSize,
                  lazy_default_icon,
                  this);

  ASSERT_FALSE(lazy_default_icon.HasRepresentation(1.0f));

  gfx::ImageSkiaRep representation = image.image_skia().GetRepresentation(1.0f);

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  // We should have default icon representation now.
  ASSERT_EQ(1u, image.image_skia().image_reps().size());

  EXPECT_TRUE(lazy_default_icon.HasRepresentation(1.0f));

  representation = image.image_skia().GetRepresentation(1.0f);
  EXPECT_TRUE(gfx::BitmapsAreEqual(
      representation.sk_bitmap(),
      EnsureBitmapSize(
          default_icon.GetRepresentation(1.0f).sk_bitmap(),
          kInvalidIconSize)));
}

// Tests behavior of image created by IconImage after IconImage host goes
// away. The image should still return loaded representations. If requested
// representation was not loaded while IconImage host was around, transparent
// representations should be returned.
TEST_F(ExtensionIconImageTest, IconImageDestruction) {
  scoped_refptr<Extension> extension(CreateExtension(
      "extension_icon_image", Manifest::INVALID_LOCATION));
  ASSERT_TRUE(extension.get() != NULL);

  gfx::ImageSkia default_icon = GetDefaultIcon();

  // Load images we expect to find as representations in icon_image, so we
  // can later use them to validate icon_image.
  SkBitmap bitmap_16 = GetTestBitmap(extension.get(), "16.png", 16);
  ASSERT_FALSE(bitmap_16.empty());

  scoped_ptr<IconImage> image(
      new IconImage(browser_context(),
                    extension.get(),
                    IconsInfo::GetIcons(extension.get()),
                    16,
                    default_icon,
                    this));

  // Load an image representation.
  gfx::ImageSkiaRep representation =
      image->image_skia().GetRepresentation(1.0f);

  WaitForImageLoad();
  EXPECT_EQ(1, ImageLoadedCount());
  ASSERT_EQ(1u, image->image_skia().image_reps().size());

  // Stash loaded image skia, and destroy |image|.
  gfx::ImageSkia image_skia = image->image_skia();
  image.reset();
  extension = NULL;

  // Image skia should still be able to get previously loaded representation.
  representation = image_skia.GetRepresentation(1.0f);

  EXPECT_EQ(1.0f, representation.scale());
  EXPECT_EQ(16, representation.pixel_width());
  EXPECT_TRUE(gfx::BitmapsAreEqual(representation.sk_bitmap(), bitmap_16));

  // When requesting another representation, we should not crash and return some
  // image of the size. It could be blank or a rescale from the existing 1.0f
  // icon.
  representation = image_skia.GetRepresentation(2.0f);

  EXPECT_EQ(16, representation.GetWidth());
  EXPECT_EQ(16, representation.GetHeight());
  EXPECT_EQ(2.0f, representation.scale());
}

}  // namespace extensions