// Copyright 2014 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 "extensions/browser/image_loader.h" #include <map> #include <vector> #include "base/callback.h" #include "base/compiler_specific.h" #include "base/files/file_util.h" #include "base/strings/string_number_conversions.h" #include "base/threading/sequenced_worker_pool.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/component_extension_resource_manager.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/image_loader_factory.h" #include "extensions/common/extension.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_family.h" #include "ui/gfx/image/image_skia.h" using content::BrowserThread; using extensions::Extension; using extensions::ExtensionsBrowserClient; 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(); case ImageLoader::ImageRepresentation::NEVER_RESIZE: return false; 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_CURRENTLY_ON(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); } std::vector<SkBitmap> LoadResourceBitmaps( const Extension* extension, const std::vector<ImageLoader::ImageRepresentation>& info_list) { // Loading resources has to happen on the UI thread. So do this first, and // pass the rest of the work off as a blocking pool task. std::vector<SkBitmap> bitmaps; bitmaps.resize(info_list.size()); int i = 0; for (std::vector<ImageLoader::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) { extensions::ComponentExtensionResourceManager* manager = extensions::ExtensionsBrowserClient::Get()-> GetComponentExtensionResourceManager(); if (manager && manager->IsComponentExtensionResource( extension->path(), it->resource.relative_path(), &resource_id)) { LoadResourceOnUIThread(resource_id, &bitmaps[i]); } } } return bitmaps; } } // 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); } void ImageLoader::LoadImageAsync(const Extension* extension, const ExtensionResource& resource, const gfx::Size& max_size, const ImageLoaderImageCallback& 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 ImageLoaderImageCallback& callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); base::PostTaskAndReplyWithResult( BrowserThread::GetBlockingPool(), FROM_HERE, base::Bind(LoadImagesOnBlockingPool, info_list, LoadResourceBitmaps(extension, info_list)), base::Bind( &ImageLoader::ReplyBack, weak_ptr_factory_.GetWeakPtr(), callback)); } void ImageLoader::LoadImageFamilyAsync( const extensions::Extension* extension, const std::vector<ImageRepresentation>& info_list, const ImageLoaderImageFamilyCallback& callback) { DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK(!BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); base::PostTaskAndReplyWithResult( BrowserThread::GetBlockingPool(), FROM_HERE, base::Bind(LoadImagesOnBlockingPool, info_list, LoadResourceBitmaps(extension, info_list)), base::Bind(&ImageLoader::ReplyBackWithImageFamily, weak_ptr_factory_.GetWeakPtr(), callback)); } void ImageLoader::ReplyBack(const ImageLoaderImageCallback& callback, const std::vector<LoadResult>& load_result) { DCHECK_CURRENTLY_ON(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::GetScaleForScaleFactor(image_rep.scale_factor))); } gfx::Image image; if (!image_skia.isNull()) { image_skia.MakeThreadSafe(); image = gfx::Image(image_skia); } callback.Run(image); } void ImageLoader::ReplyBackWithImageFamily( const ImageLoaderImageFamilyCallback& callback, const std::vector<LoadResult>& load_result) { DCHECK_CURRENTLY_ON(BrowserThread::UI); std::map<std::pair<int, int>, gfx::ImageSkia> image_skia_map; gfx::ImageFamily image_family; 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; const std::pair<int, int> key = std::make_pair( image_rep.desired_size.width(), image_rep.desired_size.height()); // Create a new ImageSkia for this width/height, or add a representation to // an existing ImageSkia with the same width/height. image_skia_map[key].AddRepresentation( gfx::ImageSkiaRep(bitmap, ui::GetScaleForScaleFactor(image_rep.scale_factor))); } for (std::map<std::pair<int, int>, gfx::ImageSkia>::iterator it = image_skia_map.begin(); it != image_skia_map.end(); ++it) { it->second.MakeThreadSafe(); image_family.Add(it->second); } callback.Run(image_family); } } // namespace extensions