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_prefs.h"
     17 #include "chrome/browser/extensions/extension_service.h"
     18 #include "chrome/browser/extensions/extension_system.h"
     19 #include "chrome/browser/extensions/image_loader.h"
     20 #include "chrome/browser/favicon/favicon_service_factory.h"
     21 #include "chrome/browser/profiles/profile.h"
     22 #include "chrome/common/extensions/extension.h"
     23 #include "chrome/common/extensions/extension_constants.h"
     24 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
     25 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
     26 #include "chrome/common/url_constants.h"
     27 #include "extensions/common/extension_resource.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_view_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->GetRawFaviconForURL(
    223       FaviconService::FaviconForURLParams(
    224           profile_, favicon_url, chrome::FAVICON, gfx::kFaviconSize),
    225       ui::SCALE_FACTOR_100P,
    226       base::Bind(&ExtensionIconSource::OnFaviconDataAvailable,
    227                  base::Unretained(this), request_id),
    228       &cancelable_task_tracker_);
    229 }
    230 
    231 void ExtensionIconSource::OnFaviconDataAvailable(
    232     int request_id,
    233     const chrome::FaviconBitmapResult& bitmap_result) {
    234   ExtensionIconRequest* request = GetData(request_id);
    235 
    236   // Fallback to the default icon if there wasn't a favicon.
    237   if (!bitmap_result.is_valid()) {
    238     LoadDefaultImage(request_id);
    239     return;
    240   }
    241 
    242   if (!request->grayscale) {
    243     // If we don't need a grayscale image, then we can bypass FinalizeImage
    244     // to avoid unnecessary conversions.
    245     request->callback.Run(bitmap_result.bitmap_data.get());
    246     ClearData(request_id);
    247   } else {
    248     FinalizeImage(ToBitmap(bitmap_result.bitmap_data->front(),
    249                            bitmap_result.bitmap_data->size()), request_id);
    250   }
    251 }
    252 
    253 void ExtensionIconSource::OnImageLoaded(int request_id,
    254                                         const gfx::Image& image) {
    255   if (image.IsEmpty())
    256     LoadIconFailed(request_id);
    257   else
    258     FinalizeImage(image.ToSkBitmap(), request_id);
    259 }
    260 
    261 void ExtensionIconSource::LoadIconFailed(int request_id) {
    262   ExtensionIconRequest* request = GetData(request_id);
    263   ExtensionResource icon = IconsInfo::GetIconResource(
    264       request->extension, request->size, request->match);
    265 
    266   if (request->size == extension_misc::EXTENSION_ICON_BITTY)
    267     LoadFaviconImage(request_id);
    268   else
    269     LoadDefaultImage(request_id);
    270 }
    271 
    272 bool ExtensionIconSource::ParseData(
    273     const std::string& path,
    274     int request_id,
    275     const content::URLDataSource::GotDataCallback& callback) {
    276   // Extract the parameters from the path by lower casing and splitting.
    277   std::string path_lower = StringToLowerASCII(path);
    278   std::vector<std::string> path_parts;
    279 
    280   base::SplitString(path_lower, '/', &path_parts);
    281   if (path_lower.empty() || path_parts.size() < 3)
    282     return false;
    283 
    284   std::string size_param = path_parts.at(1);
    285   std::string match_param = path_parts.at(2);
    286   match_param = match_param.substr(0, match_param.find('?'));
    287 
    288   int size;
    289   if (!base::StringToInt(size_param, &size))
    290     return false;
    291   if (size <= 0 || size > extension_misc::EXTENSION_ICON_GIGANTOR)
    292     return false;
    293 
    294   ExtensionIconSet::MatchType match_type;
    295   int match_num;
    296   if (!base::StringToInt(match_param, &match_num))
    297     return false;
    298   match_type = static_cast<ExtensionIconSet::MatchType>(match_num);
    299   if (!(match_type == ExtensionIconSet::MATCH_EXACTLY ||
    300         match_type == ExtensionIconSet::MATCH_SMALLER ||
    301         match_type == ExtensionIconSet::MATCH_BIGGER))
    302     match_type = ExtensionIconSet::MATCH_EXACTLY;
    303 
    304   std::string extension_id = path_parts.at(0);
    305   const Extension* extension = ExtensionSystem::Get(profile_)->
    306       extension_service()->GetInstalledExtension(extension_id);
    307   if (!extension)
    308     return false;
    309 
    310   bool grayscale = path_lower.find("grayscale=true") != std::string::npos;
    311 
    312   SetData(request_id, callback, extension, grayscale, size, match_type);
    313 
    314   return true;
    315 }
    316 
    317 void ExtensionIconSource::SetData(
    318     int request_id,
    319     const content::URLDataSource::GotDataCallback& callback,
    320     const Extension* extension,
    321     bool grayscale,
    322     int size,
    323     ExtensionIconSet::MatchType match) {
    324   ExtensionIconRequest* request = new ExtensionIconRequest();
    325   request->callback = callback;
    326   request->extension = extension;
    327   request->grayscale = grayscale;
    328   request->size = size;
    329   request->match = match;
    330   request_map_[request_id] = request;
    331 }
    332 
    333 ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData(
    334     int request_id) {
    335   return request_map_[request_id];
    336 }
    337 
    338 void ExtensionIconSource::ClearData(int request_id) {
    339   std::map<int, ExtensionIconRequest*>::iterator i =
    340       request_map_.find(request_id);
    341   if (i == request_map_.end())
    342     return;
    343 
    344   delete i->second;
    345   request_map_.erase(i);
    346 }
    347 
    348 }  // namespace extensions
    349