| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/image_loading_tracker.h" |
| |
| #include "base/file_util.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_resource.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/common/notification_service.h" |
| #include "content/common/notification_type.h" |
| #include "skia/ext/image_operations.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "webkit/glue/image_decoder.h" |
| |
| ImageLoadingTracker::Observer::~Observer() {} |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ImageLoadingTracker::ImageLoader |
| |
| // A RefCounted class for loading images on the File thread and reporting back |
| // on the UI thread. |
| class ImageLoadingTracker::ImageLoader |
| : public base::RefCountedThreadSafe<ImageLoader> { |
| public: |
| explicit ImageLoader(ImageLoadingTracker* tracker) |
| : tracker_(tracker) { |
| CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_)); |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| } |
| |
| // Lets this class know that the tracker is no longer interested in the |
| // results. |
| void StopTracking() { |
| tracker_ = NULL; |
| } |
| |
| // Instructs the loader to load a task on the File thread. |
| void LoadImage(const ExtensionResource& resource, |
| const gfx::Size& max_size, |
| int id) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| NewRunnableMethod(this, &ImageLoader::LoadOnFileThread, resource, |
| max_size, id)); |
| } |
| |
| void LoadOnFileThread(const ExtensionResource& resource, |
| const gfx::Size& max_size, |
| int id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| // Read the file from disk. |
| std::string file_contents; |
| FilePath path = resource.GetFilePath(); |
| if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) { |
| ReportBack(NULL, resource, gfx::Size(), id); |
| return; |
| } |
| |
| // Decode the image using WebKit's image decoder. |
| const unsigned char* data = |
| reinterpret_cast<const unsigned char*>(file_contents.data()); |
| webkit_glue::ImageDecoder decoder; |
| scoped_ptr<SkBitmap> decoded(new SkBitmap()); |
| *decoded = decoder.Decode(data, file_contents.length()); |
| if (decoded->empty()) { |
| ReportBack(NULL, resource, gfx::Size(), id); |
| return; // Unable to decode. |
| } |
| |
| gfx::Size original_size(decoded->width(), decoded->height()); |
| |
| if (decoded->width() > max_size.width() || |
| decoded->height() > max_size.height()) { |
| // The bitmap is too big, re-sample. |
| *decoded = skia::ImageOperations::Resize( |
| *decoded, skia::ImageOperations::RESIZE_LANCZOS3, |
| max_size.width(), max_size.height()); |
| } |
| |
| ReportBack(decoded.release(), resource, original_size, id); |
| } |
| |
| void ReportBack(SkBitmap* image, const ExtensionResource& resource, |
| const gfx::Size& original_size, int id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| BrowserThread::PostTask( |
| callback_thread_id_, FROM_HERE, |
| NewRunnableMethod(this, &ImageLoader::ReportOnUIThread, |
| image, resource, original_size, id)); |
| } |
| |
| void ReportOnUIThread(SkBitmap* image, const ExtensionResource& resource, |
| const gfx::Size& original_size, int id) { |
| DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE)); |
| |
| if (tracker_) |
| tracker_->OnImageLoaded(image, resource, original_size, id); |
| |
| delete image; |
| } |
| |
| private: |
| // The tracker we are loading the image for. If NULL, it means the tracker is |
| // no longer interested in the reply. |
| ImageLoadingTracker* tracker_; |
| |
| // The thread that we need to call back on to report that we are done. |
| BrowserThread::ID callback_thread_id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ImageLoader); |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ImageLoadingTracker |
| |
| ImageLoadingTracker::ImageLoadingTracker(Observer* observer) |
| : observer_(observer), |
| next_id_(0) { |
| registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, |
| NotificationService::AllSources()); |
| } |
| |
| ImageLoadingTracker::~ImageLoadingTracker() { |
| // The loader is created lazily and is NULL if the tracker is destroyed before |
| // any valid image load tasks have been posted. |
| if (loader_) |
| loader_->StopTracking(); |
| } |
| |
| void ImageLoadingTracker::LoadImage(const Extension* extension, |
| const ExtensionResource& resource, |
| const gfx::Size& max_size, |
| CacheParam cache) { |
| // If we don't have a path we don't need to do any further work, just respond |
| // back. |
| int id = next_id_++; |
| if (resource.relative_path().empty()) { |
| OnImageLoaded(NULL, resource, max_size, id); |
| return; |
| } |
| |
| DCHECK(extension->path() == resource.extension_root()); |
| |
| // See if the extension has the image already. |
| if (extension->HasCachedImage(resource, max_size)) { |
| SkBitmap image = extension->GetCachedImage(resource, max_size); |
| OnImageLoaded(&image, resource, max_size, id); |
| return; |
| } |
| |
| if (cache == CACHE) { |
| load_map_[id] = extension; |
| } |
| |
| // Instruct the ImageLoader to load this on the File thread. LoadImage does |
| // not block. |
| if (!loader_) |
| loader_ = new ImageLoader(this); |
| loader_->LoadImage(resource, max_size, id); |
| } |
| |
| void ImageLoadingTracker::OnImageLoaded( |
| SkBitmap* image, |
| const ExtensionResource& resource, |
| const gfx::Size& original_size, |
| int id) { |
| LoadMap::iterator i = load_map_.find(id); |
| if (i != load_map_.end()) { |
| i->second->SetCachedImage(resource, image ? *image : SkBitmap(), |
| original_size); |
| load_map_.erase(i); |
| } |
| |
| observer_->OnImageLoaded(image, resource, id); |
| } |
| |
| void ImageLoadingTracker::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| DCHECK(type == NotificationType::EXTENSION_UNLOADED); |
| |
| const Extension* extension = |
| Details<UnloadedExtensionInfo>(details)->extension; |
| |
| // Remove all entries in the load_map_ referencing the extension. This ensures |
| // we don't attempt to cache the image when the load completes. |
| for (LoadMap::iterator i = load_map_.begin(); i != load_map_.end();) { |
| if (i->second == extension) { |
| load_map_.erase(i++); |
| } else { |
| ++i; |
| } |
| } |
| } |