Home | History | Annotate | Download | only in extensions
      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 
     17 ImageLoadingTracker::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.
     24 class 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 
    121 ImageLoadingTracker::ImageLoadingTracker(Observer* observer)
    122     : observer_(observer),
    123       next_id_(0) {
    124   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
    125                  NotificationService::AllSources());
    126 }
    127 
    128 ImageLoadingTracker::~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 
    135 void 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 
    167 void 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 
    182 void 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