extension_icon_source.cc revision 6d86b77056ed63eb6871182f42a9fd5f07550f90
1// Copyright (c) 2012 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/ui/webui/extensions/extension_icon_source.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/memory/ref_counted_memory.h"
10#include "base/stl_util.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/string_split.h"
13#include "base/strings/string_util.h"
14#include "base/strings/stringprintf.h"
15#include "base/threading/thread.h"
16#include "chrome/browser/extensions/extension_service.h"
17#include "chrome/browser/favicon/favicon_service_factory.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/common/extensions/extension_constants.h"
20#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
21#include "chrome/common/url_constants.h"
22#include "extensions/browser/extension_prefs.h"
23#include "extensions/browser/extension_system.h"
24#include "extensions/browser/image_loader.h"
25#include "extensions/common/extension.h"
26#include "extensions/common/extension_resource.h"
27#include "extensions/common/manifest_handlers/icons_handler.h"
28#include "grit/component_extension_resources_map.h"
29#include "grit/theme_resources.h"
30#include "skia/ext/image_operations.h"
31#include "ui/base/layout.h"
32#include "ui/base/resource/resource_bundle.h"
33#include "ui/gfx/codec/png_codec.h"
34#include "ui/gfx/color_utils.h"
35#include "ui/gfx/favicon_size.h"
36#include "ui/gfx/size.h"
37#include "ui/gfx/skbitmap_operations.h"
38#include "url/gurl.h"
39
40namespace extensions {
41
42namespace {
43
44scoped_refptr<base::RefCountedMemory> BitmapToMemory(const SkBitmap* image) {
45  base::RefCountedBytes* image_bytes = new base::RefCountedBytes;
46  gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_bytes->data());
47  return image_bytes;
48}
49
50SkBitmap DesaturateImage(const SkBitmap* image) {
51  color_utils::HSL shift = {-1, 0, 0.6};
52  return SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift);
53}
54
55SkBitmap* ToBitmap(const unsigned char* data, size_t size) {
56  SkBitmap* decoded = new SkBitmap();
57  bool success = gfx::PNGCodec::Decode(data, size, decoded);
58  DCHECK(success);
59  return decoded;
60}
61
62}  // namespace
63
64ExtensionIconSource::ExtensionIconSource(Profile* profile) : profile_(profile) {
65}
66
67struct ExtensionIconSource::ExtensionIconRequest {
68  content::URLDataSource::GotDataCallback callback;
69  scoped_refptr<const Extension> extension;
70  bool grayscale;
71  int size;
72  ExtensionIconSet::MatchType match;
73};
74
75// static
76GURL ExtensionIconSource::GetIconURL(const Extension* extension,
77                                     int icon_size,
78                                     ExtensionIconSet::MatchType match,
79                                     bool grayscale,
80                                     bool* exists) {
81  if (exists) {
82    *exists =
83        IconsInfo::GetIconURL(extension, icon_size, match) != GURL::EmptyGURL();
84  }
85
86  GURL icon_url(base::StringPrintf("%s%s/%d/%d%s",
87                                   chrome::kChromeUIExtensionIconURL,
88                                   extension->id().c_str(),
89                                   icon_size,
90                                   match,
91                                   grayscale ? "?grayscale=true" : ""));
92  CHECK(icon_url.is_valid());
93  return icon_url;
94}
95
96// static
97SkBitmap* ExtensionIconSource::LoadImageByResourceId(int resource_id) {
98  std::string contents = ResourceBundle::GetSharedInstance()
99      .GetRawDataResourceForScale(resource_id,
100                                  ui::SCALE_FACTOR_100P).as_string();
101
102  // Convert and return it.
103  const unsigned char* data =
104      reinterpret_cast<const unsigned char*>(contents.data());
105  return ToBitmap(data, contents.length());
106}
107
108std::string ExtensionIconSource::GetSource() const {
109  return chrome::kChromeUIExtensionIconHost;
110}
111
112std::string ExtensionIconSource::GetMimeType(const std::string&) const {
113  // We need to explicitly return a mime type, otherwise if the user tries to
114  // drag the image they get no extension.
115  return "image/png";
116}
117
118void ExtensionIconSource::StartDataRequest(
119    const std::string& path,
120    int render_process_id,
121    int render_frame_id,
122    const content::URLDataSource::GotDataCallback& callback) {
123  // This is where everything gets started. First, parse the request and make
124  // the request data available for later.
125  static int next_id = 0;
126  if (!ParseData(path, ++next_id, callback)) {
127    // If the request data cannot be parsed, request parameters will not be
128    // added to |request_map_|.
129    // Send back the default application icon (not resized or desaturated) as
130    // the default response.
131    callback.Run(BitmapToMemory(GetDefaultAppImage()).get());
132    return;
133  }
134
135  ExtensionIconRequest* request = GetData(next_id);
136  ExtensionResource icon = IconsInfo::GetIconResource(
137      request->extension, request->size, request->match);
138
139  if (icon.relative_path().empty()) {
140    LoadIconFailed(next_id);
141  } else {
142    LoadExtensionImage(icon, next_id);
143  }
144}
145
146ExtensionIconSource::~ExtensionIconSource() {
147  // Clean up all the temporary data we're holding for requests.
148  STLDeleteValues(&request_map_);
149}
150
151const SkBitmap* ExtensionIconSource::GetDefaultAppImage() {
152  if (!default_app_data_.get())
153    default_app_data_.reset(LoadImageByResourceId(IDR_APP_DEFAULT_ICON));
154
155  return default_app_data_.get();
156}
157
158const SkBitmap* ExtensionIconSource::GetDefaultExtensionImage() {
159  if (!default_extension_data_.get()) {
160    default_extension_data_.reset(
161        LoadImageByResourceId(IDR_EXTENSION_DEFAULT_ICON));
162  }
163
164  return default_extension_data_.get();
165}
166
167void ExtensionIconSource::FinalizeImage(const SkBitmap* image,
168                                        int request_id) {
169  SkBitmap bitmap;
170  ExtensionIconRequest* request = GetData(request_id);
171  if (request->grayscale)
172    bitmap = DesaturateImage(image);
173  else
174    bitmap = *image;
175
176  request->callback.Run(BitmapToMemory(&bitmap).get());
177  ClearData(request_id);
178}
179
180void ExtensionIconSource::LoadDefaultImage(int request_id) {
181  ExtensionIconRequest* request = GetData(request_id);
182  const SkBitmap* default_image = NULL;
183
184  if (request->extension->is_app())
185    default_image = GetDefaultAppImage();
186  else
187    default_image = GetDefaultExtensionImage();
188
189  SkBitmap resized_image(skia::ImageOperations::Resize(
190      *default_image, skia::ImageOperations::RESIZE_LANCZOS3,
191      request->size, request->size));
192
193  // There are cases where Resize returns an empty bitmap, for example if you
194  // ask for an image too large. In this case it is better to return the default
195  // image than returning nothing at all.
196  if (resized_image.empty())
197    resized_image = *default_image;
198
199  FinalizeImage(&resized_image, request_id);
200}
201
202void ExtensionIconSource::LoadExtensionImage(const ExtensionResource& icon,
203                                             int request_id) {
204  ExtensionIconRequest* request = GetData(request_id);
205  ImageLoader::Get(profile_)->LoadImageAsync(
206      request->extension, icon,
207      gfx::Size(request->size, request->size),
208      base::Bind(&ExtensionIconSource::OnImageLoaded, AsWeakPtr(), request_id));
209}
210
211void ExtensionIconSource::LoadFaviconImage(int request_id) {
212  FaviconService* favicon_service =
213      FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
214  // Fall back to the default icons if the service isn't available.
215  if (favicon_service == NULL) {
216    LoadDefaultImage(request_id);
217    return;
218  }
219
220  GURL favicon_url =
221      AppLaunchInfo::GetFullLaunchURL(GetData(request_id)->extension);
222  favicon_service->GetRawFaviconForPageURL(
223      FaviconService::FaviconForPageURLParams(
224          favicon_url, favicon_base::FAVICON, gfx::kFaviconSize),
225      ui::SCALE_FACTOR_100P,
226      base::Bind(&ExtensionIconSource::OnFaviconDataAvailable,
227                 base::Unretained(this),
228                 request_id),
229      &cancelable_task_tracker_);
230}
231
232void ExtensionIconSource::OnFaviconDataAvailable(
233    int request_id,
234    const favicon_base::FaviconRawBitmapResult& bitmap_result) {
235  ExtensionIconRequest* request = GetData(request_id);
236
237  // Fallback to the default icon if there wasn't a favicon.
238  if (!bitmap_result.is_valid()) {
239    LoadDefaultImage(request_id);
240    return;
241  }
242
243  if (!request->grayscale) {
244    // If we don't need a grayscale image, then we can bypass FinalizeImage
245    // to avoid unnecessary conversions.
246    request->callback.Run(bitmap_result.bitmap_data.get());
247    ClearData(request_id);
248  } else {
249    FinalizeImage(ToBitmap(bitmap_result.bitmap_data->front(),
250                           bitmap_result.bitmap_data->size()), request_id);
251  }
252}
253
254void ExtensionIconSource::OnImageLoaded(int request_id,
255                                        const gfx::Image& image) {
256  if (image.IsEmpty())
257    LoadIconFailed(request_id);
258  else
259    FinalizeImage(image.ToSkBitmap(), request_id);
260}
261
262void ExtensionIconSource::LoadIconFailed(int request_id) {
263  ExtensionIconRequest* request = GetData(request_id);
264  ExtensionResource icon = IconsInfo::GetIconResource(
265      request->extension, request->size, request->match);
266
267  if (request->size == extension_misc::EXTENSION_ICON_BITTY)
268    LoadFaviconImage(request_id);
269  else
270    LoadDefaultImage(request_id);
271}
272
273bool ExtensionIconSource::ParseData(
274    const std::string& path,
275    int request_id,
276    const content::URLDataSource::GotDataCallback& callback) {
277  // Extract the parameters from the path by lower casing and splitting.
278  std::string path_lower = StringToLowerASCII(path);
279  std::vector<std::string> path_parts;
280
281  base::SplitString(path_lower, '/', &path_parts);
282  if (path_lower.empty() || path_parts.size() < 3)
283    return false;
284
285  std::string size_param = path_parts.at(1);
286  std::string match_param = path_parts.at(2);
287  match_param = match_param.substr(0, match_param.find('?'));
288
289  int size;
290  if (!base::StringToInt(size_param, &size))
291    return false;
292  if (size <= 0 || size > extension_misc::EXTENSION_ICON_GIGANTOR)
293    return false;
294
295  ExtensionIconSet::MatchType match_type;
296  int match_num;
297  if (!base::StringToInt(match_param, &match_num))
298    return false;
299  match_type = static_cast<ExtensionIconSet::MatchType>(match_num);
300  if (!(match_type == ExtensionIconSet::MATCH_EXACTLY ||
301        match_type == ExtensionIconSet::MATCH_SMALLER ||
302        match_type == ExtensionIconSet::MATCH_BIGGER))
303    match_type = ExtensionIconSet::MATCH_EXACTLY;
304
305  std::string extension_id = path_parts.at(0);
306  const Extension* extension = ExtensionSystem::Get(profile_)->
307      extension_service()->GetInstalledExtension(extension_id);
308  if (!extension)
309    return false;
310
311  bool grayscale = path_lower.find("grayscale=true") != std::string::npos;
312
313  SetData(request_id, callback, extension, grayscale, size, match_type);
314
315  return true;
316}
317
318void ExtensionIconSource::SetData(
319    int request_id,
320    const content::URLDataSource::GotDataCallback& callback,
321    const Extension* extension,
322    bool grayscale,
323    int size,
324    ExtensionIconSet::MatchType match) {
325  ExtensionIconRequest* request = new ExtensionIconRequest();
326  request->callback = callback;
327  request->extension = extension;
328  request->grayscale = grayscale;
329  request->size = size;
330  request->match = match;
331  request_map_[request_id] = request;
332}
333
334ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData(
335    int request_id) {
336  return request_map_[request_id];
337}
338
339void ExtensionIconSource::ClearData(int request_id) {
340  std::map<int, ExtensionIconRequest*>::iterator i =
341      request_map_.find(request_id);
342  if (i == request_map_.end())
343    return;
344
345  delete i->second;
346  request_map_.erase(i);
347}
348
349}  // namespace extensions
350