summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/image_loading_tracker.cc
blob: 496c3ad03b488eb73bb306fa67a1c03c5f58c0f1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Copyright (c) 2010 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/browser/browser_thread.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_resource.h"
#include "chrome/common/notification_service.h"
#include "chrome/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(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, 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());
  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED,
                 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 ||
         type == NotificationType::EXTENSION_UNLOADED_DISABLED);

  const Extension* extension = Details<const Extension>(details).ptr();

  // 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;
    }
  }
}