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