• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "extensions/grit/extensions_browser_resources.h"
29 #include "grit/component_extension_resources_map.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 
40 namespace extensions {
41 
42 namespace {
43 
BitmapToMemory(const SkBitmap * image)44 scoped_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 
DesaturateImage(const SkBitmap * image)50 SkBitmap DesaturateImage(const SkBitmap* image) {
51   color_utils::HSL shift = {-1, 0, 0.6};
52   return SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift);
53 }
54 
ToBitmap(const unsigned char * data,size_t size)55 SkBitmap* 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 
ExtensionIconSource(Profile * profile)64 ExtensionIconSource::ExtensionIconSource(Profile* profile) : profile_(profile) {
65 }
66 
67 struct 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
GetIconURL(const Extension * extension,int icon_size,ExtensionIconSet::MatchType match,bool grayscale,bool * exists)76 GURL 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
LoadImageByResourceId(int resource_id)97 SkBitmap* 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 
GetSource() const108 std::string ExtensionIconSource::GetSource() const {
109   return chrome::kChromeUIExtensionIconHost;
110 }
111 
GetMimeType(const std::string &) const112 std::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 
StartDataRequest(const std::string & path,int render_process_id,int render_frame_id,const content::URLDataSource::GotDataCallback & callback)118 void 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.get(), 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 
~ExtensionIconSource()146 ExtensionIconSource::~ExtensionIconSource() {
147   // Clean up all the temporary data we're holding for requests.
148   STLDeleteValues(&request_map_);
149 }
150 
GetDefaultAppImage()151 const 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 
GetDefaultExtensionImage()158 const 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 
FinalizeImage(const SkBitmap * image,int request_id)167 void 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 
LoadDefaultImage(int request_id)180 void 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 
LoadExtensionImage(const ExtensionResource & icon,int request_id)202 void ExtensionIconSource::LoadExtensionImage(const ExtensionResource& icon,
203                                              int request_id) {
204   ExtensionIconRequest* request = GetData(request_id);
205   ImageLoader::Get(profile_)->LoadImageAsync(
206       request->extension.get(),
207       icon,
208       gfx::Size(request->size, request->size),
209       base::Bind(&ExtensionIconSource::OnImageLoaded, AsWeakPtr(), request_id));
210 }
211 
LoadFaviconImage(int request_id)212 void ExtensionIconSource::LoadFaviconImage(int request_id) {
213   FaviconService* favicon_service =
214       FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
215   // Fall back to the default icons if the service isn't available.
216   if (favicon_service == NULL) {
217     LoadDefaultImage(request_id);
218     return;
219   }
220 
221   GURL favicon_url =
222       AppLaunchInfo::GetFullLaunchURL(GetData(request_id)->extension.get());
223   favicon_service->GetRawFaviconForPageURL(
224       favicon_url,
225       favicon_base::FAVICON,
226       gfx::kFaviconSize,
227       base::Bind(&ExtensionIconSource::OnFaviconDataAvailable,
228                  base::Unretained(this),
229                  request_id),
230       &cancelable_task_tracker_);
231 }
232 
OnFaviconDataAvailable(int request_id,const favicon_base::FaviconRawBitmapResult & bitmap_result)233 void ExtensionIconSource::OnFaviconDataAvailable(
234     int request_id,
235     const favicon_base::FaviconRawBitmapResult& bitmap_result) {
236   ExtensionIconRequest* request = GetData(request_id);
237 
238   // Fallback to the default icon if there wasn't a favicon.
239   if (!bitmap_result.is_valid()) {
240     LoadDefaultImage(request_id);
241     return;
242   }
243 
244   if (!request->grayscale) {
245     // If we don't need a grayscale image, then we can bypass FinalizeImage
246     // to avoid unnecessary conversions.
247     request->callback.Run(bitmap_result.bitmap_data.get());
248     ClearData(request_id);
249   } else {
250     FinalizeImage(ToBitmap(bitmap_result.bitmap_data->front(),
251                            bitmap_result.bitmap_data->size()), request_id);
252   }
253 }
254 
OnImageLoaded(int request_id,const gfx::Image & image)255 void ExtensionIconSource::OnImageLoaded(int request_id,
256                                         const gfx::Image& image) {
257   if (image.IsEmpty())
258     LoadIconFailed(request_id);
259   else
260     FinalizeImage(image.ToSkBitmap(), request_id);
261 }
262 
LoadIconFailed(int request_id)263 void ExtensionIconSource::LoadIconFailed(int request_id) {
264   ExtensionIconRequest* request = GetData(request_id);
265   ExtensionResource icon = IconsInfo::GetIconResource(
266       request->extension.get(), request->size, request->match);
267 
268   if (request->size == extension_misc::EXTENSION_ICON_BITTY)
269     LoadFaviconImage(request_id);
270   else
271     LoadDefaultImage(request_id);
272 }
273 
ParseData(const std::string & path,int request_id,const content::URLDataSource::GotDataCallback & callback)274 bool ExtensionIconSource::ParseData(
275     const std::string& path,
276     int request_id,
277     const content::URLDataSource::GotDataCallback& callback) {
278   // Extract the parameters from the path by lower casing and splitting.
279   std::string path_lower = base::StringToLowerASCII(path);
280   std::vector<std::string> path_parts;
281 
282   base::SplitString(path_lower, '/', &path_parts);
283   if (path_lower.empty() || path_parts.size() < 3)
284     return false;
285 
286   std::string size_param = path_parts.at(1);
287   std::string match_param = path_parts.at(2);
288   match_param = match_param.substr(0, match_param.find('?'));
289 
290   int size;
291   if (!base::StringToInt(size_param, &size))
292     return false;
293   if (size <= 0 || size > extension_misc::EXTENSION_ICON_GIGANTOR)
294     return false;
295 
296   ExtensionIconSet::MatchType match_type;
297   int match_num;
298   if (!base::StringToInt(match_param, &match_num))
299     return false;
300   match_type = static_cast<ExtensionIconSet::MatchType>(match_num);
301   if (!(match_type == ExtensionIconSet::MATCH_EXACTLY ||
302         match_type == ExtensionIconSet::MATCH_SMALLER ||
303         match_type == ExtensionIconSet::MATCH_BIGGER))
304     match_type = ExtensionIconSet::MATCH_EXACTLY;
305 
306   std::string extension_id = path_parts.at(0);
307   const Extension* extension = ExtensionSystem::Get(profile_)->
308       extension_service()->GetInstalledExtension(extension_id);
309   if (!extension)
310     return false;
311 
312   bool grayscale = path_lower.find("grayscale=true") != std::string::npos;
313 
314   SetData(request_id, callback, extension, grayscale, size, match_type);
315 
316   return true;
317 }
318 
SetData(int request_id,const content::URLDataSource::GotDataCallback & callback,const Extension * extension,bool grayscale,int size,ExtensionIconSet::MatchType match)319 void ExtensionIconSource::SetData(
320     int request_id,
321     const content::URLDataSource::GotDataCallback& callback,
322     const Extension* extension,
323     bool grayscale,
324     int size,
325     ExtensionIconSet::MatchType match) {
326   ExtensionIconRequest* request = new ExtensionIconRequest();
327   request->callback = callback;
328   request->extension = extension;
329   request->grayscale = grayscale;
330   request->size = size;
331   request->match = match;
332   request_map_[request_id] = request;
333 }
334 
GetData(int request_id)335 ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData(
336     int request_id) {
337   return request_map_[request_id];
338 }
339 
ClearData(int request_id)340 void ExtensionIconSource::ClearData(int request_id) {
341   std::map<int, ExtensionIconRequest*>::iterator i =
342       request_map_.find(request_id);
343   if (i == request_map_.end())
344     return;
345 
346   delete i->second;
347   request_map_.erase(i);
348 }
349 
350 }  // namespace extensions
351