diff options
7 files changed, 194 insertions, 31 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 5bf6760..f8ce541 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2008,7 +2008,7 @@ each locale. --> </message> <message name="IDS_PROMPT_DANGEROUS_DOWNLOAD_EXTENSION" desc="Message shown to the user to validate the download of an extension file."> - Extensions and themes can harm your computer. Are you sure you want to continue? + Extensions can harm your computer. Are you sure you want to continue? </message> <message name="IDS_SAVE_DOWNLOAD" desc="Text for the button used to validate the downloading of a dangerous download."> diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc index 758f70d..67304f7 100644 --- a/chrome/browser/extensions/extensions_ui.cc +++ b/chrome/browser/extensions/extensions_ui.cc @@ -4,8 +4,12 @@ #include "chrome/browser/extensions/extensions_ui.h" +#include "app/gfx/codec/png_codec.h" +#include "app/gfx/color_utils.h" +#include "app/gfx/skbitmap_operations.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" +#include "base/file_util.h" #include "base/string_util.h" #include "base/thread.h" #include "chrome/browser/browser.h" @@ -31,11 +35,12 @@ #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "chrome/common/url_constants.h" -#include "net/base/net_util.h" - #include "grit/browser_resources.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" +#include "net/base/base64.h" +#include "net/base/net_util.h" +#include "webkit/glue/image_decoder.h" //////////////////////////////////////////////////////////////////////////////// // @@ -115,6 +120,97 @@ void ExtensionsUIHTMLSource::StartDataRequest(const std::string& path, SendResponse(request_id, html_bytes); } + +//////////////////////////////////////////////////////////////////////////////// +// +// ExtensionsDOMHandler::IconLoader +// +//////////////////////////////////////////////////////////////////////////////// + +ExtensionsDOMHandler::IconLoader::IconLoader(ExtensionsDOMHandler* handler) + : handler_(handler) { +} + +void ExtensionsDOMHandler::IconLoader::LoadIcons( + std::vector<ExtensionResource>* icons, DictionaryValue* json) { + ChromeThread::PostTask( + ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(this, + &IconLoader::LoadIconsOnFileThread, icons, json)); +} + +void ExtensionsDOMHandler::IconLoader::Cancel() { + handler_ = NULL; +} + +void ExtensionsDOMHandler::IconLoader::LoadIconsOnFileThread( + std::vector<ExtensionResource>* icons, DictionaryValue* json) { + scoped_ptr<std::vector<ExtensionResource> > icons_deleter(icons); + scoped_ptr<DictionaryValue> json_deleter(json); + + ListValue* extensions = NULL; + CHECK(json->GetList(L"extensions", &extensions)); + + for (size_t i = 0; i < icons->size(); ++i) { + DictionaryValue* extension = NULL; + CHECK(extensions->GetDictionary(static_cast<int>(i), &extension)); + + // Read the file. + std::string file_contents; + if (icons->at(i).relative_path().empty() || + !file_util::ReadFileToString(icons->at(i).GetFilePath(), + &file_contents)) { + // If there's no icon, default to the puzzle icon. This is safe to do from + // the file thread. + file_contents = ResourceBundle::GetSharedInstance().GetDataResource( + IDR_INFOBAR_PLUGIN_INSTALL); + } + + // If the extension is disabled, we desaturate the icon to add to the + // disabledness effect. + bool enabled = false; + CHECK(extension->GetBoolean(L"enabled", &enabled)); + if (!enabled) { + const unsigned char* data = + reinterpret_cast<const unsigned char*>(file_contents.data()); + webkit_glue::ImageDecoder decoder; + scoped_ptr<SkBitmap> decoded(new SkBitmap()); + *decoded = decoder.Decode(data, file_contents.length()); + + // Desaturate the icon and lighten it a bit. + color_utils::HSL shift = {-1, 0, 0.6}; + *decoded = SkBitmapOperations::CreateHSLShiftedBitmap(*decoded, shift); + + std::vector<unsigned char> output; + gfx::PNGCodec::EncodeBGRASkBitmap(*decoded, false, &output); + + // Lame, but we must make a copy of this now, because base64 doesn't take + // the same input type. + file_contents.assign(reinterpret_cast<char*>(&output.front()), + output.size()); + } + + // Create a data URL (all icons are converted to PNGs during unpacking). + std::string base64_encoded; + net::Base64Encode(file_contents, &base64_encoded); + GURL icon_url("data:image/png;base64," + base64_encoded); + + extension->SetString(L"icon", icon_url.spec()); + } + + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &IconLoader::ReportResultOnUIThread, + json_deleter.release())); +} + +void ExtensionsDOMHandler::IconLoader::ReportResultOnUIThread( + DictionaryValue* json) { + if (handler_) + handler_->OnIconsLoaded(json); +} + + /////////////////////////////////////////////////////////////////////////////// // // ExtensionsDOMHandler @@ -151,10 +247,17 @@ void ExtensionsDOMHandler::RegisterMessages() { } void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { - DictionaryValue results; + DictionaryValue* results = new DictionaryValue(); // Add the extensions to the results structure. ListValue *extensions_list = new ListValue(); + + // Stores the icon resource for each of the extensions in extensions_list. We + // build up a list of them here, then load them on the file thread in + // ::LoadIcons(). + std::vector<ExtensionResource>* extension_icons = + new std::vector<ExtensionResource>(); + const ExtensionList* extensions = extensions_service_->extensions(); for (ExtensionList::const_iterator extension = extensions->begin(); extension != extensions->end(); ++extension) { @@ -163,6 +266,7 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { if (!(*extension)->IsTheme()) { extensions_list->Append(CreateExtensionDetailValue( *extension, GetActivePagesForExtension((*extension)->id()), true)); + extension_icons->push_back(PickExtensionIcon(*extension)); } } extensions = extensions_service_->disabled_extensions(); @@ -171,15 +275,25 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { if (!(*extension)->IsTheme()) { extensions_list->Append(CreateExtensionDetailValue( *extension, GetActivePagesForExtension((*extension)->id()), false)); + extension_icons->push_back(PickExtensionIcon(*extension)); } } - results.Set(L"extensions", extensions_list); + results->Set(L"extensions", extensions_list); bool developer_mode = dom_ui_->GetProfile()->GetPrefs() ->GetBoolean(prefs::kExtensionsUIDeveloperMode); - results.SetBoolean(L"developerMode", developer_mode); + results->SetBoolean(L"developerMode", developer_mode); + + if (icon_loader_.get()) + icon_loader_->Cancel(); + + icon_loader_ = new IconLoader(this); + icon_loader_->LoadIcons(extension_icons, results); +} - dom_ui_->CallJavascriptFunction(L"returnExtensionsData", results); +void ExtensionsDOMHandler::OnIconsLoaded(DictionaryValue* json) { + dom_ui_->CallJavascriptFunction(L"returnExtensionsData", *json); + delete json; // Register for notifications that we need to reload the page. registrar_.RemoveAll(); @@ -193,6 +307,20 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { NotificationService::AllSources()); } +ExtensionResource ExtensionsDOMHandler::PickExtensionIcon( + Extension* extension) { + // Try to fetch the medium sized icon, then (if missing) go for the large one. + const std::map<int, std::string>& icons = extension->icons(); + std::map<int, std::string>::const_iterator iter = + icons.find(Extension::EXTENSION_ICON_MEDIUM); + if (iter == icons.end()) + iter = icons.find(Extension::EXTENSION_ICON_LARGE); + if (iter != icons.end()) + return extension->GetResource(iter->second); + else + return ExtensionResource(); +} + void ExtensionsDOMHandler::HandleToggleDeveloperMode(const Value* value) { bool developer_mode = dom_ui_->GetProfile()->GetPrefs() ->GetBoolean(prefs::kExtensionsUIDeveloperMode); @@ -481,17 +609,6 @@ DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( if (!extension->options_url().is_empty()) extension_data->SetString(L"options_url", extension->options_url().spec()); - // Try to fetch the medium sized icon, then (if missing) go for the large one. - const std::map<int, std::string>& icons = extension->icons(); - std::map<int, std::string>::const_iterator iter = - icons.find(Extension::EXTENSION_ICON_MEDIUM); - if (iter == icons.end()) - iter = icons.find(Extension::EXTENSION_ICON_LARGE); - if (iter != icons.end()) - extension_data->SetString(L"icon", iter->second); - else - extension_data->SetString(L"icon", ""); - // Add list of content_script detail DictionaryValues ListValue *content_script_list = new ListValue(); UserScriptList content_scripts = extension->content_scripts(); @@ -550,6 +667,9 @@ std::vector<ExtensionPage> ExtensionsDOMHandler::GetActivePagesForExtension( ExtensionsDOMHandler::~ExtensionsDOMHandler() { if (pack_job_.get()) pack_job_->ClearClient(); + + if (icon_loader_.get()) + icon_loader_->Cancel(); } // ExtensionsDOMHandler, public: ----------------------------------------------- diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h index 37e73b6..6023255 100644 --- a/chrome/browser/extensions/extensions_ui.h +++ b/chrome/browser/extensions/extensions_ui.h @@ -12,6 +12,7 @@ #include "chrome/browser/dom_ui/dom_ui.h" #include "chrome/browser/extensions/pack_extension_job.h" #include "chrome/browser/shell_dialogs.h" +#include "chrome/common/extensions/extension_resource.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" #include "googleurl/src/gurl.h" @@ -57,6 +58,41 @@ class ExtensionsDOMHandler public PackExtensionJob::Client, public SelectFileDialog::Listener { public: + + // Helper class that loads the icons for the extensions in the management UI. + // We do this with native code instead of just using chrome-extension:// URLs + // for two reasons: + // + // 1. We need to support the disabled extensions, too, and using URLs won't + // work for them. + // 2. We want to desaturate the icons of the disabled extensions to make them + // look disabled. + class IconLoader : public base::RefCountedThreadSafe<IconLoader> { + public: + explicit IconLoader(ExtensionsDOMHandler* handler); + + // Load |icons|. Will call handler->OnIconsLoaded when complete. IconLoader + // takes ownership of both arguments. + void LoadIcons(std::vector<ExtensionResource>* icons, + DictionaryValue* json); + + // Cancel the load. IconLoader won't try to call back to the handler after + // this. + void Cancel(); + + private: + // Load the icons and call ReportResultOnUIThread when done. This method + // takes ownership of both arguments. + void LoadIconsOnFileThread(std::vector<ExtensionResource>* icons, + DictionaryValue* json); + + // Report back to the handler. This method takes ownership of |json|. + void ReportResultOnUIThread(DictionaryValue* json); + + // The handler we will report back to. + ExtensionsDOMHandler* handler_; + }; + explicit ExtensionsDOMHandler(ExtensionsService* extension_service); virtual ~ExtensionsDOMHandler(); @@ -135,6 +171,20 @@ class ExtensionsDOMHandler std::vector<ExtensionPage> GetActivePagesForExtension( const std::string& extension_id); + // Returns the best icon to display in the UI for an extension, or an empty + // ExtensionResource if no good icon exists. + ExtensionResource PickExtensionIcon(Extension* extension); + + // Loads the extension resources into the json data, then calls OnIconsLoaded. + // Takes ownership of |icons|. + // Called on the file thread. + void LoadExtensionIcons(std::vector<ExtensionResource>* icons, + DictionaryValue* json_data); + + // Takes ownership of |json_data| and tells HTML about it. + // Called on the UI thread. + void OnIconsLoaded(DictionaryValue* json_data); + // Our model. scoped_refptr<ExtensionsService> extensions_service_; @@ -144,6 +194,9 @@ class ExtensionsDOMHandler // Used to package the extension. scoped_refptr<PackExtensionJob> pack_job_; + // Used to load icons asynchronously on the file thread. + scoped_refptr<IconLoader> icon_loader_; + // We monitor changes to the extension system so that we can reload when // necessary. NotificationRegistrar registrar_; diff --git a/chrome/browser/resources/extensions_ui.html b/chrome/browser/resources/extensions_ui.html index ac7e4f0..8873aa0 100644 --- a/chrome/browser/resources/extensions_ui.html +++ b/chrome/browser/resources/extensions_ui.html @@ -653,16 +653,9 @@ function autoUpdate() { <table width="100%" cellpadding="2" cellspacing="0"> <tr jsvalues=".className:enabled ? 'extension_enabled' : 'extension_disabled'"> <td width="62" height="50" align="center" valign="top"> - <span jsdisplay="icon" - ><img - jsvalues=".src:'chrome-extension://' + id + '/' + icon" - width="48" height="48" /> - </span> - <span jsdisplay="icon == ''" - ><img - width="48" height="48" - src="../../app/theme/infobar_plugin.png" /></span> - </td> + <span jsdisplay="icon"><img jsvalues=".src:icon" width="48" + height="48" /> + </td> <td valign="top"> <div> <span class="extension-name" diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json index f364e57..e038286 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json @@ -5,7 +5,6 @@ "enabled": true, "description": "The first extension that I made.", "permissions": ["http://*.google.com/*", "https://*.google.com/*"], - "icon": "icon_128.png", "order": 2, "content_scripts": [ { diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json index 18bc9d4..e3c8b9d 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json @@ -5,7 +5,6 @@ "enabled": true, "description": "", "permissions": [], - "icon": "", "order": 2, "content_scripts": [], "views": [ diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json index 3b2847a..fa011fb 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json @@ -5,7 +5,6 @@ "enabled": true, "description": "", "permissions": [], - "icon": "", "order": 2, "content_scripts": [ { |