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 40 namespace extensions { 41 42 namespace { 43 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 50 SkBitmap DesaturateImage(const SkBitmap* image) { 51 color_utils::HSL shift = {-1, 0, 0.6}; 52 return SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift); 53 } 54 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 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 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 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 108 std::string ExtensionIconSource::GetSource() const { 109 return chrome::kChromeUIExtensionIconHost; 110 } 111 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. 115 return "image/png"; 116 } 117 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, 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 146 ExtensionIconSource::~ExtensionIconSource() { 147 // Clean up all the temporary data we're holding for requests. 148 STLDeleteValues(&request_map_); 149 } 150 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 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 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 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 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, icon, 207 gfx::Size(request->size, request->size), 208 base::Bind(&ExtensionIconSource::OnImageLoaded, AsWeakPtr(), request_id)); 209 } 210 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); 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 1.0f, 226 base::Bind(&ExtensionIconSource::OnFaviconDataAvailable, 227 base::Unretained(this), 228 request_id), 229 &cancelable_task_tracker_); 230 } 231 232 void 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 254 void 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 262 void 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 273 bool 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 318 void 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 334 ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData( 335 int request_id) { 336 return request_map_[request_id]; 337 } 338 339 void 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