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 "extensions/grit/extensions_browser_resources.h"
     29 #include "grit/component_extension_resources_map.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.get(), 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.get(),
    207       icon,
    208       gfx::Size(request->size, request->size),
    209       base::Bind(&ExtensionIconSource::OnImageLoaded, AsWeakPtr(), request_id));
    210 }
    211 
    212 void ExtensionIconSource::LoadFaviconImage(int request_id) {
    213   FaviconService* favicon_service =
    214       FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
    215   // Fall back to the default icons if the service isn't available.
    216   if (favicon_service == NULL) {
    217     LoadDefaultImage(request_id);
    218     return;
    219   }
    220 
    221   GURL favicon_url =
    222       AppLaunchInfo::GetFullLaunchURL(GetData(request_id)->extension.get());
    223   favicon_service->GetRawFaviconForPageURL(
    224       favicon_url,
    225       favicon_base::FAVICON,
    226       gfx::kFaviconSize,
    227       base::Bind(&ExtensionIconSource::OnFaviconDataAvailable,
    228                  base::Unretained(this),
    229                  request_id),
    230       &cancelable_task_tracker_);
    231 }
    232 
    233 void ExtensionIconSource::OnFaviconDataAvailable(
    234     int request_id,
    235     const favicon_base::FaviconRawBitmapResult& bitmap_result) {
    236   ExtensionIconRequest* request = GetData(request_id);
    237 
    238   // Fallback to the default icon if there wasn't a favicon.
    239   if (!bitmap_result.is_valid()) {
    240     LoadDefaultImage(request_id);
    241     return;
    242   }
    243 
    244   if (!request->grayscale) {
    245     // If we don't need a grayscale image, then we can bypass FinalizeImage
    246     // to avoid unnecessary conversions.
    247     request->callback.Run(bitmap_result.bitmap_data.get());
    248     ClearData(request_id);
    249   } else {
    250     FinalizeImage(ToBitmap(bitmap_result.bitmap_data->front(),
    251                            bitmap_result.bitmap_data->size()), request_id);
    252   }
    253 }
    254 
    255 void ExtensionIconSource::OnImageLoaded(int request_id,
    256                                         const gfx::Image& image) {
    257   if (image.IsEmpty())
    258     LoadIconFailed(request_id);
    259   else
    260     FinalizeImage(image.ToSkBitmap(), request_id);
    261 }
    262 
    263 void ExtensionIconSource::LoadIconFailed(int request_id) {
    264   ExtensionIconRequest* request = GetData(request_id);
    265   ExtensionResource icon = IconsInfo::GetIconResource(
    266       request->extension.get(), request->size, request->match);
    267 
    268   if (request->size == extension_misc::EXTENSION_ICON_BITTY)
    269     LoadFaviconImage(request_id);
    270   else
    271     LoadDefaultImage(request_id);
    272 }
    273 
    274 bool ExtensionIconSource::ParseData(
    275     const std::string& path,
    276     int request_id,
    277     const content::URLDataSource::GotDataCallback& callback) {
    278   // Extract the parameters from the path by lower casing and splitting.
    279   std::string path_lower = base::StringToLowerASCII(path);
    280   std::vector<std::string> path_parts;
    281 
    282   base::SplitString(path_lower, '/', &path_parts);
    283   if (path_lower.empty() || path_parts.size() < 3)
    284     return false;
    285 
    286   std::string size_param = path_parts.at(1);
    287   std::string match_param = path_parts.at(2);
    288   match_param = match_param.substr(0, match_param.find('?'));
    289 
    290   int size;
    291   if (!base::StringToInt(size_param, &size))
    292     return false;
    293   if (size <= 0 || size > extension_misc::EXTENSION_ICON_GIGANTOR)
    294     return false;
    295 
    296   ExtensionIconSet::MatchType match_type;
    297   int match_num;
    298   if (!base::StringToInt(match_param, &match_num))
    299     return false;
    300   match_type = static_cast<ExtensionIconSet::MatchType>(match_num);
    301   if (!(match_type == ExtensionIconSet::MATCH_EXACTLY ||
    302         match_type == ExtensionIconSet::MATCH_SMALLER ||
    303         match_type == ExtensionIconSet::MATCH_BIGGER))
    304     match_type = ExtensionIconSet::MATCH_EXACTLY;
    305 
    306   std::string extension_id = path_parts.at(0);
    307   const Extension* extension = ExtensionSystem::Get(profile_)->
    308       extension_service()->GetInstalledExtension(extension_id);
    309   if (!extension)
    310     return false;
    311 
    312   bool grayscale = path_lower.find("grayscale=true") != std::string::npos;
    313 
    314   SetData(request_id, callback, extension, grayscale, size, match_type);
    315 
    316   return true;
    317 }
    318 
    319 void ExtensionIconSource::SetData(
    320     int request_id,
    321     const content::URLDataSource::GotDataCallback& callback,
    322     const Extension* extension,
    323     bool grayscale,
    324     int size,
    325     ExtensionIconSet::MatchType match) {
    326   ExtensionIconRequest* request = new ExtensionIconRequest();
    327   request->callback = callback;
    328   request->extension = extension;
    329   request->grayscale = grayscale;
    330   request->size = size;
    331   request->match = match;
    332   request_map_[request_id] = request;
    333 }
    334 
    335 ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData(
    336     int request_id) {
    337   return request_map_[request_id];
    338 }
    339 
    340 void ExtensionIconSource::ClearData(int request_id) {
    341   std::map<int, ExtensionIconRequest*>::iterator i =
    342       request_map_.find(request_id);
    343   if (i == request_map_.end())
    344     return;
    345 
    346   delete i->second;
    347   request_map_.erase(i);
    348 }
    349 
    350 }  // namespace extensions
    351