Home | History | Annotate | Download | only in extensions
      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.
      4 
      5 #include "chrome/browser/extensions/extension_action_icon_factory.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/file_util.h"
      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 "chrome/browser/extensions/extension_action.h"
     13 #include "chrome/browser/extensions/extension_action_manager.h"
     14 #include "chrome/browser/extensions/extension_service.h"
     15 #include "chrome/browser/extensions/test_extension_system.h"
     16 #include "chrome/common/chrome_paths.h"
     17 #include "chrome/common/extensions/extension.h"
     18 #include "chrome/test/base/testing_profile.h"
     19 #include "content/public/test/test_browser_thread.h"
     20 #include "grit/theme_resources.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/codec/png_codec.h"
     25 #include "ui/gfx/image/image_skia.h"
     26 #include "ui/gfx/skia_util.h"
     27 
     28 #if defined(OS_CHROMEOS)
     29 #include "chrome/browser/chromeos/login/user_manager.h"
     30 #include "chrome/browser/chromeos/settings/cros_settings.h"
     31 #include "chrome/browser/chromeos/settings/device_settings_service.h"
     32 #endif
     33 
     34 using content::BrowserThread;
     35 
     36 namespace extensions {
     37 namespace {
     38 
     39 bool ImageRepsAreEqual(const gfx::ImageSkiaRep& image_rep1,
     40                        const gfx::ImageSkiaRep& image_rep2) {
     41   return image_rep1.scale_factor() == image_rep2.scale_factor() &&
     42          gfx::BitmapsAreEqual(image_rep1.sk_bitmap(), image_rep2.sk_bitmap());
     43 }
     44 
     45 gfx::Image EnsureImageSize(const gfx::Image& original, int size) {
     46   const SkBitmap* original_bitmap = original.ToSkBitmap();
     47   if (original_bitmap->width() == size && original_bitmap->height() == size)
     48     return original;
     49 
     50   SkBitmap resized = skia::ImageOperations::Resize(
     51       *original.ToSkBitmap(), skia::ImageOperations::RESIZE_LANCZOS3,
     52       size, size);
     53   return gfx::Image::CreateFrom1xBitmap(resized);
     54 }
     55 
     56 gfx::ImageSkiaRep CreateBlankRep(int size_dip, ui::ScaleFactor scale_factor) {
     57     SkBitmap bitmap;
     58     const float scale = ui::GetScaleFactorScale(scale_factor);
     59     bitmap.setConfig(SkBitmap::kARGB_8888_Config,
     60                      static_cast<int>(size_dip * scale),
     61                      static_cast<int>(size_dip * scale));
     62     bitmap.allocPixels();
     63     bitmap.eraseColor(SkColorSetARGB(0, 0, 0, 0));
     64     return gfx::ImageSkiaRep(bitmap, scale_factor);
     65 }
     66 
     67 gfx::Image LoadIcon(const std::string& filename) {
     68   base::FilePath path;
     69   PathService::Get(chrome::DIR_TEST_DATA, &path);
     70   path = path.AppendASCII("extensions/api_test").AppendASCII(filename);
     71 
     72   std::string file_contents;
     73   file_util::ReadFileToString(path, &file_contents);
     74   const unsigned char* data =
     75       reinterpret_cast<const unsigned char*>(file_contents.data());
     76 
     77   SkBitmap bitmap;
     78   gfx::PNGCodec::Decode(data, file_contents.length(), &bitmap);
     79 
     80   return gfx::Image::CreateFrom1xBitmap(bitmap);
     81 }
     82 
     83 class ExtensionActionIconFactoryTest
     84     : public testing::Test,
     85       public ExtensionActionIconFactory::Observer {
     86  public:
     87   ExtensionActionIconFactoryTest()
     88       : quit_in_icon_updated_(false),
     89         ui_thread_(BrowserThread::UI, &ui_loop_),
     90         file_thread_(BrowserThread::FILE),
     91         io_thread_(BrowserThread::IO) {
     92   }
     93 
     94   virtual ~ExtensionActionIconFactoryTest() {}
     95 
     96   void WaitForIconUpdate() {
     97     quit_in_icon_updated_ = true;
     98     base::MessageLoop::current()->Run();
     99     quit_in_icon_updated_ = false;
    100   }
    101 
    102   scoped_refptr<Extension> CreateExtension(const char* name,
    103                                            Manifest::Location location) {
    104     // Create and load an extension.
    105     base::FilePath test_file;
    106     if (!PathService::Get(chrome::DIR_TEST_DATA, &test_file)) {
    107       EXPECT_FALSE(true);
    108       return NULL;
    109     }
    110     test_file = test_file.AppendASCII("extensions/api_test").AppendASCII(name);
    111     int error_code = 0;
    112     std::string error;
    113     JSONFileValueSerializer serializer(test_file.AppendASCII("manifest.json"));
    114     scoped_ptr<DictionaryValue> valid_value(
    115         static_cast<DictionaryValue*>(serializer.Deserialize(&error_code,
    116                                                              &error)));
    117     EXPECT_EQ(0, error_code) << error;
    118     if (error_code != 0)
    119       return NULL;
    120 
    121     EXPECT_TRUE(valid_value.get());
    122     if (!valid_value)
    123       return NULL;
    124 
    125     scoped_refptr<Extension> extension =
    126         Extension::Create(test_file, location, *valid_value,
    127                           Extension::NO_FLAGS, &error);
    128     EXPECT_TRUE(extension.get()) << error;
    129     if (extension.get())
    130       extension_service_->AddExtension(extension.get());
    131     return extension;
    132   }
    133 
    134   // testing::Test overrides:
    135   virtual void SetUp() OVERRIDE {
    136     file_thread_.Start();
    137     io_thread_.Start();
    138     profile_.reset(new TestingProfile);
    139     CommandLine command_line(CommandLine::NO_PROGRAM);
    140     extension_service_ = static_cast<extensions::TestExtensionSystem*>(
    141         extensions::ExtensionSystem::Get(profile_.get()))->
    142         CreateExtensionService(&command_line, base::FilePath(), false);
    143   }
    144 
    145   virtual void TearDown() OVERRIDE {
    146     profile_.reset();  // Get all DeleteSoon calls sent to ui_loop_.
    147     ui_loop_.RunUntilIdle();
    148   }
    149 
    150   // ExtensionActionIconFactory::Observer overrides:
    151   virtual void OnIconUpdated() OVERRIDE {
    152     if (quit_in_icon_updated_)
    153       base::MessageLoop::current()->Quit();
    154   }
    155 
    156   gfx::ImageSkia GetFavicon() {
    157     return *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
    158         IDR_EXTENSIONS_FAVICON);
    159   }
    160 
    161   ExtensionAction* GetBrowserAction(const Extension& extension) {
    162     return ExtensionActionManager::Get(profile())->GetBrowserAction(extension);
    163   }
    164 
    165   TestingProfile* profile() { return profile_.get(); }
    166 
    167  private:
    168   bool quit_in_icon_updated_;
    169   base::MessageLoop ui_loop_;
    170   content::TestBrowserThread ui_thread_;
    171   content::TestBrowserThread file_thread_;
    172   content::TestBrowserThread io_thread_;
    173   scoped_ptr<TestingProfile> profile_;
    174   ExtensionService* extension_service_;
    175 
    176 #if defined OS_CHROMEOS
    177   chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
    178   chromeos::ScopedTestCrosSettings test_cros_settings_;
    179   chromeos::ScopedTestUserManager test_user_manager_;
    180 #endif
    181 
    182   DISALLOW_COPY_AND_ASSIGN(ExtensionActionIconFactoryTest);
    183 };
    184 
    185 // If there is no default icon, and the icon has not been set using |SetIcon|,
    186 // the factory should return favicon.
    187 TEST_F(ExtensionActionIconFactoryTest, NoIcons) {
    188   // Load an extension that has browser action without default icon set in the
    189   // manifest and does not call |SetIcon| by default.
    190   scoped_refptr<Extension> extension(CreateExtension(
    191       "browser_action/no_icon", Manifest::INVALID_LOCATION));
    192   ASSERT_TRUE(extension.get() != NULL);
    193   ExtensionAction* browser_action = GetBrowserAction(*extension.get());
    194   ASSERT_TRUE(browser_action);
    195   ASSERT_FALSE(browser_action->default_icon());
    196   ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
    197 
    198   gfx::ImageSkia favicon = GetFavicon();
    199 
    200   ExtensionActionIconFactory icon_factory(
    201       profile(), extension.get(), browser_action, this);
    202 
    203   gfx::Image icon = icon_factory.GetIcon(0);
    204 
    205   EXPECT_TRUE(ImageRepsAreEqual(
    206       favicon.GetRepresentation(ui::SCALE_FACTOR_100P),
    207       icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
    208 }
    209 
    210 // If the icon has been set using |SetIcon|, the factory should return that
    211 // icon.
    212 TEST_F(ExtensionActionIconFactoryTest, AfterSetIcon) {
    213   // Load an extension that has browser action without default icon set in the
    214   // manifest and does not call |SetIcon| by default (but has an browser action
    215   // icon resource).
    216   scoped_refptr<Extension> extension(CreateExtension(
    217       "browser_action/no_icon", Manifest::INVALID_LOCATION));
    218   ASSERT_TRUE(extension.get() != NULL);
    219   ExtensionAction* browser_action = GetBrowserAction(*extension.get());
    220   ASSERT_TRUE(browser_action);
    221   ASSERT_FALSE(browser_action->default_icon());
    222   ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
    223 
    224   gfx::Image set_icon = LoadIcon("browser_action/no_icon/icon.png");
    225   ASSERT_FALSE(set_icon.IsEmpty());
    226 
    227   browser_action->SetIcon(0, set_icon);
    228 
    229   ASSERT_FALSE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
    230 
    231   ExtensionActionIconFactory icon_factory(
    232       profile(), extension.get(), browser_action, this);
    233 
    234   gfx::Image icon = icon_factory.GetIcon(0);
    235 
    236   EXPECT_TRUE(ImageRepsAreEqual(
    237       set_icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P),
    238       icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
    239 
    240   // It should still return favicon for another tabs.
    241   icon = icon_factory.GetIcon(1);
    242 
    243   EXPECT_TRUE(ImageRepsAreEqual(
    244       GetFavicon().GetRepresentation(ui::SCALE_FACTOR_100P),
    245       icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
    246 }
    247 
    248 // If there is a default icon, and the icon has not been set using |SetIcon|,
    249 // the factory should return the default icon.
    250 TEST_F(ExtensionActionIconFactoryTest, DefaultIcon) {
    251   // Load an extension that has browser action without default icon set in the
    252   // manifest and does not call |SetIcon| by default (but has an browser action
    253   // icon resource).
    254   scoped_refptr<Extension> extension(CreateExtension(
    255       "browser_action/no_icon", Manifest::INVALID_LOCATION));
    256   ASSERT_TRUE(extension.get() != NULL);
    257   ExtensionAction* browser_action = GetBrowserAction(*extension.get());
    258   ASSERT_TRUE(browser_action);
    259   ASSERT_FALSE(browser_action->default_icon());
    260   ASSERT_TRUE(browser_action->GetExplicitlySetIcon(0 /*tab id*/).isNull());
    261 
    262   gfx::Image default_icon =
    263       EnsureImageSize(LoadIcon("browser_action/no_icon/icon.png"), 19);
    264   ASSERT_FALSE(default_icon.IsEmpty());
    265 
    266   scoped_ptr<ExtensionIconSet> default_icon_set(new ExtensionIconSet());
    267   default_icon_set->Add(19, "icon.png");
    268 
    269   browser_action->set_default_icon(default_icon_set.Pass());
    270   ASSERT_TRUE(browser_action->default_icon());
    271 
    272   ExtensionActionIconFactory icon_factory(
    273       profile(), extension.get(), browser_action, this);
    274 
    275   gfx::Image icon = icon_factory.GetIcon(0);
    276 
    277   // The icon should be loaded asynchronously. Initially a transparent icon
    278   // should be returned.
    279   EXPECT_TRUE(ImageRepsAreEqual(
    280       CreateBlankRep(19, ui::SCALE_FACTOR_100P),
    281       icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
    282 
    283   WaitForIconUpdate();
    284 
    285   icon = icon_factory.GetIcon(0);
    286 
    287   // The default icon representation should be loaded at this point.
    288   EXPECT_TRUE(ImageRepsAreEqual(
    289       default_icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P),
    290       icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
    291 
    292   // The same icon should be returned for the other tabs.
    293   icon = icon_factory.GetIcon(1);
    294 
    295   EXPECT_TRUE(ImageRepsAreEqual(
    296       default_icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P),
    297       icon.ToImageSkia()->GetRepresentation(ui::SCALE_FACTOR_100P)));
    298 
    299 }
    300 
    301 }  // namespace
    302 }  // namespace extensions
    303