[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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 | |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 7 | #include <map> |
| 8 | #include <vector> |
| 9 | |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 10 | #include "base/callback.h" |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 11 | #include "base/compiler_specific.h" |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 12 | #include "base/file_util.h" |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 13 | #include "base/lazy_instance.h" |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 14 | #include "base/path_service.h" |
[email protected] | 3ea1b18 | 2013-02-08 22:38:41 | [diff] [blame] | 15 | #include "base/strings/string_number_conversions.h" |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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" |
[email protected] | da87eec2 | 2013-05-14 09:25:28 | [diff] [blame] | 26 | #include "ui/gfx/codec/png_codec.h" |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 27 | #include "ui/gfx/image/image_skia.h" |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 28 | |
[email protected] | 7260022 | 2013-04-18 11:24:01 | [diff] [blame] | 29 | #if defined(USE_AURA) |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 30 | #include "ui/keyboard/keyboard_util.h" |
[email protected] | 7260022 | 2013-04-18 11:24:01 | [diff] [blame] | 31 | #endif |
| 32 | |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 33 | using content::BrowserThread; |
| 34 | using extensions::Extension; |
| 35 | using extensions::ImageLoader; |
[email protected] | 1d5e58b | 2013-01-31 08:41:40 | [diff] [blame] | 36 | using extensions::Manifest; |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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; |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 85 | base::FilePath path = image_info.resource.GetFilePath(); |
[email protected] | 82f84b9 | 2013-08-30 18:23:50 | [diff] [blame] | 86 | if (path.empty() || !base::ReadFileToString(path, &file_contents)) { |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 87 | return; |
| 88 | } |
| 89 | |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 90 | const unsigned char* data = |
| 91 | reinterpret_cast<const unsigned char*>(file_contents.data()); |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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. |
[email protected] | da87eec2 | 2013-05-14 09:25:28 | [diff] [blame] | 98 | gfx::PNGCodec::Decode(data, file_contents.length(), bitmap); |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 99 | } |
| 100 | |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 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 | |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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 | |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 168 | ImageLoader::ImageLoader() |
[email protected] | 9c00909 | 2013-05-01 03:14:09 | [diff] [blame] | 169 | : weak_ptr_factory_(this) { |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 170 | } |
| 171 | |
| 172 | ImageLoader::~ImageLoader() { |
| 173 | } |
| 174 | |
| 175 | // static |
| 176 | ImageLoader* ImageLoader::Get(Profile* profile) { |
| 177 | return ImageLoaderFactory::GetForProfile(profile); |
| 178 | } |
| 179 | |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 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 | |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 185 | // static |
[email protected] | 93ac047a | 2012-12-13 02:53:49 | [diff] [blame] | 186 | bool ImageLoader::IsComponentExtensionResource( |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 187 | const base::FilePath& extension_path, |
| 188 | const base::FilePath& resource_path, |
[email protected] | 93ac047a | 2012-12-13 02:53:49 | [diff] [blame] | 189 | int* resource_id) { |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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}, |
[email protected] | 56c43e8 | 2012-12-13 09:29:40 | [diff] [blame] | 195 | #if defined(ENABLE_SETTINGS_APP) |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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}, |
[email protected] | 56c43e8 | 2012-12-13 09:29:40 | [diff] [blame] | 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 |
[email protected] | 22041a1 | 2013-07-04 19:08:34 | [diff] [blame] | 201 | }; |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 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 | } |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 222 | |
[email protected] | 650b2d5 | 2013-02-10 03:41:45 | [diff] [blame] | 223 | base::FilePath directory_path = extension_path; |
| 224 | base::FilePath resources_dir; |
| 225 | base::FilePath relative_path; |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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 | |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 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; |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 237 | |
[email protected] | 95fd255 | 2013-07-04 21:19:24 | [diff] [blame] | 238 | return entry != path_to_resource_id.Get().end(); |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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; |
[email protected] | 1d5e58b | 2013-01-31 08:41:40 | [diff] [blame] | 275 | if (extension->location() == Manifest::COMPONENT && |
[email protected] | 93ac047a | 2012-12-13 02:53:49 | [diff] [blame] | 276 | IsComponentExtensionResource(extension->path(), |
| 277 | it->resource.relative_path(), |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 278 | &resource_id)) { |
| 279 | LoadResourceOnUIThread(resource_id, &bitmaps[i]); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 284 | std::vector<LoadResult>* load_result = new std::vector<LoadResult>; |
| 285 | BrowserThread::PostBlockingPoolTaskAndReply( |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 286 | FROM_HERE, |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 287 | base::Bind(LoadImagesOnBlockingPool, info_list, bitmaps, load_result), |
| 288 | base::Bind(&ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(), |
| 289 | base::Owned(load_result), callback)); |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 290 | } |
| 291 | |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 292 | // static |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 293 | void ImageLoader::LoadImagesOnBlockingPool( |
| 294 | const std::vector<ImageRepresentation>& info_list, |
| 295 | const std::vector<SkBitmap>& bitmaps, |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 296 | std::vector<LoadResult>* load_result) { |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 297 | DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); |
| 298 | |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 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 | |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 320 | load_result->push_back(LoadResult(bitmap, original_size, *it)); |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 321 | } |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 322 | } |
| 323 | |
| 324 | void ImageLoader::ReplyBack( |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 325 | const std::vector<LoadResult>* load_result, |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 326 | const base::Callback<void(const gfx::Image&)>& callback) { |
| 327 | DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 328 | |
| 329 | gfx::ImageSkia image_skia; |
| 330 | |
[email protected] | c8b8587b | 2012-11-21 23:23:32 | [diff] [blame] | 331 | for (std::vector<LoadResult>::const_iterator it = load_result->begin(); |
| 332 | it != load_result->end(); ++it) { |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 333 | const SkBitmap& bitmap = it->bitmap; |
| 334 | const ImageRepresentation& image_rep = it->image_representation; |
| 335 | |
| 336 | image_skia.AddRepresentation(gfx::ImageSkiaRep( |
[email protected] | 50b6626 | 2013-09-24 03:25:48 | [diff] [blame^] | 337 | bitmap, |
| 338 | ui::GetImageScale(image_rep.scale_factor))); |
[email protected] | ec7de0c5a | 2012-11-16 07:40:47 | [diff] [blame] | 339 | } |
| 340 | |
| 341 | gfx::Image image; |
| 342 | if (!image_skia.isNull()) { |
| 343 | image_skia.MakeThreadSafe(); |
| 344 | image = gfx::Image(image_skia); |
| 345 | } |
| 346 | |
| 347 | callback.Run(image); |
| 348 | } |
| 349 | |
| 350 | } // namespace extensions |