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/ui/webui/extension_icon_source.h"
6
7 #include "base/callback.h"
8 #include "base/memory/ref_counted_memory.h"
9 #include "base/stl_util-inl.h"
10 #include "base/string_number_conversions.h"
11 #include "base/string_split.h"
12 #include "base/string_util.h"
13 #include "base/stringprintf.h"
14 #include "base/task.h"
15 #include "base/threading/thread.h"
16 #include "chrome/browser/extensions/extension_prefs.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/common/extensions/extension.h"
20 #include "chrome/common/extensions/extension_resource.h"
21 #include "chrome/common/url_constants.h"
22 #include "grit/theme_resources.h"
23 #include "googleurl/src/gurl.h"
24 #include "skia/ext/image_operations.h"
25 #include "ui/base/resource/resource_bundle.h"
26 #include "ui/gfx/codec/png_codec.h"
27 #include "ui/gfx/color_utils.h"
28 #include "ui/gfx/skbitmap_operations.h"
29 #include "webkit/glue/image_decoder.h"
30
31 namespace {
32
BitmapToMemory(SkBitmap * image)33 scoped_refptr<RefCountedMemory> BitmapToMemory(SkBitmap* image) {
34 std::vector<unsigned char> output;
35 gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &output);
36
37 scoped_refptr<RefCountedBytes> image_bytes(new RefCountedBytes);
38 image_bytes->data.resize(output.size());
39 std::copy(output.begin(), output.end(), image_bytes->data.begin());
40 return image_bytes;
41 }
42
DesaturateImage(SkBitmap * image)43 void DesaturateImage(SkBitmap* image) {
44 color_utils::HSL shift = {-1, 0, 0.6};
45 *image = SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift);
46 }
47
ToBitmap(const unsigned char * data,size_t size)48 SkBitmap* ToBitmap(const unsigned char* data, size_t size) {
49 webkit_glue::ImageDecoder decoder;
50 SkBitmap* decoded = new SkBitmap();
51 *decoded = decoder.Decode(data, size);
52 return decoded;
53 }
54
LoadImageByResourceId(int resource_id)55 SkBitmap* LoadImageByResourceId(int resource_id) {
56 std::string contents = ResourceBundle::GetSharedInstance()
57 .GetRawDataResource(resource_id).as_string();
58
59 // Convert and return it.
60 const unsigned char* data =
61 reinterpret_cast<const unsigned char*>(contents.data());
62 return ToBitmap(data, contents.length());
63 }
64
65 } // namespace
66
67
ExtensionIconSource(Profile * profile)68 ExtensionIconSource::ExtensionIconSource(Profile* profile)
69 : DataSource(chrome::kChromeUIExtensionIconHost, MessageLoop::current()),
70 profile_(profile),
71 next_tracker_id_(0) {
72 tracker_.reset(new ImageLoadingTracker(this));
73 }
74
75 struct ExtensionIconSource::ExtensionIconRequest {
76 int request_id;
77 const Extension* extension;
78 bool grayscale;
79 Extension::Icons size;
80 ExtensionIconSet::MatchType match;
81 };
82
~ExtensionIconSource()83 ExtensionIconSource::~ExtensionIconSource() {
84 // Clean up all the temporary data we're holding for requests.
85 STLDeleteValues(&request_map_);
86 }
87
88 // static
GetIconURL(const Extension * extension,Extension::Icons icon_size,ExtensionIconSet::MatchType match,bool grayscale)89 GURL ExtensionIconSource::GetIconURL(const Extension* extension,
90 Extension::Icons icon_size,
91 ExtensionIconSet::MatchType match,
92 bool grayscale) {
93 GURL icon_url(base::StringPrintf("%s%s/%d/%d%s",
94 chrome::kChromeUIExtensionIconURL,
95 extension->id().c_str(),
96 icon_size,
97 match,
98 grayscale ? "?grayscale=true" : ""));
99 CHECK(icon_url.is_valid());
100 return icon_url;
101 }
102
GetMimeType(const std::string &) const103 std::string ExtensionIconSource::GetMimeType(const std::string&) const {
104 // We need to explicitly return a mime type, otherwise if the user tries to
105 // drag the image they get no extension.
106 return "image/png";
107 }
108
StartDataRequest(const std::string & path,bool is_incognito,int request_id)109 void ExtensionIconSource::StartDataRequest(const std::string& path,
110 bool is_incognito,
111 int request_id) {
112 // This is where everything gets started. First, parse the request and make
113 // the request data available for later.
114 if (!ParseData(path, request_id)) {
115 SendDefaultResponse(request_id);
116 return;
117 }
118
119 ExtensionIconRequest* request = GetData(request_id);
120 ExtensionResource icon =
121 request->extension->GetIconResource(request->size, request->match);
122
123 if (icon.relative_path().empty())
124 LoadIconFailed(request_id);
125 else
126 LoadExtensionImage(icon, request_id);
127 }
128
LoadIconFailed(int request_id)129 void ExtensionIconSource::LoadIconFailed(int request_id) {
130 ExtensionIconRequest* request = GetData(request_id);
131 ExtensionResource icon =
132 request->extension->GetIconResource(request->size, request->match);
133
134 if (request->size == Extension::EXTENSION_ICON_BITTY)
135 LoadFaviconImage(request_id);
136 else
137 LoadDefaultImage(request_id);
138 }
139
GetDefaultAppImage()140 SkBitmap* ExtensionIconSource::GetDefaultAppImage() {
141 if (!default_app_data_.get())
142 default_app_data_.reset(LoadImageByResourceId(IDR_APP_DEFAULT_ICON));
143
144 return default_app_data_.get();
145 }
146
GetDefaultExtensionImage()147 SkBitmap* ExtensionIconSource::GetDefaultExtensionImage() {
148 if (!default_extension_data_.get())
149 default_extension_data_.reset(
150 LoadImageByResourceId(IDR_EXTENSION_DEFAULT_ICON));
151
152 return default_extension_data_.get();
153 }
154
FinalizeImage(SkBitmap * image,int request_id)155 void ExtensionIconSource::FinalizeImage(SkBitmap* image,
156 int request_id) {
157 if (GetData(request_id)->grayscale)
158 DesaturateImage(image);
159
160 ClearData(request_id);
161 SendResponse(request_id, BitmapToMemory(image));
162 }
163
LoadDefaultImage(int request_id)164 void ExtensionIconSource::LoadDefaultImage(int request_id) {
165 ExtensionIconRequest* request = GetData(request_id);
166 SkBitmap* decoded = NULL;
167
168 if (request->extension->is_app())
169 decoded = GetDefaultAppImage();
170 else
171 decoded = GetDefaultExtensionImage();
172
173 *decoded = skia::ImageOperations::Resize(
174 *decoded, skia::ImageOperations::RESIZE_LANCZOS3,
175 request->size, request->size);
176
177 FinalizeImage(decoded, request_id);
178 }
179
LoadExtensionImage(const ExtensionResource & icon,int request_id)180 void ExtensionIconSource::LoadExtensionImage(const ExtensionResource& icon,
181 int request_id) {
182 ExtensionIconRequest* request = GetData(request_id);
183 tracker_map_[next_tracker_id_++] = request_id;
184 tracker_->LoadImage(request->extension,
185 icon,
186 gfx::Size(request->size, request->size),
187 ImageLoadingTracker::DONT_CACHE);
188 }
189
LoadFaviconImage(int request_id)190 void ExtensionIconSource::LoadFaviconImage(int request_id) {
191 FaviconService* favicon_service =
192 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS);
193 // Fall back to the default icons if the service isn't available.
194 if (favicon_service == NULL) {
195 LoadDefaultImage(request_id);
196 return;
197 }
198
199 GURL favicon_url = GetData(request_id)->extension->GetFullLaunchURL();
200 FaviconService::Handle handle = favicon_service->GetFaviconForURL(
201 favicon_url,
202 history::FAVICON,
203 &cancelable_consumer_,
204 NewCallback(this, &ExtensionIconSource::OnFaviconDataAvailable));
205 cancelable_consumer_.SetClientData(favicon_service, handle, request_id);
206 }
207
OnFaviconDataAvailable(FaviconService::Handle request_handle,history::FaviconData favicon)208 void ExtensionIconSource::OnFaviconDataAvailable(
209 FaviconService::Handle request_handle,
210 history::FaviconData favicon) {
211 int request_id = cancelable_consumer_.GetClientData(
212 profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), request_handle);
213 ExtensionIconRequest* request = GetData(request_id);
214
215 // Fallback to the default icon if there wasn't a favicon.
216 if (!favicon.is_valid()) {
217 LoadDefaultImage(request_id);
218 return;
219 }
220
221 if (!request->grayscale) {
222 // If we don't need a grayscale image, then we can bypass FinalizeImage
223 // to avoid unnecessary conversions.
224 ClearData(request_id);
225 SendResponse(request_id, favicon.image_data);
226 } else {
227 FinalizeImage(ToBitmap(favicon.image_data->front(),
228 favicon.image_data->size()), request_id);
229 }
230 }
231
OnImageLoaded(SkBitmap * image,const ExtensionResource & resource,int index)232 void ExtensionIconSource::OnImageLoaded(SkBitmap* image,
233 const ExtensionResource& resource,
234 int index) {
235 int request_id = tracker_map_[index];
236 tracker_map_.erase(tracker_map_.find(index));
237
238 if (!image || image->empty())
239 LoadIconFailed(request_id);
240 else
241 FinalizeImage(image, request_id);
242 }
243
ParseData(const std::string & path,int request_id)244 bool ExtensionIconSource::ParseData(const std::string& path,
245 int request_id) {
246 // Extract the parameters from the path by lower casing and splitting.
247 std::string path_lower = StringToLowerASCII(path);
248 std::vector<std::string> path_parts;
249
250 base::SplitString(path_lower, '/', &path_parts);
251 if (path_lower.empty() || path_parts.size() < 3)
252 return false;
253
254 std::string size_param = path_parts.at(1);
255 std::string match_param = path_parts.at(2);
256 match_param = match_param.substr(0, match_param.find('?'));
257
258 // The icon size and match types are encoded as string representations of
259 // their enum values, so to get the enum back, we read the string as an int
260 // and then cast to the enum.
261 Extension::Icons size;
262 int size_num;
263 if (!base::StringToInt(size_param, &size_num))
264 return false;
265 size = static_cast<Extension::Icons>(size_num);
266
267 ExtensionIconSet::MatchType match_type;
268 int match_num;
269 if (!base::StringToInt(match_param, &match_num))
270 return false;
271 match_type = static_cast<ExtensionIconSet::MatchType>(match_num);
272
273 std::string extension_id = path_parts.at(0);
274 const Extension* extension =
275 profile_->GetExtensionService()->GetExtensionById(extension_id, true);
276 if (!extension)
277 return false;
278
279 bool grayscale = path_lower.find("grayscale=true") != std::string::npos;
280
281 SetData(request_id, extension, grayscale, size, match_type);
282
283 return true;
284 }
285
SendDefaultResponse(int request_id)286 void ExtensionIconSource::SendDefaultResponse(int request_id) {
287 // We send back the default application icon (not resized or desaturated)
288 // as the default response, like when there is no data.
289 ClearData(request_id);
290 SendResponse(request_id, BitmapToMemory(GetDefaultAppImage()));
291 }
292
SetData(int request_id,const Extension * extension,bool grayscale,Extension::Icons size,ExtensionIconSet::MatchType match)293 void ExtensionIconSource::SetData(int request_id,
294 const Extension* extension,
295 bool grayscale,
296 Extension::Icons size,
297 ExtensionIconSet::MatchType match) {
298 ExtensionIconRequest* request = new ExtensionIconRequest();
299 request->request_id = request_id;
300 request->extension = extension;
301 request->grayscale = grayscale;
302 request->size = size;
303 request->match = match;
304 request_map_[request_id] = request;
305 }
306
GetData(int request_id)307 ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData(
308 int request_id) {
309 return request_map_[request_id];
310 }
311
ClearData(int request_id)312 void ExtensionIconSource::ClearData(int request_id) {
313 std::map<int, ExtensionIconRequest*>::iterator i =
314 request_map_.find(request_id);
315 if (i == request_map_.end())
316 return;
317
318 delete i->second;
319 request_map_.erase(i);
320 }
321