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