// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/webui/extension_icon_source.h" #include "base/callback.h" #include "base/memory/ref_counted_memory.h" #include "base/stl_util-inl.h" #include "base/string_number_conversions.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/task.h" #include "base/threading/thread.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_resource.h" #include "chrome/common/url_constants.h" #include "grit/theme_resources.h" #include "googleurl/src/gurl.h" #include "skia/ext/image_operations.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/codec/png_codec.h" #include "ui/gfx/color_utils.h" #include "ui/gfx/skbitmap_operations.h" #include "webkit/glue/image_decoder.h" namespace { scoped_refptr<RefCountedMemory> BitmapToMemory(SkBitmap* image) { std::vector<unsigned char> output; gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &output); scoped_refptr<RefCountedBytes> image_bytes(new RefCountedBytes); image_bytes->data.resize(output.size()); std::copy(output.begin(), output.end(), image_bytes->data.begin()); return image_bytes; } void DesaturateImage(SkBitmap* image) { color_utils::HSL shift = {-1, 0, 0.6}; *image = SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift); } SkBitmap* ToBitmap(const unsigned char* data, size_t size) { webkit_glue::ImageDecoder decoder; SkBitmap* decoded = new SkBitmap(); *decoded = decoder.Decode(data, size); return decoded; } SkBitmap* LoadImageByResourceId(int resource_id) { std::string contents = ResourceBundle::GetSharedInstance() .GetRawDataResource(resource_id).as_string(); // Convert and return it. const unsigned char* data = reinterpret_cast<const unsigned char*>(contents.data()); return ToBitmap(data, contents.length()); } } // namespace ExtensionIconSource::ExtensionIconSource(Profile* profile) : DataSource(chrome::kChromeUIExtensionIconHost, MessageLoop::current()), profile_(profile), next_tracker_id_(0) { tracker_.reset(new ImageLoadingTracker(this)); } struct ExtensionIconSource::ExtensionIconRequest { int request_id; const Extension* extension; bool grayscale; Extension::Icons size; ExtensionIconSet::MatchType match; }; ExtensionIconSource::~ExtensionIconSource() { // Clean up all the temporary data we're holding for requests. STLDeleteValues(&request_map_); } // static GURL ExtensionIconSource::GetIconURL(const Extension* extension, Extension::Icons icon_size, ExtensionIconSet::MatchType match, bool grayscale) { GURL icon_url(base::StringPrintf("%s%s/%d/%d%s", chrome::kChromeUIExtensionIconURL, extension->id().c_str(), icon_size, match, grayscale ? "?grayscale=true" : "")); CHECK(icon_url.is_valid()); return icon_url; } std::string ExtensionIconSource::GetMimeType(const std::string&) const { // We need to explicitly return a mime type, otherwise if the user tries to // drag the image they get no extension. return "image/png"; } void ExtensionIconSource::StartDataRequest(const std::string& path, bool is_incognito, int request_id) { // This is where everything gets started. First, parse the request and make // the request data available for later. if (!ParseData(path, request_id)) { SendDefaultResponse(request_id); return; } ExtensionIconRequest* request = GetData(request_id); ExtensionResource icon = request->extension->GetIconResource(request->size, request->match); if (icon.relative_path().empty()) LoadIconFailed(request_id); else LoadExtensionImage(icon, request_id); } void ExtensionIconSource::LoadIconFailed(int request_id) { ExtensionIconRequest* request = GetData(request_id); ExtensionResource icon = request->extension->GetIconResource(request->size, request->match); if (request->size == Extension::EXTENSION_ICON_BITTY) LoadFaviconImage(request_id); else LoadDefaultImage(request_id); } SkBitmap* ExtensionIconSource::GetDefaultAppImage() { if (!default_app_data_.get()) default_app_data_.reset(LoadImageByResourceId(IDR_APP_DEFAULT_ICON)); return default_app_data_.get(); } SkBitmap* ExtensionIconSource::GetDefaultExtensionImage() { if (!default_extension_data_.get()) default_extension_data_.reset( LoadImageByResourceId(IDR_EXTENSION_DEFAULT_ICON)); return default_extension_data_.get(); } void ExtensionIconSource::FinalizeImage(SkBitmap* image, int request_id) { if (GetData(request_id)->grayscale) DesaturateImage(image); ClearData(request_id); SendResponse(request_id, BitmapToMemory(image)); } void ExtensionIconSource::LoadDefaultImage(int request_id) { ExtensionIconRequest* request = GetData(request_id); SkBitmap* decoded = NULL; if (request->extension->is_app()) decoded = GetDefaultAppImage(); else decoded = GetDefaultExtensionImage(); *decoded = skia::ImageOperations::Resize( *decoded, skia::ImageOperations::RESIZE_LANCZOS3, request->size, request->size); FinalizeImage(decoded, request_id); } void ExtensionIconSource::LoadExtensionImage(const ExtensionResource& icon, int request_id) { ExtensionIconRequest* request = GetData(request_id); tracker_map_[next_tracker_id_++] = request_id; tracker_->LoadImage(request->extension, icon, gfx::Size(request->size, request->size), ImageLoadingTracker::DONT_CACHE); } void ExtensionIconSource::LoadFaviconImage(int request_id) { FaviconService* favicon_service = profile_->GetFaviconService(Profile::EXPLICIT_ACCESS); // Fall back to the default icons if the service isn't available. if (favicon_service == NULL) { LoadDefaultImage(request_id); return; } GURL favicon_url = GetData(request_id)->extension->GetFullLaunchURL(); FaviconService::Handle handle = favicon_service->GetFaviconForURL( favicon_url, history::FAVICON, &cancelable_consumer_, NewCallback(this, &ExtensionIconSource::OnFaviconDataAvailable)); cancelable_consumer_.SetClientData(favicon_service, handle, request_id); } void ExtensionIconSource::OnFaviconDataAvailable( FaviconService::Handle request_handle, history::FaviconData favicon) { int request_id = cancelable_consumer_.GetClientData( profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), request_handle); ExtensionIconRequest* request = GetData(request_id); // Fallback to the default icon if there wasn't a favicon. if (!favicon.is_valid()) { LoadDefaultImage(request_id); return; } if (!request->grayscale) { // If we don't need a grayscale image, then we can bypass FinalizeImage // to avoid unnecessary conversions. ClearData(request_id); SendResponse(request_id, favicon.image_data); } else { FinalizeImage(ToBitmap(favicon.image_data->front(), favicon.image_data->size()), request_id); } } void ExtensionIconSource::OnImageLoaded(SkBitmap* image, const ExtensionResource& resource, int index) { int request_id = tracker_map_[index]; tracker_map_.erase(tracker_map_.find(index)); if (!image || image->empty()) LoadIconFailed(request_id); else FinalizeImage(image, request_id); } bool ExtensionIconSource::ParseData(const std::string& path, int request_id) { // Extract the parameters from the path by lower casing and splitting. std::string path_lower = StringToLowerASCII(path); std::vector<std::string> path_parts; base::SplitString(path_lower, '/', &path_parts); if (path_lower.empty() || path_parts.size() < 3) return false; std::string size_param = path_parts.at(1); std::string match_param = path_parts.at(2); match_param = match_param.substr(0, match_param.find('?')); // The icon size and match types are encoded as string representations of // their enum values, so to get the enum back, we read the string as an int // and then cast to the enum. Extension::Icons size; int size_num; if (!base::StringToInt(size_param, &size_num)) return false; size = static_cast<Extension::Icons>(size_num); ExtensionIconSet::MatchType match_type; int match_num; if (!base::StringToInt(match_param, &match_num)) return false; match_type = static_cast<ExtensionIconSet::MatchType>(match_num); std::string extension_id = path_parts.at(0); const Extension* extension = profile_->GetExtensionService()->GetExtensionById(extension_id, true); if (!extension) return false; bool grayscale = path_lower.find("grayscale=true") != std::string::npos; SetData(request_id, extension, grayscale, size, match_type); return true; } void ExtensionIconSource::SendDefaultResponse(int request_id) { // We send back the default application icon (not resized or desaturated) // as the default response, like when there is no data. ClearData(request_id); SendResponse(request_id, BitmapToMemory(GetDefaultAppImage())); } void ExtensionIconSource::SetData(int request_id, const Extension* extension, bool grayscale, Extension::Icons size, ExtensionIconSet::MatchType match) { ExtensionIconRequest* request = new ExtensionIconRequest(); request->request_id = request_id; request->extension = extension; request->grayscale = grayscale; request->size = size; request->match = match; request_map_[request_id] = request; } ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData( int request_id) { return request_map_[request_id]; } void ExtensionIconSource::ClearData(int request_id) { std::map<int, ExtensionIconRequest*>::iterator i = request_map_.find(request_id); if (i == request_map_.end()) return; delete i->second; request_map_.erase(i); }