1 // Copyright 2014 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 "extensions/browser/image_loader.h" 6 7 #include "base/files/file_path.h" 8 #include "base/json/json_file_value_serializer.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/path_service.h" 11 #include "base/strings/string_util.h" 12 #include "chrome/browser/chrome_notification_types.h" 13 #include "chrome/common/chrome_paths.h" 14 #include "content/public/browser/notification_service.h" 15 #include "content/public/test/test_browser_thread.h" 16 #include "extensions/browser/component_extension_resource_manager.h" 17 #include "extensions/browser/extensions_browser_client.h" 18 #include "extensions/common/constants.h" 19 #include "extensions/common/extension.h" 20 #include "extensions/common/extension_icon_set.h" 21 #include "extensions/common/extension_resource.h" 22 #include "extensions/common/manifest.h" 23 #include "extensions/common/manifest_handlers/icons_handler.h" 24 #include "testing/gtest/include/gtest/gtest.h" 25 #include "third_party/skia/include/core/SkBitmap.h" 26 #include "ui/gfx/image/image.h" 27 #include "ui/gfx/image/image_family.h" 28 #include "ui/gfx/image/image_skia.h" 29 #include "ui/gfx/size.h" 30 31 #if defined(OS_CHROMEOS) 32 #include "ui/file_manager/grit/file_manager_resources.h" 33 #endif 34 35 using content::BrowserThread; 36 using extensions::Extension; 37 using extensions::ExtensionResource; 38 using extensions::ImageLoader; 39 using extensions::Manifest; 40 using extensions::UnloadedExtensionInfo; 41 42 class ImageLoaderTest : public testing::Test { 43 public: 44 ImageLoaderTest() 45 : image_loaded_count_(0), 46 quit_in_image_loaded_(false), 47 ui_thread_(BrowserThread::UI, &ui_loop_), 48 file_thread_(BrowserThread::FILE), 49 io_thread_(BrowserThread::IO) { 50 } 51 52 void OnImageLoaded(const gfx::Image& image) { 53 image_loaded_count_++; 54 if (quit_in_image_loaded_) 55 base::MessageLoop::current()->Quit(); 56 image_ = image; 57 } 58 59 void OnImageFamilyLoaded(const gfx::ImageFamily& image_family) { 60 image_loaded_count_++; 61 if (quit_in_image_loaded_) 62 base::MessageLoop::current()->Quit(); 63 image_family_ = image_family; 64 } 65 66 void WaitForImageLoad() { 67 quit_in_image_loaded_ = true; 68 base::MessageLoop::current()->Run(); 69 quit_in_image_loaded_ = false; 70 } 71 72 int image_loaded_count() { 73 int result = image_loaded_count_; 74 image_loaded_count_ = 0; 75 return result; 76 } 77 78 scoped_refptr<Extension> CreateExtension(const char* name, 79 Manifest::Location location) { 80 // Create and load an extension. 81 base::FilePath test_file; 82 if (!PathService::Get(chrome::DIR_TEST_DATA, &test_file)) { 83 EXPECT_FALSE(true); 84 return NULL; 85 } 86 test_file = test_file.AppendASCII("extensions") 87 .AppendASCII(name); 88 int error_code = 0; 89 std::string error; 90 JSONFileValueSerializer serializer(test_file.AppendASCII("app.json")); 91 scoped_ptr<base::DictionaryValue> valid_value( 92 static_cast<base::DictionaryValue*>(serializer.Deserialize(&error_code, 93 &error))); 94 EXPECT_EQ(0, error_code) << error; 95 if (error_code != 0) 96 return NULL; 97 98 EXPECT_TRUE(valid_value.get()); 99 if (!valid_value) 100 return NULL; 101 102 if (location == Manifest::COMPONENT) { 103 if (!PathService::Get(chrome::DIR_RESOURCES, &test_file)) { 104 EXPECT_FALSE(true); 105 return NULL; 106 } 107 test_file = test_file.AppendASCII(name); 108 } 109 return Extension::Create(test_file, location, *valid_value, 110 Extension::NO_FLAGS, &error); 111 } 112 113 gfx::Image image_; 114 gfx::ImageFamily image_family_; 115 116 private: 117 virtual void SetUp() OVERRIDE { 118 testing::Test::SetUp(); 119 file_thread_.Start(); 120 io_thread_.Start(); 121 } 122 123 int image_loaded_count_; 124 bool quit_in_image_loaded_; 125 base::MessageLoop ui_loop_; 126 content::TestBrowserThread ui_thread_; 127 content::TestBrowserThread file_thread_; 128 content::TestBrowserThread io_thread_; 129 }; 130 131 // Tests loading an image works correctly. 132 TEST_F(ImageLoaderTest, LoadImage) { 133 scoped_refptr<Extension> extension(CreateExtension( 134 "image_loading_tracker", Manifest::INVALID_LOCATION)); 135 ASSERT_TRUE(extension.get() != NULL); 136 137 ExtensionResource image_resource = extensions::IconsInfo::GetIconResource( 138 extension.get(), 139 extension_misc::EXTENSION_ICON_SMALLISH, 140 ExtensionIconSet::MATCH_EXACTLY); 141 gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH, 142 extension_misc::EXTENSION_ICON_SMALLISH); 143 ImageLoader loader; 144 loader.LoadImageAsync(extension.get(), 145 image_resource, 146 max_size, 147 base::Bind(&ImageLoaderTest::OnImageLoaded, 148 base::Unretained(this))); 149 150 // The image isn't cached, so we should not have received notification. 151 EXPECT_EQ(0, image_loaded_count()); 152 153 WaitForImageLoad(); 154 155 // We should have gotten the image. 156 EXPECT_EQ(1, image_loaded_count()); 157 158 // Check that the image was loaded. 159 EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, 160 image_.ToSkBitmap()->width()); 161 } 162 163 // Tests deleting an extension while waiting for the image to load doesn't cause 164 // problems. 165 TEST_F(ImageLoaderTest, DeleteExtensionWhileWaitingForCache) { 166 scoped_refptr<Extension> extension(CreateExtension( 167 "image_loading_tracker", Manifest::INVALID_LOCATION)); 168 ASSERT_TRUE(extension.get() != NULL); 169 170 ExtensionResource image_resource = extensions::IconsInfo::GetIconResource( 171 extension.get(), 172 extension_misc::EXTENSION_ICON_SMALLISH, 173 ExtensionIconSet::MATCH_EXACTLY); 174 gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH, 175 extension_misc::EXTENSION_ICON_SMALLISH); 176 ImageLoader loader; 177 std::set<int> sizes; 178 sizes.insert(extension_misc::EXTENSION_ICON_SMALLISH); 179 loader.LoadImageAsync(extension.get(), 180 image_resource, 181 max_size, 182 base::Bind(&ImageLoaderTest::OnImageLoaded, 183 base::Unretained(this))); 184 185 // The image isn't cached, so we should not have received notification. 186 EXPECT_EQ(0, image_loaded_count()); 187 188 // Send out notification the extension was uninstalled. 189 UnloadedExtensionInfo details(extension.get(), 190 UnloadedExtensionInfo::REASON_UNINSTALL); 191 content::NotificationService::current()->Notify( 192 chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED, 193 content::NotificationService::AllSources(), 194 content::Details<UnloadedExtensionInfo>(&details)); 195 196 // Chuck the extension, that way if anyone tries to access it we should crash 197 // or get valgrind errors. 198 extension = NULL; 199 200 WaitForImageLoad(); 201 202 // Even though we deleted the extension, we should still get the image. 203 // We should still have gotten the image. 204 EXPECT_EQ(1, image_loaded_count()); 205 206 // Check that the image was loaded. 207 EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, 208 image_.ToSkBitmap()->width()); 209 } 210 211 // Tests loading multiple dimensions of the same image. 212 TEST_F(ImageLoaderTest, MultipleImages) { 213 scoped_refptr<Extension> extension(CreateExtension( 214 "image_loading_tracker", Manifest::INVALID_LOCATION)); 215 ASSERT_TRUE(extension.get() != NULL); 216 217 std::vector<ImageLoader::ImageRepresentation> info_list; 218 int sizes[] = {extension_misc::EXTENSION_ICON_BITTY, 219 extension_misc::EXTENSION_ICON_SMALLISH, }; 220 for (size_t i = 0; i < arraysize(sizes); ++i) { 221 ExtensionResource resource = extensions::IconsInfo::GetIconResource( 222 extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY); 223 info_list.push_back(ImageLoader::ImageRepresentation( 224 resource, 225 ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER, 226 gfx::Size(sizes[i], sizes[i]), 227 ui::SCALE_FACTOR_NONE)); 228 } 229 230 ImageLoader loader; 231 loader.LoadImagesAsync(extension.get(), info_list, 232 base::Bind(&ImageLoaderTest::OnImageLoaded, 233 base::Unretained(this))); 234 235 // The image isn't cached, so we should not have received notification. 236 EXPECT_EQ(0, image_loaded_count()); 237 238 WaitForImageLoad(); 239 240 // We should have gotten the image. 241 EXPECT_EQ(1, image_loaded_count()); 242 243 // Check that all images were loaded. 244 std::vector<gfx::ImageSkiaRep> image_reps = 245 image_.ToImageSkia()->image_reps(); 246 ASSERT_EQ(2u, image_reps.size()); 247 248 const gfx::ImageSkiaRep* img_rep1 = &image_reps[0]; 249 const gfx::ImageSkiaRep* img_rep2 = &image_reps[1]; 250 EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY, 251 img_rep1->pixel_width()); 252 EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, 253 img_rep2->pixel_width()); 254 } 255 256 // Tests loading multiple dimensions of the same image into an image family. 257 TEST_F(ImageLoaderTest, LoadImageFamily) { 258 scoped_refptr<Extension> extension( 259 CreateExtension("image_loading_tracker", Manifest::INVALID_LOCATION)); 260 ASSERT_TRUE(extension.get() != NULL); 261 262 std::vector<ImageLoader::ImageRepresentation> info_list; 263 int sizes[] = {extension_misc::EXTENSION_ICON_BITTY, 264 extension_misc::EXTENSION_ICON_SMALLISH, }; 265 for (size_t i = 0; i < arraysize(sizes); ++i) { 266 ExtensionResource resource = extensions::IconsInfo::GetIconResource( 267 extension.get(), sizes[i], ExtensionIconSet::MATCH_EXACTLY); 268 info_list.push_back(ImageLoader::ImageRepresentation( 269 resource, 270 ImageLoader::ImageRepresentation::NEVER_RESIZE, 271 gfx::Size(sizes[i], sizes[i]), 272 ui::SCALE_FACTOR_100P)); 273 } 274 275 // Add a second icon of 200P which should get grouped with the smaller icon's 276 // ImageSkia. 277 ExtensionResource resource = extensions::IconsInfo::GetIconResource( 278 extension.get(), 279 extension_misc::EXTENSION_ICON_SMALLISH, 280 ExtensionIconSet::MATCH_EXACTLY); 281 info_list.push_back(ImageLoader::ImageRepresentation( 282 resource, 283 ImageLoader::ImageRepresentation::NEVER_RESIZE, 284 gfx::Size(extension_misc::EXTENSION_ICON_BITTY, 285 extension_misc::EXTENSION_ICON_BITTY), 286 ui::SCALE_FACTOR_200P)); 287 288 ImageLoader loader; 289 loader.LoadImageFamilyAsync(extension.get(), 290 info_list, 291 base::Bind(&ImageLoaderTest::OnImageFamilyLoaded, 292 base::Unretained(this))); 293 294 // The image isn't cached, so we should not have received notification. 295 EXPECT_EQ(0, image_loaded_count()); 296 297 WaitForImageLoad(); 298 299 // We should have gotten the image. 300 EXPECT_EQ(1, image_loaded_count()); 301 302 // Check that all images were loaded. 303 for (size_t i = 0; i < arraysize(sizes); ++i) { 304 const gfx::Image* image = image_family_.GetBest(sizes[i], sizes[i]); 305 EXPECT_EQ(sizes[i], image->Width()); 306 } 307 308 // Check the smaller image has 2 representations of different scale factors. 309 std::vector<gfx::ImageSkiaRep> image_reps = 310 image_family_.GetBest(extension_misc::EXTENSION_ICON_BITTY, 311 extension_misc::EXTENSION_ICON_BITTY) 312 ->ToImageSkia() 313 ->image_reps(); 314 315 ASSERT_EQ(2u, image_reps.size()); 316 317 const gfx::ImageSkiaRep* img_rep1 = &image_reps[0]; 318 const gfx::ImageSkiaRep* img_rep2 = &image_reps[1]; 319 EXPECT_EQ(extension_misc::EXTENSION_ICON_BITTY, img_rep1->pixel_width()); 320 EXPECT_EQ(1.0f, img_rep1->scale()); 321 EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, img_rep2->pixel_width()); 322 EXPECT_EQ(2.0f, img_rep2->scale()); 323 } 324 325 // Tests IsComponentExtensionResource function. 326 // TODO(mukai): move this to ChromeComponentExtensionResourceManager's test. 327 TEST_F(ImageLoaderTest, IsComponentExtensionResource) { 328 extensions::ComponentExtensionResourceManager* resource_manager = 329 extensions::ExtensionsBrowserClient::Get()-> 330 GetComponentExtensionResourceManager(); 331 if (!resource_manager) 332 return; 333 334 scoped_refptr<Extension> extension(CreateExtension( 335 "file_manager", Manifest::COMPONENT)); 336 ASSERT_TRUE(extension.get() != NULL); 337 338 ExtensionResource resource = extensions::IconsInfo::GetIconResource( 339 extension.get(), 340 extension_misc::EXTENSION_ICON_BITTY, 341 ExtensionIconSet::MATCH_EXACTLY); 342 343 #if defined(OS_CHROMEOS) 344 int resource_id; 345 ASSERT_TRUE(resource_manager->IsComponentExtensionResource( 346 extension->path(), 347 resource.relative_path(), 348 &resource_id)); 349 ASSERT_EQ(IDR_FILE_MANAGER_ICON_16, resource_id); 350 #endif 351 } 352