普通文本  |  363行  |  12.11 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/image_loader.h"

#include <map>
#include <vector>

#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/lazy_instance.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/extensions/image_loader_factory.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/common/extension.h"
#include "grit/chrome_unscaled_resources.h"
#include "grit/component_extension_resources_map.h"
#include "grit/theme_resources.h"
#include "skia/ext/image_operations.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/image/image_skia.h"

#if defined(USE_AURA)
#include "ui/keyboard/keyboard_util.h"
#endif

using content::BrowserThread;
using extensions::Extension;
using extensions::ImageLoader;
using extensions::Manifest;

namespace {

bool ShouldResizeImageRepresentation(
    ImageLoader::ImageRepresentation::ResizeCondition resize_method,
    const gfx::Size& decoded_size,
    const gfx::Size& desired_size) {
  switch (resize_method) {
    case ImageLoader::ImageRepresentation::ALWAYS_RESIZE:
      return decoded_size != desired_size;
    case ImageLoader::ImageRepresentation::RESIZE_WHEN_LARGER:
      return decoded_size.width() > desired_size.width() ||
             decoded_size.height() > desired_size.height();
    default:
      NOTREACHED();
      return false;
  }
}

SkBitmap ResizeIfNeeded(const SkBitmap& bitmap,
                        const ImageLoader::ImageRepresentation& image_info) {
  gfx::Size original_size(bitmap.width(), bitmap.height());
  if (ShouldResizeImageRepresentation(image_info.resize_condition,
                                      original_size,
                                      image_info.desired_size)) {
    return skia::ImageOperations::Resize(
        bitmap, skia::ImageOperations::RESIZE_LANCZOS3,
        image_info.desired_size.width(), image_info.desired_size.height());
  }

  return bitmap;
}

void LoadResourceOnUIThread(int resource_id, SkBitmap* bitmap) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  gfx::ImageSkia image(
      *ResourceBundle::GetSharedInstance().GetImageSkiaNamed(resource_id));
  image.MakeThreadSafe();
  *bitmap = *image.bitmap();
}

void LoadImageOnBlockingPool(const ImageLoader::ImageRepresentation& image_info,
                             SkBitmap* bitmap) {
  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());

  // Read the file from disk.
  std::string file_contents;
  base::FilePath path = image_info.resource.GetFilePath();
  if (path.empty() || !base::ReadFileToString(path, &file_contents)) {
    return;
  }

  const unsigned char* data =
      reinterpret_cast<const unsigned char*>(file_contents.data());
  // Note: This class only decodes bitmaps from extension resources. Chrome
  // doesn't (for security reasons) directly load extension resources provided
  // by the extension author, but instead decodes them in a separate
  // locked-down utility process. Only if the decoding succeeds is the image
  // saved from memory to disk and subsequently used in the Chrome UI.
  // Chrome is therefore decoding bitmaps here that were generated by Chrome.
  gfx::PNGCodec::Decode(data, file_contents.length(), bitmap);
}

// Add the resources from |entries| (there are |size| of them) to
// |path_to_resource_id| after normalizing separators.
void AddComponentResourceEntries(
    std::map<base::FilePath, int>* path_to_resource_id,
    const GritResourceMap* entries,
    size_t size) {
  for (size_t i = 0; i < size; ++i) {
    base::FilePath resource_path = base::FilePath().AppendASCII(
        entries[i].name);
    resource_path = resource_path.NormalizePathSeparators();

    DCHECK(path_to_resource_id->find(resource_path) ==
        path_to_resource_id->end());
    (*path_to_resource_id)[resource_path] = entries[i].value;
  }
}

}  // namespace

