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 "content/public/browser/browser_thread.h"
     20 #include "extensions/common/extension.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() || !base::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 namespace {
    166 
    167 // Need to be after ImageRepresentation and LoadResult are defined.
    168 std::vector<ImageLoader::LoadResult> LoadImagesOnBlockingPool(
    169     const std::vector<ImageLoader::ImageRepresentation>& info_list,
    170     const std::vector<SkBitmap>& bitmaps) {
    171   DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    172   std::vector<ImageLoader::LoadResult> load_result;
    173 
    174   for (size_t i = 0; i < info_list.size(); ++i) {
    175     const ImageLoader::ImageRepresentation& image = info_list[i];
    176 
    177     // If we don't have a path there isn't anything we can do, just skip it.
    178     if (image.resource.relative_path().empty())
    179       continue;
    180 
    181     SkBitmap bitmap;
    182     if (bitmaps[i].isNull())
    183       LoadImageOnBlockingPool(image, &bitmap);
    184     else
    185       bitmap = bitmaps[i];
    186 
    187     // If the image failed to load, skip it.
    188     if (bitmap.isNull() || bitmap.empty())
    189       continue;
    190 
    191     gfx::Size original_size(bitmap.width(), bitmap.height());
    192     bitmap = ResizeIfNeeded(bitmap, image);
    193 
    194     load_result.push_back(
    195         ImageLoader::LoadResult(bitmap, original_size, image));
    196   }
    197 
    198   return load_result;
    199 }
    200 
    201 }  // namespace
    202 
    203 ////////////////////////////////////////////////////////////////////////////////
    204 // ImageLoader
    205 
    206 ImageLoader::ImageLoader()
    207     : weak_ptr_factory_(this) {
    208 }
    209 
    210 ImageLoader::~ImageLoader() {
    211 }
    212 
    213 // static
    214 ImageLoader* ImageLoader::Get(content::BrowserContext* context) {
    215   return ImageLoaderFactory::GetForBrowserContext(context);
    216 }
    217 
    218 // A map from a resource path to the resource ID.  Used only by
    219 // IsComponentExtensionResource below.
    220 static base::LazyInstance<std::map<base::FilePath, int> > path_to_resource_id =
    221     LAZY_INSTANCE_INITIALIZER;
    222 
    223 // static
    224 bool ImageLoader::IsComponentExtensionResource(
    225     const base::FilePath& extension_path,
    226     const base::FilePath& resource_path,
    227     int* resource_id) {
    228   static const GritResourceMap kExtraComponentExtensionResources[] = {
    229     {"web_store/webstore_icon_128.png", IDR_WEBSTORE_ICON},
    230     {"web_store/webstore_icon_16.png", IDR_WEBSTORE_ICON_16},
    231     {"chrome_app/product_logo_128.png", IDR_PRODUCT_LOGO_128},
    232     {"chrome_app/product_logo_16.png", IDR_PRODUCT_LOGO_16},
    233 #if defined(ENABLE_SETTINGS_APP)
    234     {"settings_app/settings_app_icon_128.png", IDR_SETTINGS_APP_ICON_128},
    235     {"settings_app/settings_app_icon_16.png", IDR_SETTINGS_APP_ICON_16},
    236     {"settings_app/settings_app_icon_32.png", IDR_SETTINGS_APP_ICON_32},
    237     {"settings_app/settings_app_icon_48.png", IDR_SETTINGS_APP_ICON_48},
    238 #endif
    239   };
    240 
    241   if (path_to_resource_id.Get().empty()) {
    242     AddComponentResourceEntries(
    243         path_to_resource_id.Pointer(),
    244         kComponentExtensionResources,
    245         kComponentExtensionResourcesSize);
    246     AddComponentResourceEntries(
    247         path_to_resource_id.Pointer(),
    248         kExtraComponentExtensionResources,
    249         arraysize(kExtraComponentExtensionResources));
    250 #if defined(USE_AURA)
    251     if (keyboard::IsKeyboardEnabled()) {
    252       size_t size;
    253       const GritResourceMap* keyboard_resources =
    254           keyboard::GetKeyboardExtensionResources(&size);
    255       AddComponentResourceEntries(
    256           path_to_resource_id.Pointer(), keyboard_resources, size);
    257     }
    258 #endif
    259   }
    260 
    261   base::FilePath directory_path = extension_path;
    262   base::FilePath resources_dir;
    263   base::FilePath relative_path;
    264   if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) ||
    265       !resources_dir.AppendRelativePath(directory_path, &relative_path)) {
    266     return false;
    267   }
    268   relative_path = relative_path.Append(resource_path);
    269   relative_path = relative_path.NormalizePathSeparators();
    270 
    271   std::map<base::FilePath, int>::const_iterator entry =
    272       path_to_resource_id.Get().find(relative_path);
    273   if (entry != path_to_resource_id.Get().end())
    274     *resource_id = entry->second;
    275 
    276   return entry != path_to_resource_id.Get().end();
    277 }
    278 
    279 void ImageLoader::LoadImageAsync(const Extension* extension,
    280                                  const ExtensionResource& resource,
    281                                  const gfx::Size& max_size,
    282                                  const ImageLoaderCallback& callback) {
    283   std::vector<ImageRepresentation> info_list;
    284   info_list.push_back(ImageRepresentation(
    285       resource,
    286       ImageRepresentation::RESIZE_WHEN_LARGER,
    287       max_size,
    288       ui::SCALE_FACTOR_100P));
    289   LoadImagesAsync(extension, info_list, callback);
    290 }
    291 
    292 void ImageLoader::LoadImagesAsync(
    293     const Extension* extension,
    294     const std::vector<ImageRepresentation>& info_list,
    295     const ImageLoaderCallback& callback) {
    296   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    297 
    298   // Loading an image from the cache and loading resources have to happen
    299   // on the UI thread. So do those two things first, and pass the rest of the
    300   // work of as a blocking pool task.
    301 
    302   std::vector<SkBitmap> bitmaps;
    303   bitmaps.resize(info_list.size());
    304 
    305   int i = 0;
    306   for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin();
    307        it != info_list.end(); ++it, ++i) {
    308     DCHECK(it->resource.relative_path().empty() ||
    309            extension->path() == it->resource.extension_root());
    310 
    311     int resource_id;
    312     if (extension->location() == Manifest::COMPONENT &&
    313         IsComponentExtensionResource(extension->path(),
    314                                      it->resource.relative_path(),
    315                                      &resource_id)) {
    316       LoadResourceOnUIThread(resource_id, &bitmaps[i]);
    317       if (bitmaps[i].isNull()) {
    318         // bshe's log for http://crbug.com/314872
    319         LOG(ERROR) << "Component extension icon for " << extension->name()
    320                    << " is null.";
    321         LOG(ERROR) << "Extension icon resource id = " << resource_id
    322                    << "; desired_size = " << it->desired_size.ToString()
    323                    << "; scale_factor = " << it->scale_factor;
    324       }
    325     }
    326   }
    327 
    328   DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
    329   base::PostTaskAndReplyWithResult(
    330       BrowserThread::GetBlockingPool(),
    331       FROM_HERE,
    332       base::Bind(LoadImagesOnBlockingPool, info_list, bitmaps),
    333       base::Bind(&ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(),
    334                  callback));
    335 }
    336 
    337 void ImageLoader::ReplyBack(const ImageLoaderCallback& callback,
    338                             const std::vector<LoadResult>& load_result) {
    339   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    340 
    341   gfx::ImageSkia image_skia;
    342 
    343   for (std::vector<LoadResult>::const_iterator it = load_result.begin();
    344        it != load_result.end(); ++it) {
    345     const SkBitmap& bitmap = it->bitmap;
    346     const ImageRepresentation& image_rep = it->image_representation;
    347 
    348     image_skia.AddRepresentation(gfx::ImageSkiaRep(
    349         bitmap,
    350         ui::GetImageScale(image_rep.scale_factor)));
    351   }
    352 
    353   gfx::Image image;
    354   if (!image_skia.isNull()) {
    355     image_skia.MakeThreadSafe();
    356     image = gfx::Image(image_skia);
    357   }
    358 
    359   callback.Run(image);
    360 }
    361 
    362 }  // namespace extensions
    363