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 34using content::BrowserThread; 35 36namespace extensions { 37namespace { 38 39bool 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 45gfx::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 56gfx::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 64gfx::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 80class 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. 184TEST_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. 209TEST_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. 247TEST_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