image_loading_tracker.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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/browser/browser_thread.h"
9#include "chrome/common/extensions/extension.h"
10#include "chrome/common/extensions/extension_resource.h"
11#include "chrome/common/notification_service.h"
12#include "chrome/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(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, 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  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED,
127                 NotificationService::AllSources());
128}
129
130ImageLoadingTracker::~ImageLoadingTracker() {
131  // The loader is created lazily and is NULL if the tracker is destroyed before
132  // any valid image load tasks have been posted.
133  if (loader_)
134    loader_->StopTracking();
135}
136
137void ImageLoadingTracker::LoadImage(Extension* extension,
138                                    const ExtensionResource& resource,
139                                    const gfx::Size& max_size,
140                                    CacheParam cache) {
141  // If we don't have a path we don't need to do any further work, just respond
142  // back.
143  int id = next_id_++;
144  if (resource.relative_path().empty()) {
145    OnImageLoaded(NULL, resource, max_size, id);
146    return;
147  }
148
149  DCHECK(extension->path() == resource.extension_root());
150
151  // See if the extension has the image already.
152  if (extension->HasCachedImage(resource, max_size)) {
153    SkBitmap image = extension->GetCachedImage(resource, max_size);
154    OnImageLoaded(&image, resource, max_size, id);
155    return;
156  }
157
158  if (cache == CACHE) {
159    load_map_[id] = extension;
160  }
161
162  // Instruct the ImageLoader to load this on the File thread. LoadImage does
163  // not block.
164  if (!loader_)
165    loader_ = new ImageLoader(this);
166  loader_->LoadImage(resource, max_size, id);
167}
168
169void ImageLoadingTracker::OnImageLoaded(
170    SkBitmap* image,
171    const ExtensionResource& resource,
172    const gfx::Size& original_size,
173    int id) {
174  LoadMap::iterator i = load_map_.find(id);
175  if (i != load_map_.end()) {
176    i->second->SetCachedImage(resource, image ? *image : SkBitmap(),
177                              original_size);
178    load_map_.erase(i);
179  }
180
181  observer_->OnImageLoaded(image, resource, id);
182}
183
184void ImageLoadingTracker::Observe(NotificationType type,
185                                  const NotificationSource& source,
186                                  const NotificationDetails& details) {
187  DCHECK(type == NotificationType::EXTENSION_UNLOADED ||
188         type == NotificationType::EXTENSION_UNLOADED_DISABLED);
189
190  Extension* extension = Details<Extension>(details).ptr();
191
192  // Remove all entries in the load_map_ referencing the extension. This ensures
193  // we don't attempt to cache the image when the load completes.
194  for (LoadMap::iterator i = load_map_.begin(); i != load_map_.end();) {
195    if (i->second == extension) {
196      load_map_.erase(i++);
197    } else {
198      ++i;
199    }
200  }
201}
202