Home | History | Annotate | Download | only in extensions
      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