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.
5 #include "chrome/browser/ui/webui/extensions/extension_icon_source.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"
40 namespace extensions
{
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());
50 SkBitmap
DesaturateImage(const SkBitmap
* image
) {
51 color_utils::HSL shift
= {-1, 0, 0.6};
52 return SkBitmapOperations::CreateHSLShiftedBitmap(*image
, shift
);
55 SkBitmap
* ToBitmap(const unsigned char* data
, size_t size
) {
56 SkBitmap
* decoded
= new SkBitmap();
57 bool success
= gfx::PNGCodec::Decode(data
, size
, decoded
);
64 ExtensionIconSource::ExtensionIconSource(Profile
* profile
) : profile_(profile
) {
67 struct ExtensionIconSource::ExtensionIconRequest
{
68 content::URLDataSource::GotDataCallback callback
;
69 scoped_refptr
<const Extension
> extension
;
72 ExtensionIconSet::MatchType match
;
76 GURL
ExtensionIconSource::GetIconURL(const Extension
* extension
,
78 ExtensionIconSet::MatchType match
,
83 IconsInfo::GetIconURL(extension
, icon_size
, match
) != GURL::EmptyGURL();
86 GURL
icon_url(base::StringPrintf("%s%s/%d/%d%s",
87 chrome::kChromeUIExtensionIconURL
,
88 extension
->id().c_str(),
91 grayscale
? "?grayscale=true" : ""));
92 CHECK(icon_url
.is_valid());
97 SkBitmap
* ExtensionIconSource::LoadImageByResourceId(int resource_id
) {
98 std::string contents
= ResourceBundle::GetSharedInstance()
99 .GetRawDataResourceForScale(resource_id
,
100 ui::SCALE_FACTOR_100P
).as_string();
102 // Convert and return it.
103 const unsigned char* data
=
104 reinterpret_cast<const unsigned char*>(contents
.data());
105 return ToBitmap(data
, contents
.length());
108 std::string
ExtensionIconSource::GetSource() const {
109 return chrome::kChromeUIExtensionIconHost
;
112 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.
118 void ExtensionIconSource::StartDataRequest(
119 const std::string
& path
,
120 int render_process_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());
135 ExtensionIconRequest
* request
= GetData(next_id
);
136 ExtensionResource icon
= IconsInfo::GetIconResource(
137 request
->extension
.get(), request
->size
, request
->match
);
139 if (icon
.relative_path().empty()) {
140 LoadIconFailed(next_id
);
142 LoadExtensionImage(icon
, next_id
);
146 ExtensionIconSource::~ExtensionIconSource() {
147 // Clean up all the temporary data we're holding for requests.
148 STLDeleteValues(&request_map_
);
151 const SkBitmap
* ExtensionIconSource::GetDefaultAppImage() {
152 if (!default_app_data_
.get())
153 default_app_data_
.reset(LoadImageByResourceId(IDR_APP_DEFAULT_ICON
));
155 return default_app_data_
.get();
158 const SkBitmap
* ExtensionIconSource::GetDefaultExtensionImage() {
159 if (!default_extension_data_
.get()) {
160 default_extension_data_
.reset(
161 LoadImageByResourceId(IDR_EXTENSION_DEFAULT_ICON
));
164 return default_extension_data_
.get();
167 void ExtensionIconSource::FinalizeImage(const SkBitmap
* image
,
170 ExtensionIconRequest
* request
= GetData(request_id
);
171 if (request
->grayscale
)
172 bitmap
= DesaturateImage(image
);
176 request
->callback
.Run(BitmapToMemory(&bitmap
).get());
177 ClearData(request_id
);
180 void ExtensionIconSource::LoadDefaultImage(int request_id
) {
181 ExtensionIconRequest
* request
= GetData(request_id
);
182 const SkBitmap
* default_image
= NULL
;
184 if (request
->extension
->is_app())
185 default_image
= GetDefaultAppImage();
187 default_image
= GetDefaultExtensionImage();
189 SkBitmap
resized_image(skia::ImageOperations::Resize(
190 *default_image
, skia::ImageOperations::RESIZE_LANCZOS3
,
191 request
->size
, request
->size
));
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
;
199 FinalizeImage(&resized_image
, request_id
);
202 void ExtensionIconSource::LoadExtensionImage(const ExtensionResource
& icon
,
204 ExtensionIconRequest
* request
= GetData(request_id
);
205 ImageLoader::Get(profile_
)->LoadImageAsync(
206 request
->extension
.get(),
208 gfx::Size(request
->size
, request
->size
),
209 base::Bind(&ExtensionIconSource::OnImageLoaded
, AsWeakPtr(), 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
);
222 AppLaunchInfo::GetFullLaunchURL(GetData(request_id
)->extension
.get());
223 favicon_service
->GetRawFaviconForPageURL(
225 favicon_base::FAVICON
,
227 base::Bind(&ExtensionIconSource::OnFaviconDataAvailable
,
228 base::Unretained(this),
230 &cancelable_task_tracker_
);
233 void ExtensionIconSource::OnFaviconDataAvailable(
235 const favicon_base::FaviconRawBitmapResult
& bitmap_result
) {
236 ExtensionIconRequest
* request
= GetData(request_id
);
238 // Fallback to the default icon if there wasn't a favicon.
239 if (!bitmap_result
.is_valid()) {
240 LoadDefaultImage(request_id
);
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
);
250 FinalizeImage(ToBitmap(bitmap_result
.bitmap_data
->front(),
251 bitmap_result
.bitmap_data
->size()), request_id
);
255 void ExtensionIconSource::OnImageLoaded(int request_id
,
256 const gfx::Image
& image
) {
258 LoadIconFailed(request_id
);
260 FinalizeImage(image
.ToSkBitmap(), 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
);
268 if (request
->size
== extension_misc::EXTENSION_ICON_BITTY
)
269 LoadFaviconImage(request_id
);
271 LoadDefaultImage(request_id
);
274 bool ExtensionIconSource::ParseData(
275 const std::string
& path
,
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
;
282 base::SplitString(path_lower
, '/', &path_parts
);
283 if (path_lower
.empty() || path_parts
.size() < 3)
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('?'));
291 if (!base::StringToInt(size_param
, &size
))
293 if (size
<= 0 || size
> extension_misc::EXTENSION_ICON_GIGANTOR
)
296 ExtensionIconSet::MatchType match_type
;
298 if (!base::StringToInt(match_param
, &match_num
))
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
;
306 std::string extension_id
= path_parts
.at(0);
307 const Extension
* extension
= ExtensionSystem::Get(profile_
)->
308 extension_service()->GetInstalledExtension(extension_id
);
312 bool grayscale
= path_lower
.find("grayscale=true") != std::string::npos
;
314 SetData(request_id
, callback
, extension
, grayscale
, size
, match_type
);
319 void ExtensionIconSource::SetData(
321 const content::URLDataSource::GotDataCallback
& callback
,
322 const Extension
* extension
,
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
;
335 ExtensionIconSource::ExtensionIconRequest
* ExtensionIconSource::GetData(
337 return request_map_
[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())
347 request_map_
.erase(i
);
350 } // namespace extensions