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