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/image_loader.h" 6 7 #include <map> 8 #include <vector> 9 10 #include "base/callback.h" 11 #include "base/compiler_specific.h" 12 #include "base/file_util.h" 13 #include "base/lazy_instance.h" 14 #include "base/path_service.h" 15 #include "base/strings/string_number_conversions.h" 16 #include "base/threading/sequenced_worker_pool.h" 17 #include "chrome/browser/extensions/image_loader_factory.h" 18 #include "chrome/common/chrome_paths.h" 19 #include "chrome/common/extensions/extension.h" 20 #include "content/public/browser/browser_thread.h" 21 #include "grit/chrome_unscaled_resources.h" 22 #include "grit/component_extension_resources_map.h" 23 #include "grit/theme_resources.h" 24 #include "skia/ext/image_operations.h" 25 #include "ui/base/resource/resource_bundle.h" 26 #include "ui/gfx/codec/png_codec.h" 27 #include "ui/gfx/image/image_skia.h" 28 29 #if defined(USE_AURA) 30 #include "ui/keyboard/keyboard_util.h" 31 #endif 32 33 using content::BrowserThread; 34 using extensions::Extension; 35 using extensions::ImageLoader; 36 using extensions::Manifest; 37 38 namespace { 39 40 bool ShouldResizeImageRepresentation( 41 ImageLoader::ImageRepresentation::ResizeCondition resize_method, 42 const gfx::Size& decoded_size, 43 const gfx::Size& desired_size) { 44 switch (resize_method) { 45 case ImageLoader::ImageRepresentation::ALWAYS_RESIZE: 46 return decoded_size != desired_size; 47 case ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER: 48 return decoded_size.width() > desired_size.width() || 49 decoded_size.height() > desired_size.height(); 50 default: 51 NOTREACHED(); 52 return false; 53 } 54 } 55 56 SkBitmap ResizeIfNeeded(const SkBitmap& bitmap, 57 const ImageLoader::ImageRepresentation& image_info) { 58 gfx::Size original_size(bitmap.width(), bitmap.height()); 59 if (ShouldResizeImageRepresentation(image_info.resize_condition, 60 original_size, 61 image_info.desired_size)) { 62 return skia::ImageOperations::Resize( 63 bitmap, skia::ImageOperations::RESIZE_LANCZOS3, 64 image_info.desired_size.width(), image_info.desired_size.height()); 65 } 66 67 return bitmap; 68 } 69 70 void LoadResourceOnUIThread(int resource_id, SkBitmap* bitmap) { 71 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 72 73 gfx::ImageSkia image( 74 *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id)); 75 image.MakeThreadSafe(); 76 *bitmap = *image.bitmap(); 77 } 78 79 void LoadImageOnBlockingPool(const ImageLoader::ImageRepresentation& image_info, 80 SkBitmap* bitmap) { 81 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 82 83 // Read the file from disk. 84 std::string file_contents; 85 base::FilePath path = image_info.resource.GetFilePath(); 86 if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) { 87 return; 88 } 89 90 const unsigned char* data = 91 reinterpret_cast<const unsigned char*>(file_contents.data()); 92 // Note: This class only decodes bitmaps from extension resources. Chrome 93 // doesn't (for security reasons) directly load extension resources provided 94 // by the extension author, but instead decodes them in a separate 95 // locked-down utility process. Only if the decoding succeeds is the image 96 // saved from memory to disk and subsequently used in the Chrome UI. 97 // Chrome is therefore decoding bitmaps here that were generated by Chrome. 98 gfx::PNGCodec::Decode(data, file_contents.length(), bitmap); 99 } 100 101 // Add the resources from |entries| (there are |size| of them) to 102 // |path_to_resource_id| after normalizing separators. 103 void AddComponentResourceEntries( 104 std::map<base::FilePath, int>* path_to_resource_id, 105 const GritResourceMap* entries, 106 size_t size) { 107 for (size_t i = 0; i < size; ++i) { 108 base::FilePath resource_path = base::FilePath().AppendASCII( 109 entries[i].name); 110 resource_path = resource_path.NormalizePathSeparators(); 111 112 DCHECK(path_to_resource_id->find(resource_path) == 113 path_to_resource_id->end()); 114 (*path_to_resource_id)[resource_path] = entries[i].value; 115 } 116 } 117 118 } // namespace 119 120 namespace extensions { 121 122 //////////////////////////////////////////////////////////////////////////////// 123 // ImageLoader::ImageRepresentation 124 125 ImageLoader::ImageRepresentation::ImageRepresentation( 126 const ExtensionResource& resource, 127 ResizeCondition resize_condition, 128 const gfx::Size& desired_size, 129 ui::ScaleFactor scale_factor) 130 : resource(resource), 131 resize_condition(resize_condition), 132 desired_size(desired_size), 133 scale_factor(scale_factor) { 134 } 135 136 ImageLoader::ImageRepresentation::~ImageRepresentation() { 137 } 138 139 //////////////////////////////////////////////////////////////////////////////// 140 // ImageLoader::LoadResult 141 142 struct ImageLoader::LoadResult { 143 LoadResult(const SkBitmap& bitmap, 144 const gfx::Size& original_size, 145 const ImageRepresentation& image_representation); 146 ~LoadResult(); 147 148 SkBitmap bitmap; 149 gfx::Size original_size; 150 ImageRepresentation image_representation; 151 }; 152 153 ImageLoader::LoadResult::LoadResult( 154 const SkBitmap& bitmap, 155 const gfx::Size& original_size, 156 const ImageLoader::ImageRepresentation& image_representation) 157 : bitmap(bitmap), 158 original_size(original_size), 159 image_representation(image_representation) { 160 } 161 162 ImageLoader::LoadResult::~LoadResult() { 163 } 164 165 //////////////////////////////////////////////////////////////////////////////// 166 // ImageLoader 167 168 ImageLoader::ImageLoader() 169 : weak_ptr_factory_(this) { 170 } 171 172 ImageLoader::~ImageLoader() { 173 } 174 175 // static 176 ImageLoader* ImageLoader::Get(Profile* profile) { 177 return ImageLoaderFactory::GetForProfile(profile); 178 } 179 180 // A map from a resource path to the resource ID. Used only by 181 // IsComponentExtensionResource below. 182 static base::LazyInstance<std::map<base::FilePath, int> > path_to_resource_id = 183 LAZY_INSTANCE_INITIALIZER; 184 185 // static 186 bool ImageLoader::IsComponentExtensionResource( 187 const base::FilePath& extension_path, 188 const base::FilePath& resource_path, 189 int* resource_id) { 190 static const GritResourceMap kExtraComponentExtensionResources[] = { 191 {"web_store/webstore_icon_128.png", IDR_WEBSTORE_ICON}, 192 {"web_store/webstore_icon_16.png", IDR_WEBSTORE_ICON_16}, 193 {"chrome_app/product_logo_128.png", IDR_PRODUCT_LOGO_128}, 194 {"chrome_app/product_logo_16.png", IDR_PRODUCT_LOGO_16}, 195 #if defined(ENABLE_SETTINGS_APP) 196 {"settings_app/settings_app_icon_128.png", IDR_SETTINGS_APP_ICON_128}, 197 {"settings_app/settings_app_icon_16.png", IDR_SETTINGS_APP_ICON_16}, 198 {"settings_app/settings_app_icon_32.png", IDR_SETTINGS_APP_ICON_32}, 199 {"settings_app/settings_app_icon_48.png", IDR_SETTINGS_APP_ICON_48}, 200 #endif 201 }; 202 203 if (path_to_resource_id.Get().empty()) { 204 AddComponentResourceEntries( 205 path_to_resource_id.Pointer(), 206 kComponentExtensionResources, 207 kComponentExtensionResourcesSize); 208 AddComponentResourceEntries( 209 path_to_resource_id.Pointer(), 210 kExtraComponentExtensionResources, 211 arraysize(kExtraComponentExtensionResources)); 212 #if defined(USE_AURA) 213 if (keyboard::IsKeyboardEnabled()) { 214 size_t size; 215 const GritResourceMap* keyboard_resources = 216 keyboard::GetKeyboardExtensionResources(&size); 217 AddComponentResourceEntries( 218 path_to_resource_id.Pointer(), keyboard_resources, size); 219 } 220 #endif 221 } 222 223 base::FilePath directory_path = extension_path; 224 base::FilePath resources_dir; 225 base::FilePath relative_path; 226 if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) || 227 !resources_dir.AppendRelativePath(directory_path, &relative_path)) { 228 return false; 229 } 230 relative_path = relative_path.Append(resource_path); 231 relative_path = relative_path.NormalizePathSeparators(); 232 233 std::map<base::FilePath, int>::const_iterator entry = 234 path_to_resource_id.Get().find(relative_path); 235 if (entry != path_to_resource_id.Get().end()) 236 *resource_id = entry->second; 237 238 return entry != path_to_resource_id.Get().end(); 239 } 240 241 void ImageLoader::LoadImageAsync( 242 const Extension* extension, 243 const ExtensionResource& resource, 244 const gfx::Size& max_size, 245 const base::Callback<void(const gfx::Image&)>& callback) { 246 std::vector<ImageRepresentation> info_list; 247 info_list.push_back(ImageRepresentation( 248 resource, 249 ImageRepresentation::RESIZE_WHEN_LARGER, 250 max_size, 251 ui::SCALE_FACTOR_100P)); 252 LoadImagesAsync(extension, info_list, callback); 253 } 254 255 void ImageLoader::LoadImagesAsync( 256 const Extension* extension, 257 const std::vector<ImageRepresentation>& info_list, 258 const base::Callback<void(const gfx::Image&)>& callback) { 259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 260 261 // Loading an image from the cache and loading resources have to happen 262 // on the UI thread. So do those two things first, and pass the rest of the 263 // work of as a blocking pool task. 264 265 std::vector<SkBitmap> bitmaps; 266 bitmaps.resize(info_list.size()); 267 268 int i = 0; 269 for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin(); 270 it != info_list.end(); ++it, ++i) { 271 DCHECK(it->resource.relative_path().empty() || 272 extension->path() == it->resource.extension_root()); 273 274 int resource_id; 275 if (extension->location() == Manifest::COMPONENT && 276 IsComponentExtensionResource(extension->path(), 277 it->resource.relative_path(), 278 &resource_id)) { 279 LoadResourceOnUIThread(resource_id, &bitmaps[i]); 280 } 281 } 282 283 DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 284 std::vector<LoadResult>* load_result = new std::vector<LoadResult>; 285 BrowserThread::PostBlockingPoolTaskAndReply( 286 FROM_HERE, 287 base::Bind(LoadImagesOnBlockingPool, info_list, bitmaps, load_result), 288 base::Bind(&ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(), 289 base::Owned(load_result), callback)); 290 } 291 292 // static 293 void ImageLoader::LoadImagesOnBlockingPool( 294 const std::vector<ImageRepresentation>& info_list, 295 const std::vector<SkBitmap>& bitmaps, 296 std::vector<LoadResult>* load_result) { 297 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 298 299 int i = 0; 300 for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin(); 301 it != info_list.end(); ++it, ++i) { 302 // If we don't have a path there isn't anything we can do, just skip it. 303 if (it->resource.relative_path().empty()) 304 continue; 305 306 SkBitmap bitmap; 307 if (!bitmaps[i].isNull()) { 308 bitmap = bitmaps[i]; 309 } else { 310 LoadImageOnBlockingPool(*it, &bitmap); 311 } 312 313 // If the image failed to load, skip it. 314 if (bitmap.isNull() || bitmap.empty()) 315 continue; 316 317 gfx::Size original_size(bitmap.width(), bitmap.height()); 318 bitmap = ResizeIfNeeded(bitmap, *it); 319 320 load_result->push_back(LoadResult(bitmap, original_size, *it)); 321 } 322 } 323 324 void ImageLoader::ReplyBack( 325 const std::vector<LoadResult>* load_result, 326 const base::Callback<void(const gfx::Image&)>& callback) { 327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 328 329 gfx::ImageSkia image_skia; 330 331 for (std::vector<LoadResult>::const_iterator it = load_result->begin(); 332 it != load_result->end(); ++it) { 333 const SkBitmap& bitmap = it->bitmap; 334 const ImageRepresentation& image_rep = it->image_representation; 335 336 image_skia.AddRepresentation(gfx::ImageSkiaRep( 337 bitmap, image_rep.scale_factor)); 338 } 339 340 gfx::Image image; 341 if (!image_skia.isNull()) { 342 image_skia.MakeThreadSafe(); 343 image = gfx::Image(image_skia); 344 } 345 346 callback.Run(image); 347 } 348 349 } // namespace extensions 350