namespace extensions {

////////////////////////////////////////////////////////////////////////////////
// ImageLoader::ImageRepresentation

ImageLoader::ImageRepresentation::ImageRepresentation(
    const ExtensionResource& resource,
    ResizeCondition resize_condition,
    const gfx::Size& desired_size,
    ui::ScaleFactor scale_factor)
    : resource(resource),
      resize_condition(resize_condition),
      desired_size(desired_size),
      scale_factor(scale_factor) {
}

ImageLoader::ImageRepresentation::~ImageRepresentation() {
}

////////////////////////////////////////////////////////////////////////////////
// ImageLoader::LoadResult

struct ImageLoader::LoadResult  {
  LoadResult(const SkBitmap& bitmap,
             const gfx::Size& original_size,
             const ImageRepresentation& image_representation);
  ~LoadResult();

  SkBitmap bitmap;
  gfx::Size original_size;
  ImageRepresentation image_representation;
};

ImageLoader::LoadResult::LoadResult(
    const SkBitmap& bitmap,
    const gfx::Size& original_size,
    const ImageLoader::ImageRepresentation& image_representation)
    : bitmap(bitmap),
      original_size(original_size),
      image_representation(image_representation) {
}

ImageLoader::LoadResult::~LoadResult() {
}

namespace {

// Need to be after ImageRepresentation and LoadResult are defined.
std::vector<ImageLoader::LoadResult> LoadImagesOnBlockingPool(
    const std::vector<ImageLoader::ImageRepresentation>& info_list,
    const std::vector<SkBitmap>& bitmaps) {
  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
  std::vector<ImageLoader::LoadResult> load_result;

  for (size_t i = 0; i < info_list.size(); ++i) {
    const ImageLoader::ImageRepresentation& image = info_list[i];

    // If we don't have a path there isn't anything we can do, just skip it.
    if (image.resource.relative_path().empty())
      continue;

    SkBitmap bitmap;
    if (bitmaps[i].isNull())
      LoadImageOnBlockingPool(image, &bitmap);
    else
      bitmap = bitmaps[i];

    // If the image failed to load, skip it.
    if (bitmap.isNull() || bitmap.empty())
      continue;

    gfx::Size original_size(bitmap.width(), bitmap.height());
    bitmap = ResizeIfNeeded(bitmap, image);

    load_result.push_back(
        ImageLoader::LoadResult(bitmap, original_size, image));
  }

  return load_result;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// ImageLoader

ImageLoader::ImageLoader()
    : weak_ptr_factory_(this) {
}

ImageLoader::~ImageLoader() {
}

// static
ImageLoader* ImageLoader::Get(content::BrowserContext* context) {
  return ImageLoaderFactory::GetForBrowserContext(context);
}

// A map from a resource path to the resource ID.  Used only by
// IsComponentExtensionResource below.
static base::LazyInstance<std::map<base::FilePath, int> > path_to_resource_id =
    LAZY_INSTANCE_INITIALIZER;

// static
bool ImageLoader::IsComponentExtensionResource(
    const base::FilePath& extension_path,
    const base::FilePath& resource_path,
    int* resource_id) {
  static const GritResourceMap kExtraComponentExtensionResources[] = {
    {"web_store/webstore_icon_128.png", IDR_WEBSTORE_ICON},
    {"web_store/webstore_icon_16.png", IDR_WEBSTORE_ICON_16},
    {"chrome_app/product_logo_128.png", IDR_PRODUCT_LOGO_128},
    {"chrome_app/product_logo_16.png", IDR_PRODUCT_LOGO_16},
#if defined(ENABLE_SETTINGS_APP)
    {"settings_app/settings_app_icon_128.png", IDR_SETTINGS_APP_ICON_128},
    {"settings_app/settings_app_icon_16.png", IDR_SETTINGS_APP_ICON_16},
    {"settings_app/settings_app_icon_32.png", IDR_SETTINGS_APP_ICON_32},
    {"settings_app/settings_app_icon_48.png", IDR_SETTINGS_APP_ICON_48},
#endif
  };

  if (path_to_resource_id.Get().empty()) {
    AddComponentResourceEntries(
        path_to_resource_id.Pointer(),
        kComponentExtensionResources,
        kComponentExtensionResourcesSize);
    AddComponentResourceEntries(
        path_to_resource_id.Pointer(),
        kExtraComponentExtensionResources,
        arraysize(kExtraComponentExtensionResources));
#if defined(USE_AURA)
    if (keyboard::IsKeyboardEnabled()) {
      size_t size;
      const GritResourceMap* keyboard_resources =
          keyboard::GetKeyboardExtensionResources(&size);
      AddComponentResourceEntries(
          path_to_resource_id.Pointer(), keyboard_resources, size);
    }
#endif
  }

  base::FilePath directory_path = extension_path;
  base::FilePath resources_dir;
  base::FilePath relative_path;
  if (!PathService::Get(chrome::DIR_RESOURCES, &resources_dir) ||
      !resources_dir.AppendRelativePath(directory_path, &relative_path)) {
    return false;
  }
  relative_path = relative_path.Append(resource_path);
  relative_path = relative_path.NormalizePathSeparators();

  std::map<base::FilePath, int>::const_iterator entry =
      path_to_resource_id.Get().find(relative_path);
  if (entry != path_to_resource_id.Get().end())
    *resource_id = entry->second;

  return entry != path_to_resource_id.Get().end();
}

void ImageLoader::LoadImageAsync(const Extension* extension,
                                 const ExtensionResource& resource,
                                 const gfx::Size& max_size,
                                 const ImageLoaderCallback& callback) {
  std::vector<ImageRepresentation> info_list;
  info_list.push_back(ImageRepresentation(
      resource,
      ImageRepresentation::RESIZE_WHEN_LARGER,
      max_size,
      ui::SCALE_FACTOR_100P));
  LoadImagesAsync(extension, info_list, callback);
}

void ImageLoader::LoadImagesAsync(
    const Extension* extension,
    const std::vector<ImageRepresentation>& info_list,
    const ImageLoaderCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  // Loading an image from the cache and loading resources have to happen
  // on the UI thread. So do those two things first, and pass the rest of the
  // work of as a blocking pool task.

  std::vector<SkBitmap> bitmaps;
  bitmaps.resize(info_list.size());

  int i = 0;
  for (std::vector<ImageRepresentation>::const_iterator it = info_list.begin();
       it != info_list.end(); ++it, ++i) {
    DCHECK(it->resource.relative_path().empty() ||
           extension->path() == it->resource.extension_root());

    int resource_id;
    if (extension->location() == Manifest::COMPONENT &&
        IsComponentExtensionResource(extension->path(),
                                     it->resource.relative_path(),
                                     &resource_id)) {
      LoadResourceOnUIThread(resource_id, &bitmaps[i]);
      if (bitmaps[i].isNull()) {
        // bshe's log for http://crbug.com/314872
        LOG(ERROR) << "Component extension icon for " << extension->name()
                   << " is null.";
        LOG(ERROR) << "Extension icon resource id = " << resource_id
                   << "; desired_size = " << it->desired_size.ToString()
                   << "; scale_factor = " << it->scale_factor;
      }
    }
  }

  DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
  base::PostTaskAndReplyWithResult(
      BrowserThread::GetBlockingPool(),
      FROM_HERE,
      base::Bind(LoadImagesOnBlockingPool, info_list, bitmaps),
      base::Bind(&ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(),
                 callback));
}

void ImageLoader::ReplyBack(const ImageLoaderCallback& callback,
                            const std::vector<LoadResult>& load_result) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  gfx::ImageSkia image_skia;

  for (std::vector<LoadResult>::const_iterator it = load_result.begin();
       it != load_result.end(); ++it) {
    const SkBitmap& bitmap = it->bitmap;
    const ImageRepresentation& image_rep = it->image_representation;

    image_skia.AddRepresentation(gfx::ImageSkiaRep(
        bitmap,
        ui::GetImageScale(image_rep.scale_factor)));
  }

  gfx::Image image;
  if (!image_skia.isNull()) {
    image_skia.MakeThreadSafe();
    image = gfx::Image(image_skia);
  }

  callback.Run(image);
}

}  // namespace extensions