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/extensions/image_loader.h"
      6 
      7 #include <map>
      8 #include <vector>
      9 
     10 #include "base/callback.h"
     11 #include "base/compiler_specific.h"
     12 #include "base/file_util.h"
     13 #include "base/lazy_instance.h"
     14 #include "base/path_service.h"
     15 #include "base/strings/string_number_conversions.h"
     16 #include "base/threading/sequenced_worker_pool.h"
     17 #include "chrome/browser/extensions/image_loader_factory.h"
     18 #include "chrome/common/chrome_paths.h"
     19 #include "chrome/common/extensions/extension.h"
     20 #include "content/public/browser/browser_thread.h"
     21 #include "grit/chrome_unscaled_resources.h"
     22 #include "grit/component_extension_resources_map.h"
     23 #include "grit/theme_resources.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/image/image_skia.h"
     28 
     29 #if defined(USE_AURA)
     30 #include "ui/keyboard/keyboard_util.h"
     31 #endif
     32 
     33 using content::BrowserThread;
     34 using extensions::Extension;
     35 using extensions::ImageLoader;
     36 using extensions::Manifest;
     37 
     38 namespace {
     39 
     40 bool ShouldResizeImageRepresentation(
     41     ImageLoader::ImageRepresentation::ResizeCondition resize_method,
     42     const gfx::Size& decoded_size,
     43     const gfx::Size& desired_size) {
     44   switch (resize_method) {
     45     case ImageLoader::ImageRepresentation::ALWAYS_RESIZE:
     46       return decoded_size != desired_size;
     47     case ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER:
     48       return decoded_size.width() > desired_size.width() ||
     49              decoded_size.height() > desired_size.height();
     50     default:
     51       NOTREACHED();
     52       return false;
     53   }
     54 }
     55 
     56 SkBitmap ResizeIfNeeded(const SkBitmap& bitmap,
     57                         const ImageLoader::ImageRepresentation& image_info) {
     58   gfx::Size original_size(bitmap.width(), bitmap.height());
     59   if (ShouldResizeImageRepresentation(image_info.resize_condition,
     60                                       original_size,
     61                                       image_info.desired_size)) {
     62     return skia::ImageOperations::Resize(
     63         bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
     64         image_info.desired_size.width(), image_info.desired_size.height());
     65   }
     66 
     67   return bitmap;
     68 }
     69 
     70 void LoadResourceOnUIThread(int resource_id, SkBitmap* bitmap) {
     71   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     72 
     73   gfx::ImageSkia image(
     74       *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id));
     75   image.MakeThreadSafe();
     76   *bitmap = *image.bitmap();
     77 }
     78 
     79 void LoadImageOnBlockingPool(const ImageLoader::ImageRepresentation& image_info,
     80                              SkBitmap* bitmap) {
     81   DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
     82 
     83   // Read the file from disk.
     84   std::string file_contents;
     85   base::FilePath path = image_info.resource.GetFilePath();
     86   if (path.empty() || !file_util::ReadFileToString(path, &file_contents)) {
     87     return;
     88   }
     89 
     90   const unsigned char* data =
     91       reinterpret_cast<const unsigned char*>(file_contents.data());
     92   // Note: This class only decodes bitmaps from extension resources. Chrome
     93   // doesn't (for security reasons) directly load extension resources provided
     94   // by the extension author, but instead decodes them in a separate
     95   // locked-down utility process. Only if the decoding succeeds is the image
     96   // saved from memory to disk and subsequently used in the Chrome UI.
     97   // Chrome is therefore decoding bitmaps here that were generated by Chrome.
     98   gfx::PNGCodec::Decode(data, file_contents.length(), bitmap);
     99 }
    100 
    101 // Add the resources from |entries| (there are |size| of them) to
    102 // |path_to_resource_id| after normalizing separators.
    103 void AddComponentResourceEntries(
    104     std::map<base::FilePath, int>* path_to_resource_id,
    105     const GritResourceMap* entries,
    106     size_t size) {
    107   for (size_t i = 0; i < size; ++i) {
    108     base::FilePath resource_path = base::FilePath().AppendASCII(
    109         entries[i].name);
    110     resource_path = resource_path.NormalizePathSeparators();
    111 
    112     DCHECK(path_to_resource_id->find(resource_path) ==
    113         path_to_resource_id->end());
    114     (*path_to_resource_id)[resource_path] = entries[i].value;
    115   }
    116 }
    117 
    118 }  // namespace
    119 
    120 namespace extensions {
    121 
    122 ////////////////////////////////////////////////////////////////////////////////
    123 // ImageLoader::ImageRepresentation
    124 
    125 ImageLoader::ImageRepresentation::ImageRepresentation(
    126     const ExtensionResource& resource,
    127     ResizeCondition resize_condition,
    128     const gfx::Size& desired_size,
    129     ui::ScaleFactor scale_factor)
    130     : resource(resource),
    131       resize_condition(resize_condition),
    132       desired_size(desired_size),
    133       scale_factor(scale_factor) {
    134 }
    135 
    136 ImageLoader::ImageRepresentation::~ImageRepresentation() {
    137 }
    138 
    139 ////////////////////////////////////////////////////////////////////////////////
    140 // ImageLoader::LoadResult
    141 
    142 struct ImageLoader::LoadResult  {
    143   LoadResult(const SkBitmap& bitmap,
    144              const gfx::Size& original_size,
    145              const ImageRepresentation& image_representation);
    146   ~LoadResult();
    147 
    148   SkBitmap bitmap;
    149   gfx::Size original_size;
    150   ImageRepresentation image_representation;
    151 };
    152 
    153 ImageLoader::LoadResult::LoadResult(
    154     const SkBitmap& bitmap,
    155     const gfx::Size& original_size,
    156     const ImageLoader::ImageRepresentation& image_representation)
    157     : bitmap(bitmap),
    158       original_size(original_size),
    159       image_representation(image_representation) {
    160 }
    161 
    162 ImageLoader::LoadResult::~LoadResult() {
    163 }
    164 
    165 ////////////////////////////////////////////////////////////////////////////////
    166 // ImageLoader
    167 
    168 ImageLoader::ImageLoader()
    169     : weak_ptr_factory_(this) {
    170 }
    171 
    172 ImageLoader::~ImageLoader() {
    173 }
    174 
    175 // static
    176 ImageLoader* ImageLoader::Get(Profile* profile) {
    177   return ImageLoaderFactory::GetForProfile(profile);
    178 }
    179 
    180 // A map from a resource path to the resource ID.  Used only by
    181 // IsComponentExtensionResource below.
    182 static base::LazyInstance<std::map<base::FilePath, int> > path_to_resource_id =
    183     LAZY_INSTANCE_INITIALIZER;
    184 
    185 // static
    186 bool ImageLoader::IsComponentExtensionResource(
    187     const base::FilePath& extension_path,
    188     const base::FilePath& resource_path,
    189     int* resource_id) {
    190   static const GritResourceMap kExtraComponentExtensionResources[] = {
    191     {"web_store/webstore_icon_128.png", IDR_WEBSTORE_ICON},
    192     {"web_store/webstore_icon_16.png", IDR_WEBSTORE_ICON_16},
    193     {"chrome_app/product_logo_128.png", IDR_PRODUCT_LOGO_128},
    194     {"chrome_app/product_logo_16.png", IDR_PRODUCT_LOGO_16},
    195 #if defined(ENABLE_SETTINGS_APP)
    196     {"settings_app/settings_app_icon_128.png", IDR_SETTINGS_APP_ICON_128},
    197     {"settings_app/settings_app_icon_16.png", IDR_SETTINGS_APP_ICON_16},
    198     {"settings_app/settings_app_icon_32.png", IDR_SETTINGS_APP_ICON_32},
    199     {"settings_app/settings_app_icon_48.png", IDR_SETTINGS_APP_ICON_48},
    200 #endif
    201   };
    202 
    203   if (path_to_resource_id.Get().empty()) {
    204     AddComponentResourceEntries(
    205         path_to_resource_id.Pointer(),
    206         kComponentExtensionResources,
    207         kComponentExtensionResourcesSize);
    208     AddComponentResourceEntries(
    209         path_to_resource_id.Pointer(),
    210         kExtraComponentExtensionResources,
    211         arraysize(kExtraComponentExtensionResources));
    212 #if defined(USE_AURA)
    213     if (keyboard::IsKeyboardEnabled()) {
    214       size_t size;
    215       const GritResourceMap* keyboard_resources =
    216           keyboard::GetKeyboardExtensionResources(&size);
    217       AddComponentResourceEntries(
    218           path_to_resource_id.Pointer(), keyboard_resources, size);
    219     }
    220 #endif
    221   }
    222 
    223   base::FilePath directory_path = extension_path;
    224   base::FilePath resources_dir;
    225   base::FilePath relative_path;
    226   if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) ||
    227       !resources_dir.AppendRelativePath(directory_path, &relative_path)) {
    228     return false;
    229   }
    230   relative_path = relative_path.Append(resource_path);
    231   relative_path = relative_path.NormalizePathSeparators();
    232 
    233   std::map<base::FilePath, int>::const_iterator entry =
    234       path_to_resource_id.Get().find(relative_path);
    235   if (entry != path_to_resource_id.Get().end())
    236     *resource_id = entry->second;
    237 
    238   return entry != path_to_resource_id.Get().end();
    239 }
    240 
    241 void ImageLoader::LoadImageAsync(
    242     const Extension* extension,
    243     const ExtensionResource& resource,
    244     const gfx::Size& max_size,
    245     const base::Callback<void(const gfx::Image&)>& callback) {
    246   std::vector<ImageRepresentation> info_list;
    247   info_list.push_back(ImageRepresentation(
    248       resource,
    249       ImageRepresentation::RESIZE_WHEN_LARGER,
    250       max_size,
    251       ui::SCALE_FACTOR_100P));
    252   LoadImagesAsync(extension, info_list, callback);
    253 }
    254 
    255 void ImageLoader::LoadImagesAsync(
    256     const Extension* extension,
    257     const std::vector<ImageRepresentation>& info_list,
    258     const base::Callback<void(const gfx::Image&)>& callback) {
    259   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    260 
    261   // Loading an image from the cache and loading resources have to happen
    262   // on the UI thread. So do those two things first, and pass the rest of the
    263   // work of as a blocking pool task.
    264 
    265   std::vector<SkBitmap> bitmaps;
    266   bitmaps.resize(info_list.size());
    267 
    268   int i = 0;
    269   for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin();
    270        it != info_list.end(); ++it, ++i) {
    271     DCHECK(it->resource.relative_path().empty() ||
    272            extension->path() == it->resource.extension_root());
    273 
    274     int resource_id;
    275     if (extension->location() == Manifest::COMPONENT &&
    276         IsComponentExtensionResource(extension->path(),
    277                                      it->resource.relative_path(),
    278                                      &resource_id)) {
    279       LoadResourceOnUIThread(resource_id, &bitmaps[i]);
    280     }
    281   }
    282 
    283   DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    284   std::vector<LoadResult>* load_result = new std::vector<LoadResult>;
    285   BrowserThread::PostBlockingPoolTaskAndReply(
    286       FROM_HERE,
    287       base::Bind(LoadImagesOnBlockingPool, info_list, bitmaps, load_result),
    288       base::Bind(&ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(),
    289                  base::Owned(load_result), callback));
    290 }
    291 
    292 // static
    293 void ImageLoader::LoadImagesOnBlockingPool(
    294     const std::vector<ImageRepresentation>& info_list,
    295     const std::vector<SkBitmap>& bitmaps,
    296     std::vector<LoadResult>* load_result) {
    297   DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    298 
    299   int i = 0;
    300   for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin();
    301        it != info_list.end(); ++it, ++i) {
    302     // If we don't have a path there isn't anything we can do, just skip it.
    303     if (it->resource.relative_path().empty())
    304       continue;
    305 
    306     SkBitmap bitmap;
    307     if (!bitmaps[i].isNull()) {
    308       bitmap = bitmaps[i];
    309     } else {
    310       LoadImageOnBlockingPool(*it, &bitmap);
    311     }
    312 
    313     // If the image failed to load, skip it.
    314     if (bitmap.isNull() || bitmap.empty())
    315       continue;
    316 
    317     gfx::Size original_size(bitmap.width(), bitmap.height());
    318     bitmap = ResizeIfNeeded(bitmap, *it);
    319 
    320     load_result->push_back(LoadResult(bitmap, original_size, *it));
    321   }
    322 }
    323 
    324 void ImageLoader::ReplyBack(
    325     const std::vector<LoadResult>* load_result,
    326     const base::Callback<void(const gfx::Image&)>& callback) {
    327   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    328 
    329   gfx::ImageSkia image_skia;
    330 
    331   for (std::vector<LoadResult>::const_iterator it = load_result->begin();
    332        it != load_result->end(); ++it) {
    333     const SkBitmap& bitmap = it->bitmap;
    334     const ImageRepresentation& image_rep = it->image_representation;
    335 
    336     image_skia.AddRepresentation(gfx::ImageSkiaRep(
    337         bitmap, image_rep.scale_factor));
    338   }
    339 
    340   gfx::Image image;
    341   if (!image_skia.isNull()) {
    342     image_skia.MakeThreadSafe();
    343     image = gfx::Image(image_skia);
    344   }
    345 
    346   callback.Run(image);
    347 }
    348 
    349 }  // namespace extensions
    350