1// Copyright (c) 2011 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 "base/file_util.h" 8#include "chrome/common/extensions/extension.h" 9#include "chrome/common/extensions/extension_resource.h" 10#include "content/browser/browser_thread.h" 11#include "content/common/notification_service.h" 12#include "content/common/notification_type.h" 13#include "skia/ext/image_operations.h" 14#include "third_party/skia/include/core/SkBitmap.h" 15#include "webkit/glue/image_decoder.h" 16 17ImageLoadingTracker::Observer::~Observer() {} 18 19//////////////////////////////////////////////////////////////////////////////// 20// ImageLoadingTracker::ImageLoader 21 22// A RefCounted class for loading images on the File thread and reporting back 23// on the UI thread. 24class ImageLoadingTracker::ImageLoader 25 : public base::RefCountedThreadSafe<ImageLoader> { 26 public: 27 explicit ImageLoader(ImageLoadingTracker* tracker) 28 : tracker_(tracker) { 29 CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_)); 30 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 31 } 32 33 // Lets this class know that the tracker is no longer interested in the 34 // results. 35 void StopTracking() { 36 tracker_ = NULL; 37 } 38 39 // Instructs the loader to load a task on the File thread. 40 void LoadImage(const ExtensionResource& resource, 41 const gfx::Size& max_size, 42 int id) { 43 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 44 BrowserThread::PostTask( 45 BrowserThread::FILE, FROM_HERE, 46 NewRunnableMethod(this, &ImageLoader::LoadOnFileThread, resource, 47 max_size, id)); 48 } 49 50 void LoadOnFileThread(const ExtensionResource& resource, 51 const gfx::Size& max_size, 52 int id) { 53 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 54 55 // Read the file from disk. 56 std::string file_contents; 57 FilePath path = resource.GetFilePath(); 58 if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) { 59 ReportBack(NULL, resource, gfx::Size(), id); 60 return; 61 } 62 63 // Decode the image using WebKit's image decoder. 64 const unsigned char* data = 65 reinterpret_cast<const unsigned char*>(file_contents.data()); 66 webkit_glue::ImageDecoder decoder; 67 scoped_ptr<SkBitmap> decoded(new SkBitmap()); 68 *decoded = decoder.Decode(data, file_contents.length()); 69 if (decoded->empty()) { 70 ReportBack(NULL, resource, gfx::Size(), id); 71 return; // Unable to decode. 72 } 73 74 gfx::Size original_size(decoded->width(), decoded->height()); 75 76 if (decoded->width() > max_size.width() || 77 decoded->height() > max_size.height()) { 78 // The bitmap is too big, re-sample. 79 *decoded = skia::ImageOperations::Resize( 80 *decoded, skia::ImageOperations::RESIZE_LANCZOS3, 81 max_size.width(), max_size.height()); 82 } 83 84 ReportBack(decoded.release(), resource, original_size, id); 85 } 86 87 void ReportBack(SkBitmap* image, const ExtensionResource& resource, 88 const gfx::Size& original_size, int id) { 89 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 90 91 BrowserThread::PostTask( 92 callback_thread_id_, FROM_HERE, 93 NewRunnableMethod(this, &ImageLoader::ReportOnUIThread, 94 image, resource, original_size, id)); 95 } 96 97 void ReportOnUIThread(SkBitmap* image, const ExtensionResource& resource, 98 const gfx::Size& original_size, int id) { 99 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); 100 101 if (tracker_) 102 tracker_->OnImageLoaded(image, resource, original_size, id); 103 104 delete image; 105 } 106 107 private: 108 // The tracker we are loading the image for. If NULL, it means the tracker is 109 // no longer interested in the reply. 110 ImageLoadingTracker* tracker_; 111 112 // The thread that we need to call back on to report that we are done. 113 BrowserThread::ID callback_thread_id_; 114 115 DISALLOW_COPY_AND_ASSIGN(ImageLoader); 116}; 117 118//////////////////////////////////////////////////////////////////////////////// 119// ImageLoadingTracker 120 121ImageLoadingTracker::ImageLoadingTracker(Observer* observer) 122 : observer_(observer), 123 next_id_(0) { 124 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, 125 NotificationService::AllSources()); 126} 127 128ImageLoadingTracker::~ImageLoadingTracker() { 129 // The loader is created lazily and is NULL if the tracker is destroyed before 130 // any valid image load tasks have been posted. 131 if (loader_) 132 loader_->StopTracking(); 133} 134 135void ImageLoadingTracker::LoadImage(const Extension* extension, 136 const ExtensionResource& resource, 137 const gfx::Size& max_size, 138 CacheParam cache) { 139 // If we don't have a path we don't need to do any further work, just respond 140 // back. 141 int id = next_id_++; 142 if (resource.relative_path().empty()) { 143 OnImageLoaded(NULL, resource, max_size, id); 144 return; 145 } 146 147 DCHECK(extension->path() == resource.extension_root()); 148 149 // See if the extension has the image already. 150 if (extension->HasCachedImage(resource, max_size)) { 151 SkBitmap image = extension->GetCachedImage(resource, max_size); 152 OnImageLoaded(&image, resource, max_size, id); 153 return; 154 } 155 156 if (cache == CACHE) { 157 load_map_[id] = extension; 158 } 159 160 // Instruct the ImageLoader to load this on the File thread. LoadImage does 161 // not block. 162 if (!loader_) 163 loader_ = new ImageLoader(this); 164 loader_->LoadImage(resource, max_size, id); 165} 166 167void ImageLoadingTracker::OnImageLoaded( 168 SkBitmap* image, 169 const ExtensionResource& resource, 170 const gfx::Size& original_size, 171 int id) { 172 LoadMap::iterator i = load_map_.find(id); 173 if (i != load_map_.end()) { 174 i->second->SetCachedImage(resource, image ? *image : SkBitmap(), 175 original_size); 176 load_map_.erase(i); 177 } 178 179 observer_->OnImageLoaded(image, resource, id); 180} 181 182void ImageLoadingTracker::Observe(NotificationType type, 183 const NotificationSource& source, 184 const NotificationDetails& details) { 185 DCHECK(type == NotificationType::EXTENSION_UNLOADED); 186 187 const Extension* extension = 188 Details<UnloadedExtensionInfo>(details)->extension; 189 190 // Remove all entries in the load_map_ referencing the extension. This ensures 191 // we don't attempt to cache the image when the load completes. 192 for (LoadMap::iterator i = load_map_.begin(); i != load_map_.end();) { 193 if (i->second == extension) { 194 load_map_.erase(i++); 195 } else { 196 ++i; 197 } 198 } 199} 200