| OLD | NEW |
| (Empty) |
| 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_loading_tracker.h" | |
| 6 | |
| 7 #include <string> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/file_util.h" | |
| 12 #include "base/threading/sequenced_worker_pool.h" | |
| 13 #include "chrome/browser/extensions/image_loader.h" | |
| 14 #include "chrome/browser/ui/webui/extensions/extension_icon_source.h" | |
| 15 #include "chrome/common/chrome_notification_types.h" | |
| 16 #include "chrome/common/extensions/extension.h" | |
| 17 #include "chrome/common/extensions/extension_constants.h" | |
| 18 #include "chrome/common/extensions/extension_file_util.h" | |
| 19 #include "chrome/common/extensions/extension_resource.h" | |
| 20 #include "content/public/browser/browser_thread.h" | |
| 21 #include "content/public/browser/notification_service.h" | |
| 22 #include "skia/ext/image_operations.h" | |
| 23 #include "third_party/skia/include/core/SkBitmap.h" | |
| 24 #include "ui/base/resource/resource_bundle.h" | |
| 25 #include "ui/gfx/image/image.h" | |
| 26 #include "ui/gfx/image/image_skia_rep.h" | |
| 27 #include "webkit/glue/image_decoder.h" | |
| 28 | |
| 29 using content::BrowserThread; | |
| 30 using extensions::Extension; | |
| 31 using extensions::Manifest; | |
| 32 | |
| 33 namespace { | |
| 34 | |
| 35 bool ShouldResizeImageRepresentation( | |
| 36 ImageLoadingTracker::ImageRepresentation::ResizeCondition resize_method, | |
| 37 const gfx::Size& decoded_size, | |
| 38 const gfx::Size& desired_size) { | |
| 39 switch (resize_method) { | |
| 40 case ImageLoadingTracker::ImageRepresentation::ALWAYS_RESIZE: | |
| 41 return decoded_size != desired_size; | |
| 42 case ImageLoadingTracker::ImageRepresentation::RESIZE_WHEN_LARGER: | |
| 43 return decoded_size.width() > desired_size.width() || | |
| 44 decoded_size.height() > desired_size.height(); | |
| 45 default: | |
| 46 NOTREACHED(); | |
| 47 return false; | |
| 48 } | |
| 49 } | |
| 50 | |
| 51 } // namespace | |
| 52 | |
| 53 //////////////////////////////////////////////////////////////////////////////// | |
| 54 // ImageLoadingTracker::Observer | |
| 55 | |
| 56 ImageLoadingTracker::Observer::~Observer() {} | |
| 57 | |
| 58 //////////////////////////////////////////////////////////////////////////////// | |
| 59 // ImageLoadingTracker::ImageRepresentation | |
| 60 | |
| 61 ImageLoadingTracker::ImageRepresentation::ImageRepresentation( | |
| 62 const ExtensionResource& resource, | |
| 63 ResizeCondition resize_method, | |
| 64 const gfx::Size& desired_size, | |
| 65 ui::ScaleFactor scale_factor) | |
| 66 : resource(resource), | |
| 67 resize_method(resize_method), | |
| 68 desired_size(desired_size), | |
| 69 scale_factor(scale_factor) { | |
| 70 } | |
| 71 | |
| 72 ImageLoadingTracker::ImageRepresentation::~ImageRepresentation() { | |
| 73 } | |
| 74 | |
| 75 //////////////////////////////////////////////////////////////////////////////// | |
| 76 // ImageLoadingTracker::PendingLoadInfo | |
| 77 | |
| 78 ImageLoadingTracker::PendingLoadInfo::PendingLoadInfo() | |
| 79 : extension(NULL), | |
| 80 cache(CACHE), | |
| 81 pending_count(0) { | |
| 82 } | |
| 83 | |
| 84 ImageLoadingTracker::PendingLoadInfo::~PendingLoadInfo() {} | |
| 85 | |
| 86 //////////////////////////////////////////////////////////////////////////////// | |
| 87 // ImageLoadingTracker::ImageLoader | |
| 88 | |
| 89 // A RefCounted class for loading bitmaps/image reps on the File thread and | |
| 90 // reporting back on the UI thread. | |
| 91 class ImageLoadingTracker::ImageLoader | |
| 92 : public base::RefCountedThreadSafe<ImageLoader> { | |
| 93 public: | |
| 94 explicit ImageLoader(ImageLoadingTracker* tracker) | |
| 95 : tracker_(tracker) { | |
| 96 CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_)); | |
| 97 DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 98 } | |
| 99 | |
| 100 // Lets this class know that the tracker is no longer interested in the | |
| 101 // results. | |
| 102 void StopTracking() { | |
| 103 tracker_ = NULL; | |
| 104 } | |
| 105 | |
| 106 // Instructs the loader to load a task on the blocking pool. | |
| 107 void LoadImage(const ImageRepresentation& image_info, int id) { | |
| 108 DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_)); | |
| 109 BrowserThread::PostBlockingPoolTask( | |
| 110 FROM_HERE, | |
| 111 base::Bind(&ImageLoader::LoadOnBlockingPool, this, image_info, id)); | |
| 112 } | |
| 113 | |
| 114 void LoadOnBlockingPool(const ImageRepresentation& image_info, int id) { | |
| 115 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 116 | |
| 117 // Read the file from disk. | |
| 118 std::string file_contents; | |
| 119 base::FilePath path = image_info.resource.GetFilePath(); | |
| 120 if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) { | |
| 121 ReportBack(NULL, image_info, gfx::Size(), id); | |
| 122 return; | |
| 123 } | |
| 124 | |
| 125 // Decode the bitmap using WebKit's image decoder. | |
| 126 const unsigned char* data = | |
| 127 reinterpret_cast<const unsigned char*>(file_contents.data()); | |
| 128 webkit_glue::ImageDecoder decoder; | |
| 129 scoped_ptr<SkBitmap> decoded(new SkBitmap()); | |
| 130 // Note: This class only decodes bitmaps from extension resources. Chrome | |
| 131 // doesn't (for security reasons) directly load extension resources provided | |
| 132 // by the extension author, but instead decodes them in a separate | |
| 133 // locked-down utility process. Only if the decoding succeeds is the image | |
| 134 // saved from memory to disk and subsequently used in the Chrome UI. | |
| 135 // Chrome is therefore decoding bitmaps here that were generated by Chrome. | |
| 136 *decoded = decoder.Decode(data, file_contents.length()); | |
| 137 if (decoded->empty()) { | |
| 138 ReportBack(NULL, image_info, gfx::Size(), id); | |
| 139 return; // Unable to decode. | |
| 140 } | |
| 141 | |
| 142 gfx::Size original_size(decoded->width(), decoded->height()); | |
| 143 *decoded = ResizeIfNeeded(*decoded, image_info); | |
| 144 | |
| 145 ReportBack(decoded.release(), image_info, original_size, id); | |
| 146 } | |
| 147 | |
| 148 // Instructs the loader to load a resource on the UI thread. | |
| 149 void LoadResource(const ImageRepresentation& image_info, | |
| 150 int id, | |
| 151 int resource_id) { | |
| 152 DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_)); | |
| 153 | |
| 154 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { | |
| 155 LoadResourceOnUIThread(image_info, id, resource_id); | |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 BrowserThread::PostTask( | |
| 160 BrowserThread::UI, FROM_HERE, | |
| 161 base::Bind(&ImageLoader::LoadResourceOnUIThread, this, image_info, | |
| 162 id, resource_id)); | |
| 163 } | |
| 164 | |
| 165 void LoadResourceOnUIThread(const ImageRepresentation& image_info, | |
| 166 int id, | |
| 167 int resource_id) { | |
| 168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 169 | |
| 170 // Bundled image resources is only safe to be loaded on UI thread. | |
| 171 gfx::ImageSkia* image = | |
| 172 ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id); | |
| 173 image->MakeThreadSafe(); | |
| 174 | |
| 175 BrowserThread::PostBlockingPoolTask( | |
| 176 FROM_HERE, | |
| 177 base::Bind(&ImageLoader::ResizeOnBlockingPool, this, image_info, | |
| 178 id, *image)); | |
| 179 } | |
| 180 | |
| 181 void ResizeOnBlockingPool(const ImageRepresentation& image_info, | |
| 182 int id, | |
| 183 const gfx::ImageSkia& image) { | |
| 184 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 185 // TODO(xiyuan): Clean up to use SkBitmap here and in LoadOnBlockingPool. | |
| 186 scoped_ptr<SkBitmap> bitmap(new SkBitmap); | |
| 187 gfx::Size original_size(image.width(), image.height()); | |
| 188 *bitmap = ResizeIfNeeded(*image.bitmap(), image_info); | |
| 189 ReportBack(bitmap.release(), image_info, original_size, id); | |
| 190 } | |
| 191 | |
| 192 void ReportBack(const SkBitmap* bitmap, const ImageRepresentation& image_info, | |
| 193 const gfx::Size& original_size, int id) { | |
| 194 DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); | |
| 195 | |
| 196 BrowserThread::PostTask( | |
| 197 callback_thread_id_, FROM_HERE, | |
| 198 base::Bind(&ImageLoader::ReportOnCallingThread, this, | |
| 199 bitmap, image_info, original_size, id)); | |
| 200 } | |
| 201 | |
| 202 void ReportOnCallingThread(const SkBitmap* bitmap, | |
| 203 const ImageRepresentation& image_info, | |
| 204 const gfx::Size& original_size, | |
| 205 int id) { | |
| 206 DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_)); | |
| 207 | |
| 208 if (tracker_) | |
| 209 tracker_->OnBitmapLoaded(bitmap, image_info, original_size, id, true); | |
| 210 | |
| 211 if (bitmap) | |
| 212 delete bitmap; | |
| 213 } | |
| 214 | |
| 215 private: | |
| 216 friend class base::RefCountedThreadSafe<ImageLoader>; | |
| 217 ~ImageLoader() {} | |
| 218 | |
| 219 SkBitmap ResizeIfNeeded(const SkBitmap& bitmap, | |
| 220 const ImageRepresentation& image_info) { | |
| 221 gfx::Size original_size(bitmap.width(), bitmap.height()); | |
| 222 if (ShouldResizeImageRepresentation(image_info.resize_method, | |
| 223 original_size, | |
| 224 image_info.desired_size)) { | |
| 225 return skia::ImageOperations::Resize( | |
| 226 bitmap, skia::ImageOperations::RESIZE_LANCZOS3, | |
| 227 image_info.desired_size.width(), image_info.desired_size.height()); | |
| 228 } | |
| 229 | |
| 230 return bitmap; | |
| 231 } | |
| 232 | |
| 233 // The tracker we are loading the bitmap for. If NULL, it means the tracker is | |
| 234 // no longer interested in the reply. | |
| 235 ImageLoadingTracker* tracker_; | |
| 236 | |
| 237 // The thread that we need to call back on to report that we are done. | |
| 238 BrowserThread::ID callback_thread_id_; | |
| 239 | |
| 240 DISALLOW_COPY_AND_ASSIGN(ImageLoader); | |
| 241 }; | |
| 242 | |
| 243 //////////////////////////////////////////////////////////////////////////////// | |
| 244 // ImageLoadingTracker | |
| 245 | |
| 246 ImageLoadingTracker::ImageLoadingTracker(Observer* observer) | |
| 247 : observer_(observer), | |
| 248 next_id_(0) { | |
| 249 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, | |
| 250 content::NotificationService::AllSources()); | |
| 251 } | |
| 252 | |
| 253 ImageLoadingTracker::~ImageLoadingTracker() { | |
| 254 // The loader is created lazily and is NULL if the tracker is destroyed before | |
| 255 // any valid image load tasks have been posted. | |
| 256 if (loader_) | |
| 257 loader_->StopTracking(); | |
| 258 } | |
| 259 | |
| 260 void ImageLoadingTracker::LoadImage(const Extension* extension, | |
| 261 const ExtensionResource& resource, | |
| 262 const gfx::Size& max_size, | |
| 263 CacheParam cache) { | |
| 264 std::vector<ImageRepresentation> info_list; | |
| 265 info_list.push_back(ImageRepresentation( | |
| 266 resource, | |
| 267 ImageRepresentation::RESIZE_WHEN_LARGER, | |
| 268 max_size, | |
| 269 ui::SCALE_FACTOR_100P)); | |
| 270 LoadImages(extension, info_list, cache); | |
| 271 } | |
| 272 | |
| 273 void ImageLoadingTracker::LoadImages( | |
| 274 const Extension* extension, | |
| 275 const std::vector<ImageRepresentation>& info_list, | |
| 276 CacheParam cache) { | |
| 277 PendingLoadInfo load_info; | |
| 278 load_info.extension = extension; | |
| 279 load_info.cache = cache; | |
| 280 load_info.extension_id = extension->id(); | |
| 281 load_info.pending_count = info_list.size(); | |
| 282 int id = next_id_++; | |
| 283 load_map_[id] = load_info; | |
| 284 | |
| 285 for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin(); | |
| 286 it != info_list.end(); ++it) { | |
| 287 // If we don't have a path we don't need to do any further work, just | |
| 288 // respond back. | |
| 289 if (it->resource.relative_path().empty()) { | |
| 290 OnBitmapLoaded(NULL, *it, it->desired_size, id, false); | |
| 291 continue; | |
| 292 } | |
| 293 | |
| 294 DCHECK(extension->path() == it->resource.extension_root()); | |
| 295 | |
| 296 // See if the extension has the bitmap already. | |
| 297 if (extension->HasCachedImage(it->resource, it->desired_size)) { | |
| 298 SkBitmap bitmap = extension->GetCachedImage(it->resource, | |
| 299 it->desired_size); | |
| 300 OnBitmapLoaded(&bitmap, *it, it->desired_size, id, false); | |
| 301 continue; | |
| 302 } | |
| 303 | |
| 304 // Instruct the ImageLoader to load this on the File thread. LoadImage and | |
| 305 // LoadResource do not block. | |
| 306 if (!loader_) | |
| 307 loader_ = new ImageLoader(this); | |
| 308 | |
| 309 int resource_id = -1; | |
| 310 if (extension->location() == Manifest::COMPONENT && | |
| 311 extensions::ImageLoader::IsComponentExtensionResource( | |
| 312 extension->path(), it->resource.relative_path(), &resource_id)) { | |
| 313 loader_->LoadResource(*it, id, resource_id); | |
| 314 } else { | |
| 315 loader_->LoadImage(*it, id); | |
| 316 } | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 void ImageLoadingTracker::OnBitmapLoaded( | |
| 321 const SkBitmap* bitmap, | |
| 322 const ImageRepresentation& image_info, | |
| 323 const gfx::Size& original_size, | |
| 324 int id, | |
| 325 bool should_cache) { | |
| 326 LoadMap::iterator load_map_it = load_map_.find(id); | |
| 327 DCHECK(load_map_it != load_map_.end()); | |
| 328 PendingLoadInfo* info = &load_map_it->second; | |
| 329 | |
| 330 // Save the pending results. | |
| 331 DCHECK_GT(info->pending_count, 0u); | |
| 332 info->pending_count--; | |
| 333 if (bitmap) { | |
| 334 info->image_skia.AddRepresentation(gfx::ImageSkiaRep(*bitmap, | |
| 335 image_info.scale_factor)); | |
| 336 } | |
| 337 | |
| 338 // Add to the extension's bitmap cache if requested. | |
| 339 DCHECK(info->cache != CACHE || info->extension); | |
| 340 if (should_cache && info->cache == CACHE && !image_info.resource.empty() && | |
| 341 !info->extension->HasCachedImage(image_info.resource, original_size)) { | |
| 342 info->extension->SetCachedImage(image_info.resource, | |
| 343 bitmap ? *bitmap : SkBitmap(), | |
| 344 original_size); | |
| 345 } | |
| 346 | |
| 347 // If all pending bitmaps are done then report back. | |
| 348 if (info->pending_count == 0) { | |
| 349 gfx::Image image; | |
| 350 std::string extension_id = info->extension_id; | |
| 351 | |
| 352 if (!info->image_skia.isNull()) { | |
| 353 info->image_skia.MakeThreadSafe(); | |
| 354 image = gfx::Image(info->image_skia); | |
| 355 } | |
| 356 | |
| 357 load_map_.erase(load_map_it); | |
| 358 | |
| 359 // ImageLoadingTracker might be deleted after the callback so don't do | |
| 360 // anything after this statement. | |
| 361 observer_->OnImageLoaded(image, extension_id, id); | |
| 362 } | |
| 363 } | |
| 364 | |
| 365 void ImageLoadingTracker::Observe(int type, | |
| 366 const content::NotificationSource& source, | |
| 367 const content::NotificationDetails& details) { | |
| 368 DCHECK(type == chrome::NOTIFICATION_EXTENSION_UNLOADED); | |
| 369 | |
| 370 const Extension* extension = | |
| 371 content::Details<extensions::UnloadedExtensionInfo>(details)->extension; | |
| 372 | |
| 373 // Remove reference to this extension from all pending load entries. This | |
| 374 // ensures we don't attempt to cache the bitmap when the load completes. | |
| 375 for (LoadMap::iterator i = load_map_.begin(); i != load_map_.end(); ++i) { | |
| 376 PendingLoadInfo* info = &i->second; | |
| 377 if (info->extension == extension) { | |
| 378 info->extension = NULL; | |
| 379 info->cache = DONT_CACHE; | |
| 380 } | |
| 381 } | |
| 382 } | |
| OLD | NEW |