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 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 43 void DesaturateImage(SkBitmap* image) { 44 color_utils::HSL shift = {-1, 0, 0.6}; 45 *image = SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift); 46 } 47 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 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 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 83 ExtensionIconSource::~ExtensionIconSource() { 84 // Clean up all the temporary data we're holding for requests. 85 STLDeleteValues(&request_map_); 86 } 87 88 // static 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 103 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 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 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 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 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 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 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 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 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 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 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 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 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 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 307 ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData( 308 int request_id) { 309 return request_map_[request_id]; 310 } 311 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