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_prefs.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/extensions/extension_system.h"
19 #include "chrome/browser/extensions/image_loader.h"
20 #include "chrome/browser/favicon/favicon_service_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/common/extensions/extension_constants.h"
23 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
24 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
25 #include "chrome/common/url_constants.h"
26 #include "extensions/common/extension.h"
27 #include "extensions/common/extension_resource.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
, 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
, icon
,
207 gfx::Size(request
->size
, request
->size
),
208 base::Bind(&ExtensionIconSource::OnImageLoaded
, AsWeakPtr(), request_id
));
211 void 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
);
221 AppLaunchInfo::GetFullLaunchURL(GetData(request_id
)->extension
);
222 favicon_service
->GetRawFaviconForURL(
223 FaviconService::FaviconForURLParams(favicon_url
, chrome::FAVICON
,
225 ui::SCALE_FACTOR_100P
,
226 base::Bind(&ExtensionIconSource::OnFaviconDataAvailable
,
227 base::Unretained(this), request_id
),
228 &cancelable_task_tracker_
);
231 void ExtensionIconSource::OnFaviconDataAvailable(
233 const chrome::FaviconBitmapResult
& bitmap_result
) {
234 ExtensionIconRequest
* request
= GetData(request_id
);
236 // Fallback to the default icon if there wasn't a favicon.
237 if (!bitmap_result
.is_valid()) {
238 LoadDefaultImage(request_id
);
242 if (!request
->grayscale
) {
243 // If we don't need a grayscale image, then we can bypass FinalizeImage
244 // to avoid unnecessary conversions.
245 request
->callback
.Run(bitmap_result
.bitmap_data
.get());
246 ClearData(request_id
);
248 FinalizeImage(ToBitmap(bitmap_result
.bitmap_data
->front(),
249 bitmap_result
.bitmap_data
->size()), request_id
);
253 void ExtensionIconSource::OnImageLoaded(int request_id
,
254 const gfx::Image
& image
) {
256 LoadIconFailed(request_id
);
258 FinalizeImage(image
.ToSkBitmap(), request_id
);
261 void ExtensionIconSource::LoadIconFailed(int request_id
) {
262 ExtensionIconRequest
* request
= GetData(request_id
);
263 ExtensionResource icon
= IconsInfo::GetIconResource(
264 request
->extension
, request
->size
, request
->match
);
266 if (request
->size
== extension_misc::EXTENSION_ICON_BITTY
)
267 LoadFaviconImage(request_id
);
269 LoadDefaultImage(request_id
);
272 bool ExtensionIconSource::ParseData(
273 const std::string
& path
,
275 const content::URLDataSource::GotDataCallback
& callback
) {
276 // Extract the parameters from the path by lower casing and splitting.
277 std::string path_lower
= StringToLowerASCII(path
);
278 std::vector
<std::string
> path_parts
;
280 base::SplitString(path_lower
, '/', &path_parts
);
281 if (path_lower
.empty() || path_parts
.size() < 3)
284 std::string size_param
= path_parts
.at(1);
285 std::string match_param
= path_parts
.at(2);
286 match_param
= match_param
.substr(0, match_param
.find('?'));
289 if (!base::StringToInt(size_param
, &size
))
291 if (size
<= 0 || size
> extension_misc::EXTENSION_ICON_GIGANTOR
)
294 ExtensionIconSet::MatchType match_type
;
296 if (!base::StringToInt(match_param
, &match_num
))
298 match_type
= static_cast<ExtensionIconSet::MatchType
>(match_num
);
299 if (!(match_type
== ExtensionIconSet::MATCH_EXACTLY
||
300 match_type
== ExtensionIconSet::MATCH_SMALLER
||
301 match_type
== ExtensionIconSet::MATCH_BIGGER
))
302 match_type
= ExtensionIconSet::MATCH_EXACTLY
;
304 std::string extension_id
= path_parts
.at(0);
305 const Extension
* extension
= ExtensionSystem::Get(profile_
)->
306 extension_service()->GetInstalledExtension(extension_id
);
310 bool grayscale
= path_lower
.find("grayscale=true") != std::string::npos
;
312 SetData(request_id
, callback
, extension
, grayscale
, size
, match_type
);
317 void ExtensionIconSource::SetData(
319 const content::URLDataSource::GotDataCallback
& callback
,
320 const Extension
* extension
,
323 ExtensionIconSet::MatchType match
) {
324 ExtensionIconRequest
* request
= new ExtensionIconRequest();
325 request
->callback
= callback
;
326 request
->extension
= extension
;
327 request
->grayscale
= grayscale
;
328 request
->size
= size
;
329 request
->match
= match
;
330 request_map_
[request_id
] = request
;
333 ExtensionIconSource::ExtensionIconRequest
* ExtensionIconSource::GetData(
335 return request_map_
[request_id
];
338 void ExtensionIconSource::ClearData(int request_id
) {
339 std::map
<int, ExtensionIconRequest
*>::iterator i
=
340 request_map_
.find(request_id
);
341 if (i
== request_map_
.end())
345 request_map_
.erase(i
);
348 } // namespace extensions