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 "content/public/browser/browser_thread.h" 20 #include "extensions/common/extension.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() || !base::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 namespace { 166 167 // Need to be after ImageRepresentation and LoadResult are defined. 168 std::vector<ImageLoader::LoadResult> LoadImagesOnBlockingPool( 169 const std::vector<ImageLoader::ImageRepresentation>& info_list, 170 const std::vector<SkBitmap>& bitmaps) { 171 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 172 std::vector<ImageLoader::LoadResult> load_result; 173 174 for (size_t i = 0; i < info_list.size(); ++i) { 175 const ImageLoader::ImageRepresentation& image = info_list[i]; 176 177 // If we don't have a path there isn't anything we can do, just skip it. 178 if (image.resource.relative_path().empty()) 179 continue; 180 181 SkBitmap bitmap; 182 if (bitmaps[i].isNull()) 183 LoadImageOnBlockingPool(image, &bitmap); 184 else 185 bitmap = bitmaps[i]; 186 187 // If the image failed to load, skip it. 188 if (bitmap.isNull() || bitmap.empty()) 189 continue; 190 191 gfx::Size original_size(bitmap.width(), bitmap.height()); 192 bitmap = ResizeIfNeeded(bitmap, image); 193 194 load_result.push_back( 195 ImageLoader::LoadResult(bitmap, original_size, image)); 196 } 197 198 return load_result; 199 } 200 201 } // namespace 202 203 //////////////////////////////////////////////////////////////////////////////// 204 // ImageLoader 205 206 ImageLoader::ImageLoader() 207 : weak_ptr_factory_(this) { 208 } 209 210 ImageLoader::~ImageLoader() { 211 } 212 213 // static 214 ImageLoader* ImageLoader::Get(content::BrowserContext* context) { 215 return ImageLoaderFactory::GetForBrowserContext(context); 216 } 217 218 // A map from a resource path to the resource ID. Used only by 219 // IsComponentExtensionResource below. 220 static base::LazyInstance<std::map<base::FilePath, int> > path_to_resource_id = 221 LAZY_INSTANCE_INITIALIZER; 222 223 // static 224 bool ImageLoader::IsComponentExtensionResource( 225 const base::FilePath& extension_path, 226 const base::FilePath& resource_path, 227 int* resource_id) { 228 static const GritResourceMap kExtraComponentExtensionResources[] = { 229 {"web_store/webstore_icon_128.png", IDR_WEBSTORE_ICON}, 230 {"web_store/webstore_icon_16.png", IDR_WEBSTORE_ICON_16}, 231 {"chrome_app/product_logo_128.png", IDR_PRODUCT_LOGO_128}, 232 {"chrome_app/product_logo_16.png", IDR_PRODUCT_LOGO_16}, 233 #if defined(ENABLE_SETTINGS_APP) 234 {"settings_app/settings_app_icon_128.png", IDR_SETTINGS_APP_ICON_128}, 235 {"settings_app/settings_app_icon_16.png", IDR_SETTINGS_APP_ICON_16}, 236 {"settings_app/settings_app_icon_32.png", IDR_SETTINGS_APP_ICON_32}, 237 {"settings_app/settings_app_icon_48.png", IDR_SETTINGS_APP_ICON_48}, 238 #endif 239 }; 240 241 if (path_to_resource_id.Get().empty()) { 242 AddComponentResourceEntries( 243 path_to_resource_id.Pointer(), 244 kComponentExtensionResources, 245 kComponentExtensionResourcesSize); 246 AddComponentResourceEntries( 247 path_to_resource_id.Pointer(), 248 kExtraComponentExtensionResources, 249 arraysize(kExtraComponentExtensionResources)); 250 #if defined(USE_AURA) 251 if (keyboard::IsKeyboardEnabled()) { 252 size_t size; 253 const GritResourceMap* keyboard_resources = 254 keyboard::GetKeyboardExtensionResources(&size); 255 AddComponentResourceEntries( 256 path_to_resource_id.Pointer(), keyboard_resources, size); 257 } 258 #endif 259 } 260 261 base::FilePath directory_path = extension_path; 262 base::FilePath resources_dir; 263 base::FilePath relative_path; 264 if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) || 265 !resources_dir.AppendRelativePath(directory_path, &relative_path)) { 266 return false; 267 } 268 relative_path = relative_path.Append(resource_path); 269 relative_path = relative_path.NormalizePathSeparators(); 270 271 std::map<base::FilePath, int>::const_iterator entry = 272 path_to_resource_id.Get().find(relative_path); 273 if (entry != path_to_resource_id.Get().end()) 274 *resource_id = entry->second; 275 276 return entry != path_to_resource_id.Get().end(); 277 } 278 279 void ImageLoader::LoadImageAsync(const Extension* extension, 280 const ExtensionResource& resource, 281 const gfx::Size& max_size, 282 const ImageLoaderCallback& callback) { 283 std::vector<ImageRepresentation> info_list; 284 info_list.push_back(ImageRepresentation( 285 resource, 286 ImageRepresentation::RESIZE_WHEN_LARGER, 287 max_size, 288 ui::SCALE_FACTOR_100P)); 289 LoadImagesAsync(extension, info_list, callback); 290 } 291 292 void ImageLoader::LoadImagesAsync( 293 const Extension* extension, 294 const std::vector<ImageRepresentation>& info_list, 295 const ImageLoaderCallback& callback) { 296 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 297 298 // Loading an image from the cache and loading resources have to happen 299 // on the UI thread. So do those two things first, and pass the rest of the 300 // work of as a blocking pool task. 301 302 std::vector<SkBitmap> bitmaps; 303 bitmaps.resize(info_list.size()); 304 305 int i = 0; 306 for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin(); 307 it != info_list.end(); ++it, ++i) { 308 DCHECK(it->resource.relative_path().empty() || 309 extension->path() == it->resource.extension_root()); 310 311 int resource_id; 312 if (extension->location() == Manifest::COMPONENT && 313 IsComponentExtensionResource(extension->path(), 314 it->resource.relative_path(), 315 &resource_id)) { 316 LoadResourceOnUIThread(resource_id, &bitmaps[i]); 317 if (bitmaps[i].isNull()) { 318 // bshe's log for http://crbug.com/314872 319 LOG(ERROR) << "Component extension icon for " << extension->name() 320 << " is null."; 321 LOG(ERROR) << "Extension icon resource id = " << resource_id 322 << "; desired_size = " << it->desired_size.ToString() 323 << "; scale_factor = " << it->scale_factor; 324 } 325 } 326 } 327 328 DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); 329 base::PostTaskAndReplyWithResult( 330 BrowserThread::GetBlockingPool(), 331 FROM_HERE, 332 base::Bind(LoadImagesOnBlockingPool, info_list, bitmaps), 333 base::Bind(&ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(), 334 callback)); 335 } 336 337 void ImageLoader::ReplyBack(const ImageLoaderCallback& callback, 338 const std::vector<LoadResult>& load_result) { 339 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 340 341 gfx::ImageSkia image_skia; 342 343 for (std::vector<LoadResult>::const_iterator it = load_result.begin(); 344 it != load_result.end(); ++it) { 345 const SkBitmap& bitmap = it->bitmap; 346 const ImageRepresentation& image_rep = it->image_representation; 347 348 image_skia.AddRepresentation(gfx::ImageSkiaRep( 349 bitmap, 350 ui::GetImageScale(image_rep.scale_factor))); 351 } 352 353 gfx::Image image; 354 if (!image_skia.isNull()) { 355 image_skia.MakeThreadSafe(); 356 image = gfx::Image(image_skia); 357 } 358 359 callback.Run(image); 360 } 361 362 } // namespace extensions 363