diff options
author | dvh@chromium.org <dvh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-08 06:50:55 +0000 |
---|---|---|
committer | dvh@chromium.org <dvh@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-08 06:50:55 +0000 |
commit | dcc41abf828333397c7100044ff285afe0bafe03 (patch) | |
tree | 5ad8bd81b7231ca29759bb7d2c94463f737a8d4d | |
parent | 5488874f4eaaeaf5fd205b07695900acaf39c983 (diff) | |
download | chromium_src-dcc41abf828333397c7100044ff285afe0bafe03.zip chromium_src-dcc41abf828333397c7100044ff285afe0bafe03.tar.gz chromium_src-dcc41abf828333397c7100044ff285afe0bafe03.tar.bz2 |
Refactored loading of applications / extensions icons.
Added ImageLoader::LoadExtensionIconAsync() and ImageLoader::LoadExtensionIconDataURLAsync().
Moved all apps / extension loading code to ImageLoader.
BUG=297298, 297301, 304180
Review URL: https://codereview.chromium.org/26161002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@227477 0039d316-1c4b-4281-b951-d872f2087c98
10 files changed, 719 insertions, 306 deletions
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc index 3f594d6..40b1612 100644 --- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc +++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc @@ -27,6 +27,7 @@ #include "chrome/browser/extensions/extension_error_reporter.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/extensions/image_loader.h" #include "chrome/browser/extensions/management_policy.h" #include "chrome/browser/extensions/unpacked_installer.h" #include "chrome/browser/extensions/updater/extension_updater.h" @@ -85,45 +86,6 @@ ExtensionUpdater* GetExtensionUpdater(Profile* profile) { return profile->GetExtensionService()->updater(); } -GURL GetImageURLFromData(std::string contents) { - std::string contents_base64; - if (!base::Base64Encode(contents, &contents_base64)) - return GURL(); - - // TODO(dvh): make use of chrome::kDataScheme. Filed as crbug/297301. - const char kDataURLPrefix[] = "data:image;base64,"; - return GURL(kDataURLPrefix + contents_base64); -} - -GURL GetDefaultImageURL(developer_private::ItemType type) { - int icon_resource_id; - switch (type) { - case developer::ITEM_TYPE_LEGACY_PACKAGED_APP: - case developer::ITEM_TYPE_HOSTED_APP: - case developer::ITEM_TYPE_PACKAGED_APP: - icon_resource_id = IDR_APP_DEFAULT_ICON; - break; - default: - icon_resource_id = IDR_EXTENSION_DEFAULT_ICON; - break; - } - - return GetImageURLFromData( - ResourceBundle::GetSharedInstance().GetRawDataResourceForScale( - icon_resource_id, ui::SCALE_FACTOR_100P).as_string()); -} - -// TODO(dvh): This code should be refactored and moved to -// extensions::ImageLoader. Also a resize should be performed to avoid -// potential huge URLs: crbug/297298. -GURL ToDataURL(const base::FilePath& path, developer_private::ItemType type) { - std::string contents; - if (path.empty() || !base::ReadFileToString(path, &contents)) - return GetDefaultImageURL(type); - - return GetImageURLFromData(contents); -} - std::vector<base::FilePath> ListFolder(const base::FilePath path) { base::FileEnumerator files(path, false, base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES); @@ -374,27 +336,6 @@ scoped_ptr<developer::ItemInfo> return info.Pass(); } -void DeveloperPrivateGetItemsInfoFunction::GetIconsOnFileThread( - ItemInfoList item_list, - const std::map<std::string, ExtensionResource> idToIcon) { - for (ItemInfoList::iterator iter = item_list.begin(); - iter != item_list.end(); ++iter) { - developer_private::ItemInfo* info = iter->get(); - std::map<std::string, ExtensionResource>::const_iterator resource_ptr - = idToIcon.find(info->id); - if (resource_ptr != idToIcon.end()) { - info->icon_url = - ToDataURL(resource_ptr->second.GetFilePath(), info->type).spec(); - } - } - - results_ = developer::GetItemsInfo::Results::Create(item_list); - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - base::Bind(&DeveloperPrivateGetItemsInfoFunction::SendResponse, - this, - true)); -} - void DeveloperPrivateGetItemsInfoFunction:: GetInspectablePagesForExtensionProcess( const Extension* extension, @@ -554,37 +495,55 @@ bool DeveloperPrivateGetItemsInfoFunction::RunImpl() { items.InsertAll(*service->terminated_extensions()); } - std::map<std::string, ExtensionResource> id_to_icon; - ItemInfoList item_list; - for (ExtensionSet::const_iterator iter = items.begin(); iter != items.end(); ++iter) { const Extension& item = *iter->get(); - ExtensionResource item_resource = - IconsInfo::GetIconResource(&item, - extension_misc::EXTENSION_ICON_MEDIUM, - ExtensionIconSet::MATCH_BIGGER); - id_to_icon[item.id()] = item_resource; - // Don't show component extensions and invisible apps. if (item.ShouldNotBeVisible()) continue; - item_list.push_back(make_linked_ptr<developer::ItemInfo>( + item_list_.push_back(make_linked_ptr<developer::ItemInfo>( CreateItemInfo( item, service->IsExtensionEnabled(item.id())).release())); } - content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, - base::Bind(&DeveloperPrivateGetItemsInfoFunction::GetIconsOnFileThread, - this, - item_list, - id_to_icon)); + // Iterate over |item_list_| to request all the icons. + RequestNextIcon(); return true; } +void DeveloperPrivateGetItemsInfoFunction::RequestNextIcon() { + developer::ItemInfo* info = item_list_[icon_to_load_].get(); + ExtensionService* service = profile()->GetExtensionService(); + ImageLoader::Get(profile())->LoadExtensionIconDataURLAsync( + service->GetExtensionById(info->id, true), + extension_misc::EXTENSION_ICON_MEDIUM, + ExtensionIconSet::MATCH_BIGGER, + false, + base::Bind(&DeveloperPrivateGetItemsInfoFunction::LoadIconFinished, + this)); +} + +void DeveloperPrivateGetItemsInfoFunction::LoadIconFinished(const GURL& url) { + // Store the result. + developer::ItemInfo* info = item_list_[icon_to_load_].get(); + info->icon_url = url.spec(); + ++icon_to_load_; + if (icon_to_load_ < static_cast<int>(item_list_.size())) { + RequestNextIcon(); + } else { + // All icons have been loaded: send the result. + results_ = developer::GetItemsInfo::Results::Create(item_list_); + SendResponse(true); + } +} + +DeveloperPrivateGetItemsInfoFunction::DeveloperPrivateGetItemsInfoFunction() + : icon_to_load_(0) { +} + DeveloperPrivateGetItemsInfoFunction::~DeveloperPrivateGetItemsInfoFunction() {} bool DeveloperPrivateAllowFileAccessFunction::RunImpl() { diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.h b/chrome/browser/extensions/api/developer_private/developer_private_api.h index ac5dc5c..41d5a7f 100644 --- a/chrome/browser/extensions/api/developer_private/developer_private_api.h +++ b/chrome/browser/extensions/api/developer_private/developer_private_api.h @@ -133,6 +133,8 @@ class DeveloperPrivateGetItemsInfoFunction : public AsyncExtensionFunction { DECLARE_EXTENSION_FUNCTION("developerPrivate.getItemsInfo", DEVELOPERPRIVATE_GETITEMSINFO) + DeveloperPrivateGetItemsInfoFunction(); + protected: virtual ~DeveloperPrivateGetItemsInfoFunction(); @@ -140,15 +142,19 @@ class DeveloperPrivateGetItemsInfoFunction : public AsyncExtensionFunction { virtual bool RunImpl() OVERRIDE; private: + // List of extensions/apps items to return. This list will be populated in a + // first step. Then, it will be used to keep track of items that need to be + // filled with icons. + ItemInfoList item_list_; + + // Index of the extension/app in |item_list_| for which we need to load the + // icon. + int icon_to_load_; scoped_ptr<developer::ItemInfo> CreateItemInfo( const extensions::Extension& item, bool item_is_enabled); - void GetIconsOnFileThread( - ItemInfoList item_list, - std::map<std::string, ExtensionResource> itemIdToIconResourceMap); - // Helper that lists the current inspectable html pages for the extension. void GetInspectablePagesForExtensionProcess( const Extension* extension, @@ -169,6 +175,14 @@ class DeveloperPrivateGetItemsInfoFunction : public AsyncExtensionFunction { int render_view_id, bool incognito, bool generated_background_page); + + // Request the icon of the extension/app at the index |icon_to_load_| in + // |item_list_|. + void RequestNextIcon(); + + // Called when each icon has been loaded by RequestNextIcon(). |url| is the + // data url containing the icon. + void LoadIconFinished(const GURL& url); }; class DeveloperPrivateInspectFunction : public SyncExtensionFunction { diff --git a/chrome/browser/extensions/extension_icon_image_unittest.cc b/chrome/browser/extensions/extension_icon_image_unittest.cc index fd35ee9..1e73efa 100644 --- a/chrome/browser/extensions/extension_icon_image_unittest.cc +++ b/chrome/browser/extensions/extension_icon_image_unittest.cc @@ -88,7 +88,8 @@ class TestImageLoader { int size) { image_loaded_ = false; - image_loader_.LoadImageAsync( + image_loader_.reset(new extensions::ImageLoader(NULL)); + image_loader_->LoadImageAsync( extension_, extension_->GetResource(path), gfx::Size(size, size), base::Bind(&TestImageLoader::OnImageLoaded, base::Unretained(this))); @@ -111,7 +112,7 @@ class TestImageLoader { bool waiting_; bool image_loaded_; gfx::Image image_; - extensions::ImageLoader image_loader_; + scoped_ptr<extensions::ImageLoader> image_loader_; DISALLOW_COPY_AND_ASSIGN(TestImageLoader); }; diff --git a/chrome/browser/extensions/image_loader.cc b/chrome/browser/extensions/image_loader.cc index 2c8f77c..bcab0fe 100644 --- a/chrome/browser/extensions/image_loader.cc +++ b/chrome/browser/extensions/image_loader.cc @@ -7,6 +7,7 @@ #include <map> #include <vector> +#include "base/base64.h" #include "base/callback.h" #include "base/compiler_specific.h" #include "base/file_util.h" @@ -15,16 +16,26 @@ #include "base/strings/string_number_conversions.h" #include "base/threading/sequenced_worker_pool.h" #include "chrome/browser/extensions/image_loader_factory.h" +#include "chrome/browser/favicon/favicon_service.h" +#include "chrome/browser/favicon/favicon_service_factory.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_icon_set.h" +#include "chrome/common/extensions/manifest_handlers/app_launch_info.h" +#include "chrome/common/extensions/manifest_handlers/icons_handler.h" #include "content/public/browser/browser_thread.h" +#include "content/public/common/url_constants.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/color_utils.h" +#include "ui/gfx/favicon_size.h" #include "ui/gfx/image/image_skia.h" +#include "ui/gfx/size.h" +#include "ui/gfx/skbitmap_operations.h" #if defined(USE_AURA) #include "ui/keyboard/keyboard_util.h" @@ -115,6 +126,54 @@ void AddComponentResourceEntries( } } +// Returns a PNG image data URL of a given image. +GURL GetImageDataURL(const gfx::Image& image) { + if (image.IsEmpty()) + return GURL(); + + scoped_refptr<base::RefCountedMemory> mem = + ImageLoader::BitmapToMemory(image.ToSkBitmap()); + std::string contents_base64; + // TODO(dvh): contents_base64 calculation of the result of the function is + // sub-optimal: it's a concatenation of 3 strings and |contents_base64| can + // be huge. Additionally, the GURL is returned by copy. + // crbug/304759. + if (!base::Base64Encode( + std::string(reinterpret_cast<const char*>(mem->front()), mem->size()), + &contents_base64)) + return GURL(); + + const char kDataURLPrefix[] = ":image/png;base64,"; + return GURL(std::string(chrome::kDataScheme) + kDataURLPrefix + + contents_base64); +} + +// Converts the image to grayscale. +SkBitmap DesaturateImage(const SkBitmap* image) { + color_utils::HSL shift = {-1, 0, 0.6}; + return SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift); +} + +// Creates an image from PNG data. +SkBitmap* ToBitmap(const unsigned char* data, size_t size) { + SkBitmap* decoded = new SkBitmap(); + bool success = gfx::PNGCodec::Decode(data, size, decoded); + DCHECK(success); + return decoded; +} + +// Load an image from a resource given its identifier |resource_id|. +SkBitmap* GetImageByResourceId(int resource_id) { + std::string contents = ResourceBundle::GetSharedInstance() + .GetRawDataResourceForScale(resource_id, + ui::SCALE_FACTOR_100P).as_string(); + + // Convert and return it. + const unsigned char* data = + reinterpret_cast<const unsigned char*>(contents.data()); + return ToBitmap(data, contents.length()); +} + } // namespace namespace extensions { @@ -165,8 +224,8 @@ ImageLoader::LoadResult::~LoadResult() { //////////////////////////////////////////////////////////////////////////////// // ImageLoader -ImageLoader::ImageLoader() - : weak_ptr_factory_(this) { +ImageLoader::ImageLoader(Profile* profile) + : weak_ptr_factory_(this), profile_(profile) { } ImageLoader::~ImageLoader() { @@ -347,4 +406,273 @@ void ImageLoader::ReplyBack( callback.Run(image); } +void ImageLoader::LoadExtensionIconAsync( + const extensions::Extension* extension, + int icon_size, + ExtensionIconSet::MatchType match, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + // |extension| can be NULL when the default icon is requested. In this case, + // fail the icon loading, which will result in the default icon being loaded. + if (extension == NULL) { + LoadIconFailed(extension, icon_size, grayscale, callback); + return; + } + + ExtensionResource resource = IconsInfo::GetIconResource( + extension, icon_size, match); + LoadImageAsync(extension, + resource, + gfx::Size(icon_size, icon_size), + base::Bind(&ImageLoader::LoadExtensionIconDone, + base::Unretained(this), + extension, + icon_size, + grayscale, + callback)); +} + +void ImageLoader::LoadExtensionIconDone( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback, + const gfx::Image& image) { + if (image.IsEmpty()) + LoadIconFailed(extension, icon_size, grayscale, callback); + else + FinalizeImage(image, grayscale, callback); +} + +void ImageLoader::LoadIconFailed( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + if ((extension != NULL) && + (icon_size == extension_misc::EXTENSION_ICON_BITTY)) + LoadFaviconImage(extension, icon_size, grayscale, callback); + else + LoadDefaultImage(extension, icon_size, grayscale, callback); +} + +void ImageLoader::LoadFaviconImage( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + FaviconService* favicon_service = NULL; + if (profile_ != NULL) + favicon_service = FaviconServiceFactory::GetForProfile( + profile_, Profile::EXPLICIT_ACCESS); + // Fall back to the default icons if the service isn't available. + if (favicon_service == NULL) { + LoadDefaultImage(extension, icon_size, grayscale, callback); + return; + } + + GURL favicon_url = AppLaunchInfo::GetFullLaunchURL(extension); + favicon_service->GetRawFaviconForURL( + FaviconService::FaviconForURLParams( + profile_, favicon_url, chrome::FAVICON, gfx::kFaviconSize), + ui::SCALE_FACTOR_100P, + base::Bind(&ImageLoader::OnFaviconDataAvailable, + base::Unretained(this), + extension, + icon_size, + grayscale, + callback), + &cancelable_task_tracker_); +} + +void ImageLoader::OnFaviconDataAvailable( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback, + const chrome::FaviconBitmapResult& bitmap_result) { + // Fallback to the default icon if there wasn't a favicon. + if (!bitmap_result.is_valid()) { + LoadDefaultImage(extension, icon_size, grayscale, callback); + return; + } + + gfx::Image image = gfx::Image::CreateFrom1xBitmap(*ToBitmap( + bitmap_result.bitmap_data->front(), bitmap_result.bitmap_data->size())); + FinalizeImage(image, grayscale, callback); +} + +void ImageLoader::LoadDefaultImage( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + content::BrowserThread::PostTask( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&ImageLoader::LoadDefaultImageOnFileThread, + base::Unretained(this), + extension, + icon_size, + grayscale, + callback)); +} + +void ImageLoader::LoadDefaultImageOnFileThread( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + const SkBitmap* default_image = NULL; + if ((extension == NULL) || (extension->is_app())) + default_image = GetDefaultAppImage(); + else + default_image = GetDefaultExtensionImage(); + + SkBitmap result_image; + if (icon_size == -1) { + // If a specific size was not requested. + result_image = *default_image; + } else { + SkBitmap resized_image( + skia::ImageOperations::Resize(*default_image, + skia::ImageOperations::RESIZE_LANCZOS3, + icon_size, + icon_size)); + // There are cases where Resize returns an empty bitmap, for example if you + // ask for an image too large. In this case it is better to return the + // default image than returning nothing at all. + if (resized_image.empty()) + resized_image = *default_image; + result_image = resized_image; + } + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&ImageLoader::LoadDefaultImageDone, + base::Unretained(this), + gfx::Image::CreateFrom1xBitmap(result_image), + grayscale, + callback)); +} + +void ImageLoader::LoadDefaultImageDone( + const gfx::Image image, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + FinalizeImage(image, grayscale, callback); +} + +void ImageLoader::FinalizeImage( + const gfx::Image& image, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + content::BrowserThread::PostTask( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&ImageLoader::FinalizeImageOnFileThread, + base::Unretained(this), + image, + grayscale, + callback)); +} + +void ImageLoader::FinalizeImageOnFileThread( + const gfx::Image image, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback) { + SkBitmap bitmap; + if (grayscale) + bitmap = DesaturateImage(image.ToSkBitmap()); + else + bitmap = *image.ToSkBitmap(); + + gfx::Image modifiedImage = gfx::Image::CreateFrom1xBitmap(bitmap); + content::BrowserThread::PostTask(content::BrowserThread::UI, + FROM_HERE, + base::Bind(&ImageLoader::FinalizeImageDone, + base::Unretained(this), + modifiedImage, + callback)); +} + +void ImageLoader::FinalizeImageDone( + const gfx::Image image, + const base::Callback<void(const gfx::Image&)>& callback) { + callback.Run(image); +} + +void ImageLoader::LoadExtensionIconDataURLAsync( + const extensions::Extension* extension, + int icon_size, + ExtensionIconSet::MatchType match, + bool grayscale, + const base::Callback<void(const GURL&)>& callback) { + LoadExtensionIconAsync(extension, + icon_size, + match, + grayscale, + base::Bind(&ImageLoader::OnIconAvailable, + base::Unretained(this), + callback)); +} + +void ImageLoader::OnIconAvailable( + const base::Callback<void(const GURL&)>& callback, + const gfx::Image& image) { + content::BrowserThread::PostTask( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&ImageLoader::ConvertIconToURLOnFileThread, + base::Unretained(this), + image, + callback)); +} + +void ImageLoader::ConvertIconToURLOnFileThread( + const gfx::Image image, + const base::Callback<void(const GURL&)>& callback) { + GURL url = GetImageDataURL(image); + // TODO(dvh): |url| can be huge and it's passed by copy to + // OnIconConvertedToURL(). It can be optimized. + // crbug/304759. + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&ImageLoader::OnIconConvertedToURL, + base::Unretained(this), + url, + callback)); +} + +void ImageLoader::OnIconConvertedToURL( + const GURL url, + const base::Callback<void(const GURL&)>& callback) { + callback.Run(url); +} + +const SkBitmap* ImageLoader::GetDefaultAppImage() { + if (!default_app_icon_.get()) + default_app_icon_.reset(GetImageByResourceId(IDR_APP_DEFAULT_ICON)); + + return default_app_icon_.get(); +} + +const SkBitmap* ImageLoader::GetDefaultExtensionImage() { + if (!default_extension_icon_.get()) { + default_extension_icon_.reset( + GetImageByResourceId(IDR_EXTENSION_DEFAULT_ICON)); + } + + return default_extension_icon_.get(); +} + +scoped_refptr<base::RefCountedMemory> ImageLoader::BitmapToMemory( + const SkBitmap* image) { + base::RefCountedBytes* image_bytes = new base::RefCountedBytes; + gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_bytes->data()); + return image_bytes; +} + } // namespace extensions diff --git a/chrome/browser/extensions/image_loader.h b/chrome/browser/extensions/image_loader.h index 5e6bc3e..9259c52 100644 --- a/chrome/browser/extensions/image_loader.h +++ b/chrome/browser/extensions/image_loader.h @@ -10,14 +10,25 @@ #include "base/callback_forward.h" #include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" +#include "chrome/common/cancelable_task_tracker.h" +#include "chrome/common/extensions/extension_icon_set.h" #include "components/browser_context_keyed_service/browser_context_keyed_service.h" #include "extensions/common/extension_resource.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/layout.h" #include "ui/gfx/size.h" +class GURL; class Profile; +namespace base { +class RefCountedMemory; +} + +namespace chrome { +struct FaviconBitmapResult; +} + namespace gfx { class Image; } @@ -68,9 +79,6 @@ class ImageLoader : public BrowserContextKeyedService { // a convenience wrapper around ImageLoaderFactory::GetForProfile. static ImageLoader* Get(Profile* profile); - ImageLoader(); - virtual ~ImageLoader(); - // Checks whether image is a component extension resource. Returns false // if a given |resource| does not have a corresponding image in bundled // resources. Otherwise fills |resource_id|. This doesn't check if the @@ -80,6 +88,13 @@ class ImageLoader : public BrowserContextKeyedService { const base::FilePath& resource_path, int* resource_id); + // Converts a bitmap image to a PNG representation. + static scoped_refptr<base::RefCountedMemory> BitmapToMemory( + const SkBitmap* image); + + explicit ImageLoader(Profile* profile); + virtual ~ImageLoader(); + // Specify image resource to load. If the loaded image is larger than // |max_size| it will be resized to those dimensions. IMPORTANT NOTE: this // function may call back your callback synchronously (ie before it returns) @@ -96,11 +111,46 @@ class ImageLoader : public BrowserContextKeyedService { // type. void LoadImagesAsync(const extensions::Extension* extension, const std::vector<ImageRepresentation>& info_list, - const base::Callback<void(const gfx::Image&)>& callback); + const base::Callback<void(const gfx::Image&)>& + callback); + + // Load the icon of the given |extension| and return it via |callback| as a + // gfx::Image. |extension| can be NULL to request the default extension icon. + // The size in pixels of the returned icon can be chosen with |icon_size| or + // -1, -1 if no resize is requested. The icon can also be converted to + // grayscale by setting |grayscale| to true. |match| optionally indicates if + // it should fallback to smaller or bigger size when choosing the icon source + // image. + void LoadExtensionIconAsync(const extensions::Extension* extension, + int icon_size, + ExtensionIconSet::MatchType match, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& + callback); + + // Same as LoadExtensionIconAsync() above except the result icon is returned + // as a PNG image data URL. + void LoadExtensionIconDataURLAsync(const extensions::Extension* extension, + int icon_size, + ExtensionIconSet::MatchType match, + bool grayscale, + const base::Callback<void(const GURL&)>& + callback); private: base::WeakPtrFactory<ImageLoader> weak_ptr_factory_; + Profile* profile_; + + // Task tracker when getting favicon. + CancelableTaskTracker cancelable_task_tracker_; + + // Cache for the default app icon. + scoped_ptr<SkBitmap> default_app_icon_; + + // Cache for the default extension icon. + scoped_ptr<SkBitmap> default_extension_icon_; + static void LoadImagesOnBlockingPool( const std::vector<ImageRepresentation>& info_list, const std::vector<SkBitmap>& bitmaps, @@ -109,6 +159,123 @@ class ImageLoader : public BrowserContextKeyedService { void ReplyBack( const std::vector<LoadResult>* load_result, const base::Callback<void(const gfx::Image&)>& callback); + + // The sequence for LoadExtensionIconAsync() is the following: + // 1) It loads the icon image using LoadImageAsync(). + // 2) When it finishes, LoadExtensionIconLoaded() will be called. + // 3) On success, it will call FinalizeImage(). If it failed, it will call + // LoadIconFailed(). See below for more on those methods. + void LoadExtensionIconDone(const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& + callback, + const gfx::Image& image); + + // Called when the extension doesn't have an icon. We fall back to multiple + // sources, using the following order: + // 1) The icons as listed in the extension manifests. + // 2) If a 16px icon and the extension has a launch URL, see if Chrome has a + // corresponding favicon. + // 3) If still no matches, load the default extension icon. + void LoadIconFailed(const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback); + + // Loads the favicon image for the given |extension|. If the image does not + // exist, we fall back to the default image. + void LoadFaviconImage(const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& + callback); + + // FaviconService callback. It will call FinalizedImage() on success or try + // another fallback. + void OnFaviconDataAvailable( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback, + const chrome::FaviconBitmapResult& bitmap_result); + + // The sequence for LoadDefaultImage() will be the following: + // 1) LoadDefaultImage() will invoke LoadDefaultImageOnFileThread() on the + // file thread. + // 2) LoadDefaultImageOnFileThread() will perform the work, then invoke + // LoadDefaultImageDone() on the UI thread. + void LoadDefaultImage(const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& + callback); + + // Loads the default image on the file thread. + void LoadDefaultImageOnFileThread( + const extensions::Extension* extension, + int icon_size, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback); + + // When loading of default image is done, it will call FinalizeImage(). + void LoadDefaultImageDone(const gfx::Image image, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& + callback); + + // Performs any remaining transformations (like desaturating the |image|), + // then returns the |image| to the |callback|. + // + // The sequence for FinalizeImage() will be the following: + // 1) FinalizeImage() will invoke FinalizeImageOnFileThread() on the file + // thread. + // 2) FinalizeImageOnFileThread() will perform the work, then invoke + // FinalizeImageDone() on the UI thread. + void FinalizeImage(const gfx::Image& image, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& callback); + + // Process the "finalize" operation on the file thread. + void FinalizeImageOnFileThread(const gfx::Image image, + bool grayscale, + const base::Callback<void(const gfx::Image&)>& + callback); + + // Called when the "finalize" operation on the file thread is done. + void FinalizeImageDone(const gfx::Image image, + const base::Callback<void(const gfx::Image&)> & + callback); + + // The sequence for LoadExtensionIconDataURLAsync() will be the following: + // 1) Call LoadExtensionIconAsync() to fetch the icon of the extension. + // 2) When the icon is loaded, OnIconAvailable() will be called and will + // invoke ConvertIconToURLOnFileThread() on the file thread. + // 3) OnIconConvertedToURL() will be called on the UI thread when it's done + // and will call the callback. + // + // LoadExtensionIconDataURLAsync() will use LoadExtensionIconAsync() to get + // the icon of the extension. The following method will be called when the + // image has been fetched. + void OnIconAvailable(const base::Callback<void(const GURL&)>& callback, + const gfx::Image& image); + + // ConvertIconToURLOnFileThread() will convert the image to a PNG image data + // URL on the file thread. + void ConvertIconToURLOnFileThread(const gfx::Image image, + const base::Callback<void(const GURL&)>& + callback); + + // This method will call the callback of LoadExtensionIconDataURLAsync() with + // the result. + void OnIconConvertedToURL(const GURL url, + const base::Callback<void(const GURL&)>& callback); + + // Returns the bitmap for the default extension icon. + const SkBitmap* GetDefaultExtensionImage(); + + // Returns the bitmap for the default app icon. + const SkBitmap* GetDefaultAppImage(); }; } // namespace extensions diff --git a/chrome/browser/extensions/image_loader_factory.cc b/chrome/browser/extensions/image_loader_factory.cc index 6d08dd7..14dfbd5 100644 --- a/chrome/browser/extensions/image_loader_factory.cc +++ b/chrome/browser/extensions/image_loader_factory.cc @@ -32,7 +32,7 @@ ImageLoaderFactory::~ImageLoaderFactory() { BrowserContextKeyedService* ImageLoaderFactory::BuildServiceInstanceFor( content::BrowserContext* profile) const { - return new ImageLoader; + return new ImageLoader(static_cast<Profile*>(profile)); } bool ImageLoaderFactory::ServiceIsCreatedWithBrowserContext() const { diff --git a/chrome/browser/extensions/image_loader_unittest.cc b/chrome/browser/extensions/image_loader_unittest.cc index 05cea06..b1320d3 100644 --- a/chrome/browser/extensions/image_loader_unittest.cc +++ b/chrome/browser/extensions/image_loader_unittest.cc @@ -34,7 +34,6 @@ class ImageLoaderTest : public testing::Test { public: ImageLoaderTest() : image_loaded_count_(0), - quit_in_image_loaded_(false), ui_thread_(BrowserThread::UI, &ui_loop_), file_thread_(BrowserThread::FILE), io_thread_(BrowserThread::IO) { @@ -42,15 +41,18 @@ class ImageLoaderTest : public testing::Test { void OnImageLoaded(const gfx::Image& image) { image_loaded_count_++; - if (quit_in_image_loaded_) - base::MessageLoop::current()->Quit(); + base::MessageLoop::current()->Quit(); image_ = image; } + void OnImageURLLoaded(const GURL& url) { + image_loaded_count_++; + base::MessageLoop::current()->Quit(); + url_ = url; + } + void WaitForImageLoad() { - quit_in_image_loaded_ = true; base::MessageLoop::current()->Run(); - quit_in_image_loaded_ = false; } int image_loaded_count() { @@ -94,7 +96,10 @@ class ImageLoaderTest : public testing::Test { Extension::NO_FLAGS, &error); } + // Holds the image loaded by the test. gfx::Image image_; + // Holds the data url retrieved by the test. + GURL url_; private: virtual void SetUp() OVERRIDE { @@ -104,7 +109,6 @@ class ImageLoaderTest : public testing::Test { } int image_loaded_count_; - bool quit_in_image_loaded_; base::MessageLoop ui_loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread file_thread_; @@ -123,7 +127,7 @@ TEST_F(ImageLoaderTest, LoadImage) { ExtensionIconSet::MATCH_EXACTLY); gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH, extension_misc::EXTENSION_ICON_SMALLISH); - ImageLoader loader; + ImageLoader loader(NULL); loader.LoadImageAsync(extension.get(), image_resource, max_size, @@ -156,7 +160,7 @@ TEST_F(ImageLoaderTest, DeleteExtensionWhileWaitingForCache) { ExtensionIconSet::MATCH_EXACTLY); gfx::Size max_size(extension_misc::EXTENSION_ICON_SMALLISH, extension_misc::EXTENSION_ICON_SMALLISH); - ImageLoader loader; + ImageLoader loader(NULL); std::set<int> sizes; sizes.insert(extension_misc::EXTENSION_ICON_SMALLISH); loader.LoadImageAsync(extension.get(), @@ -210,7 +214,7 @@ TEST_F(ImageLoaderTest, MultipleImages) { ui::SCALE_FACTOR_NONE)); } - ImageLoader loader; + ImageLoader loader(NULL); loader.LoadImagesAsync(extension.get(), info_list, base::Bind(&ImageLoaderTest::OnImageLoaded, base::Unretained(this))); @@ -258,3 +262,126 @@ TEST_F(ImageLoaderTest, IsComponentExtensionResource) { ASSERT_EQ(IDR_FILE_MANAGER_ICON_16, resource_id); #endif } + +// Tests loading an image works correctly. +TEST_F(ImageLoaderTest, LoadIcon) { + scoped_refptr<Extension> extension(CreateExtension( + "image_loading_tracker", Manifest::INVALID_LOCATION)); + ASSERT_TRUE(extension.get() != NULL); + + ImageLoader loader(NULL); + loader.LoadExtensionIconAsync(extension.get(), + extension_misc::EXTENSION_ICON_SMALLISH, + ExtensionIconSet::MATCH_BIGGER, + false, + base::Bind(&ImageLoaderTest::OnImageLoaded, + base::Unretained(this))); + + // The image isn't cached, so we should not have received notification. + EXPECT_EQ(0, image_loaded_count()); + + WaitForImageLoad(); + + // We should have gotten the image. + EXPECT_EQ(1, image_loaded_count()); + + // Check that the image was loaded. + EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, + image_.ToSkBitmap()->width()); +} + +// Tests loading an icon in grayscale works correctly. +TEST_F(ImageLoaderTest, LoadIconGrayscale) { + scoped_refptr<Extension> extension(CreateExtension( + "image_loading_tracker", Manifest::INVALID_LOCATION)); + ASSERT_TRUE(extension.get() != NULL); + + ImageLoader loader(NULL); + loader.LoadExtensionIconAsync(extension.get(), + extension_misc::EXTENSION_ICON_SMALLISH, + ExtensionIconSet::MATCH_BIGGER, + true, + base::Bind(&ImageLoaderTest::OnImageLoaded, + base::Unretained(this))); + + // The image isn't cached, so we should not have received notification. + EXPECT_EQ(0, image_loaded_count()); + + WaitForImageLoad(); + + // We should have gotten the image. + EXPECT_EQ(1, image_loaded_count()); + + // Check that the image was loaded. + EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, + image_.ToSkBitmap()->width()); +} + +// Tests loading an icon URL works correctly. +TEST_F(ImageLoaderTest, LoadIconURL) { + scoped_refptr<Extension> extension(CreateExtension( + "image_loading_tracker", Manifest::INVALID_LOCATION)); + ASSERT_TRUE(extension.get() != NULL); + + ImageLoader loader(NULL); + loader.LoadExtensionIconDataURLAsync( + extension.get(), + extension_misc::EXTENSION_ICON_SMALLISH, + ExtensionIconSet::MATCH_BIGGER, + true, + base::Bind(&ImageLoaderTest::OnImageURLLoaded, base::Unretained(this))); + + // The image isn't cached, so we should not have received notification. + EXPECT_EQ(0, image_loaded_count()); + + WaitForImageLoad(); + + // We should have gotten the image. + EXPECT_EQ(1, image_loaded_count()); +} + +// Tests loading the default icon works correctly. +TEST_F(ImageLoaderTest, LoadDefaultIcon) { + ImageLoader loader(NULL); + loader.LoadExtensionIconAsync( + NULL, + -1, + ExtensionIconSet::MATCH_BIGGER, + false, + base::Bind(&ImageLoaderTest::OnImageLoaded, base::Unretained(this))); + + // The image isn't cached, so we should not have received notification. + EXPECT_EQ(0, image_loaded_count()); + + WaitForImageLoad(); + + // We should have gotten the image. + EXPECT_EQ(1, image_loaded_count()); +} + +// Tests loading the icon of an extension without icon works correctly. +TEST_F(ImageLoaderTest, LoadExtensionWithoutIcon) { + scoped_refptr<Extension> extension(CreateExtension( + "default_image_loading_tracker", Manifest::INVALID_LOCATION)); + ASSERT_TRUE(extension.get() != NULL); + + ImageLoader loader(NULL); + loader.LoadExtensionIconAsync(extension.get(), + extension_misc::EXTENSION_ICON_SMALLISH, + ExtensionIconSet::MATCH_BIGGER, + false, + base::Bind(&ImageLoaderTest::OnImageLoaded, + base::Unretained(this))); + + // The image isn't cached, so we should not have received notification. + EXPECT_EQ(0, image_loaded_count()); + + WaitForImageLoad(); + + // We should have gotten the image. + EXPECT_EQ(1, image_loaded_count()); + + // Check that the image was loaded. + EXPECT_EQ(extension_misc::EXTENSION_ICON_SMALLISH, + image_.ToSkBitmap()->width()); +} diff --git a/chrome/browser/ui/webui/extensions/extension_icon_source.cc b/chrome/browser/ui/webui/extensions/extension_icon_source.cc index 22dcf45..6d8abd4 100644 --- a/chrome/browser/ui/webui/extensions/extension_icon_source.cc +++ b/chrome/browser/ui/webui/extensions/extension_icon_source.cc @@ -39,28 +39,6 @@ namespace extensions { -namespace { - -scoped_refptr<base::RefCountedMemory> BitmapToMemory(const SkBitmap* image) { - base::RefCountedBytes* image_bytes = new base::RefCountedBytes; - gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &image_bytes->data()); - return image_bytes; -} - -SkBitmap DesaturateImage(const SkBitmap* image) { - color_utils::HSL shift = {-1, 0, 0.6}; - return SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift); -} - -SkBitmap* ToBitmap(const unsigned char* data, size_t size) { - SkBitmap* decoded = new SkBitmap(); - bool success = gfx::PNGCodec::Decode(data, size, decoded); - DCHECK(success); - return decoded; -} - -} // namespace - ExtensionIconSource::ExtensionIconSource(Profile* profile) : profile_(profile) { } @@ -93,18 +71,6 @@ GURL ExtensionIconSource::GetIconURL(const Extension* extension, return icon_url; } -// static -SkBitmap* ExtensionIconSource::LoadImageByResourceId(int resource_id) { - std::string contents = ResourceBundle::GetSharedInstance() - .GetRawDataResourceForScale(resource_id, - ui::SCALE_FACTOR_100P).as_string(); - - // Convert and return it. - const unsigned char* data = - reinterpret_cast<const unsigned char*>(contents.data()); - return ToBitmap(data, contents.length()); -} - std::string ExtensionIconSource::GetSource() const { return chrome::kChromeUIExtensionIconHost; } @@ -124,23 +90,12 @@ void ExtensionIconSource::StartDataRequest( // the request data available for later. static int next_id = 0; if (!ParseData(path, ++next_id, callback)) { - // If the request data cannot be parsed, request parameters will not be - // added to |request_map_|. - // Send back the default application icon (not resized or desaturated) as - // the default response. - callback.Run(BitmapToMemory(GetDefaultAppImage()).get()); - return; + // If the request data cannot be parsed, we will request anyway a default + // icon (not resized or desaturated). + SetData(next_id, callback, NULL, false, -1, ExtensionIconSet::MATCH_BIGGER); } - ExtensionIconRequest* request = GetData(next_id); - ExtensionResource icon = IconsInfo::GetIconResource( - request->extension, request->size, request->match); - - if (icon.relative_path().empty()) { - LoadIconFailed(next_id); - } else { - LoadExtensionImage(icon, next_id); - } + LoadExtensionImage(next_id); } ExtensionIconSource::~ExtensionIconSource() { @@ -148,125 +103,21 @@ ExtensionIconSource::~ExtensionIconSource() { STLDeleteValues(&request_map_); } -const SkBitmap* ExtensionIconSource::GetDefaultAppImage() { - if (!default_app_data_.get()) - default_app_data_.reset(LoadImageByResourceId(IDR_APP_DEFAULT_ICON)); - - return default_app_data_.get(); -} - -const SkBitmap* ExtensionIconSource::GetDefaultExtensionImage() { - if (!default_extension_data_.get()) { - default_extension_data_.reset( - LoadImageByResourceId(IDR_EXTENSION_DEFAULT_ICON)); - } - - return default_extension_data_.get(); -} - -void ExtensionIconSource::FinalizeImage(const SkBitmap* image, - int request_id) { - SkBitmap bitmap; - ExtensionIconRequest* request = GetData(request_id); - if (request->grayscale) - bitmap = DesaturateImage(image); - else - bitmap = *image; - - request->callback.Run(BitmapToMemory(&bitmap).get()); - ClearData(request_id); -} - -void ExtensionIconSource::LoadDefaultImage(int request_id) { - ExtensionIconRequest* request = GetData(request_id); - const SkBitmap* default_image = NULL; - - if (request->extension->is_app()) - default_image = GetDefaultAppImage(); - else - default_image = GetDefaultExtensionImage(); - - SkBitmap resized_image(skia::ImageOperations::Resize( - *default_image, skia::ImageOperations::RESIZE_LANCZOS3, - request->size, request->size)); - - // There are cases where Resize returns an empty bitmap, for example if you - // ask for an image too large. In this case it is better to return the default - // image than returning nothing at all. - if (resized_image.empty()) - resized_image = *default_image; - - FinalizeImage(&resized_image, request_id); -} - -void ExtensionIconSource::LoadExtensionImage(const ExtensionResource& icon, - int request_id) { - ExtensionIconRequest* request = GetData(request_id); - ImageLoader::Get(profile_)->LoadImageAsync( - request->extension, icon, - gfx::Size(request->size, request->size), - base::Bind(&ExtensionIconSource::OnImageLoaded, AsWeakPtr(), request_id)); -} - -void ExtensionIconSource::LoadFaviconImage(int request_id) { - FaviconService* favicon_service = - FaviconServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS); - // Fall back to the default icons if the service isn't available. - if (favicon_service == NULL) { - LoadDefaultImage(request_id); - return; - } - - GURL favicon_url = - AppLaunchInfo::GetFullLaunchURL(GetData(request_id)->extension); - favicon_service->GetRawFaviconForURL( - FaviconService::FaviconForURLParams( - profile_, favicon_url, chrome::FAVICON, gfx::kFaviconSize), - ui::SCALE_FACTOR_100P, - base::Bind(&ExtensionIconSource::OnFaviconDataAvailable, - base::Unretained(this), request_id), - &cancelable_task_tracker_); -} - -void ExtensionIconSource::OnFaviconDataAvailable( - int request_id, - const chrome::FaviconBitmapResult& bitmap_result) { +void ExtensionIconSource::LoadExtensionImage(int request_id) { ExtensionIconRequest* request = GetData(request_id); - - // Fallback to the default icon if there wasn't a favicon. - if (!bitmap_result.is_valid()) { - LoadDefaultImage(request_id); - return; - } - - if (!request->grayscale) { - // If we don't need a grayscale image, then we can bypass FinalizeImage - // to avoid unnecessary conversions. - request->callback.Run(bitmap_result.bitmap_data.get()); - ClearData(request_id); - } else { - FinalizeImage(ToBitmap(bitmap_result.bitmap_data->front(), - bitmap_result.bitmap_data->size()), request_id); - } -} - -void ExtensionIconSource::OnImageLoaded(int request_id, - const gfx::Image& image) { - if (image.IsEmpty()) - LoadIconFailed(request_id); - else - FinalizeImage(image.ToSkBitmap(), request_id); + ImageLoader::Get(profile_)->LoadExtensionIconAsync( + request->extension, + request->size, + request->match, + request->grayscale, + base::Bind(&ExtensionIconSource::OnIconLoaded, AsWeakPtr(), request_id)); } -void ExtensionIconSource::LoadIconFailed(int request_id) { +void ExtensionIconSource::OnIconLoaded(int request_id, const gfx::Image& image) +{ ExtensionIconRequest* request = GetData(request_id); - ExtensionResource icon = IconsInfo::GetIconResource( - request->extension, request->size, request->match); - - if (request->size == extension_misc::EXTENSION_ICON_BITTY) - LoadFaviconImage(request_id); - else - LoadDefaultImage(request_id); + request->callback.Run(ImageLoader::BitmapToMemory(image.ToSkBitmap()).get()); + ClearData(request_id); } bool ExtensionIconSource::ParseData( diff --git a/chrome/browser/ui/webui/extensions/extension_icon_source.h b/chrome/browser/ui/webui/extensions/extension_icon_source.h index 7a94d11..81fe956 100644 --- a/chrome/browser/ui/webui/extensions/extension_icon_source.h +++ b/chrome/browser/ui/webui/extensions/extension_icon_source.h @@ -84,44 +84,14 @@ class ExtensionIconSource : public content::URLDataSource, virtual ~ExtensionIconSource(); - // Returns the bitmap for the default app image. - const SkBitmap* GetDefaultAppImage(); - - // Returns the bitmap for the default extension. - const SkBitmap* GetDefaultExtensionImage(); - - // Performs any remaining transformations (like desaturating the |image|), - // then returns the |image| to the client and clears up any temporary data - // associated with the |request_id|. - void FinalizeImage(const SkBitmap* image, int request_id); - - // Loads the default image for |request_id| and returns to the client. - void LoadDefaultImage(int request_id); - - // Loads the extension's |icon| for the given |request_id| and returns the + // Loads the extension's icon for the given |request_id| and returns the // image to the client. - void LoadExtensionImage(const ExtensionResource& icon, - int request_id); - - // Loads the favicon image for the app associated with the |request_id|. If - // the image does not exist, we fall back to the default image. - void LoadFaviconImage(int request_id); - - // FaviconService callback - void OnFaviconDataAvailable( - int request_id, - const chrome::FaviconBitmapResult& bitmap_result); + void LoadExtensionImage(int request_id); - // ImageLoader callback - void OnImageLoaded(int request_id, const gfx::Image& image); - - // Called when the extension doesn't have an icon. We fall back to multiple - // sources, using the following order: - // 1) The icons as listed in the extension / app manifests. - // 2) If a 16px icon and the extension has a launch URL, see if Chrome - // has a corresponding favicon. - // 3) If still no matches, load the default extension / application icon. - void LoadIconFailed(int request_id); + // LoadExtensionImage() will load the icon using + // ImageLoader::LoadExtensionIconAsync(). OnIconLoaded will be called when + // the icon has been loaded. + void OnIconLoaded(int request_id, const gfx::Image& image); // Parses and savse an ExtensionIconRequest for the URL |path| for the // specified |request_id|. @@ -146,18 +116,9 @@ class ExtensionIconSource : public content::URLDataSource, Profile* profile_; - // Maps tracker ids to request ids. - std::map<int, int> tracker_map_; - // Maps request_ids to ExtensionIconRequests. std::map<int, ExtensionIconRequest*> request_map_; - scoped_ptr<SkBitmap> default_app_data_; - - scoped_ptr<SkBitmap> default_extension_data_; - - CancelableTaskTracker cancelable_task_tracker_; - DISALLOW_COPY_AND_ASSIGN(ExtensionIconSource); }; diff --git a/chrome/test/data/extensions/default_image_loading_tracker/app.json b/chrome/test/data/extensions/default_image_loading_tracker/app.json new file mode 100644 index 0000000..23855be --- /dev/null +++ b/chrome/test/data/extensions/default_image_loading_tracker/app.json @@ -0,0 +1,5 @@ +{ + "name": "test", + "version": "1", + "manifest_version": 2 +} |