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