summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd12
-rw-r--r--chrome/browser/extensions/extension_icon_source_apitest.cc43
-rw-r--r--chrome/browser/extensions/extension_install_ui.cc27
-rw-r--r--chrome/browser/extensions/extension_install_ui.h11
-rw-r--r--chrome/browser/extensions/extensions_ui.cc149
-rw-r--r--chrome/browser/extensions/extensions_ui.h54
-rw-r--r--chrome/browser/profiles/profile_impl.cc5
-rw-r--r--chrome/browser/resources/new_new_tab.js3
-rw-r--r--chrome/browser/resources/ntp/apps.css5
-rw-r--r--chrome/browser/resources/ntp/apps.js59
-rw-r--r--chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.h5
-rw-r--r--chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.mm18
-rw-r--r--chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller_unittest.mm15
-rw-r--r--chrome/browser/ui/gtk/extension_install_prompt2_gtk.cc20
-rw-r--r--chrome/browser/ui/views/extensions/extension_install_prompt2.cc26
-rw-r--r--chrome/browser/ui/webui/app_launcher_handler.cc108
-rw-r--r--chrome/browser/ui/webui/app_launcher_handler.h8
-rw-r--r--chrome/browser/ui/webui/extension_icon_source.cc318
-rw-r--r--chrome/browser/ui/webui/extension_icon_source.h155
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/extensions/extension_constants.h3
-rw-r--r--chrome/common/url_constants.cc2
-rw-r--r--chrome/common/url_constants.h2
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.cc15
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_no_permission/24.pngbin0 -> 845 bytes
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_no_permission/index.html25
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_no_permission/manifest.json10
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_with_permission/128.pngbin0 -> 4347 bytes
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_with_permission/24.pngbin0 -> 845 bytes
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_with_permission/32.pngbin0 -> 1174 bytes
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_with_permission/index.html63
-rw-r--r--chrome/test/data/extensions/api_test/icons/extension_with_permission/manifest.json12
-rw-r--r--chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json3
-rw-r--r--chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json3
-rw-r--r--chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json3
36 files changed, 940 insertions, 245 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e25618e..2c6d513 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -3538,17 +3538,26 @@ Other platform defines such as use_titlecase are declared in build/common.gypi.
<message name="IDS_EXTENSION_UNINSTALL_PROMPT_TITLE" desc="Titlebar of the extension or app uninstallation prompt window">
Confirm Uninstallation
</message>
+ <message name="IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE" desc="Titlebar of the extension or app prompt window when re-enabling an extension that requires additional permissions">
+ Confirm Re-enable
+ </message>
<message name="IDS_EXTENSION_INSTALL_PROMPT_HEADING" desc="First bold line in the content area of the extension or app installation prompt. Asks the user if they want to install a particular extension or app.">
Install <ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>?
</message>
<message name="IDS_EXTENSION_UNINSTALL_PROMPT_HEADING" desc="First bold line in the content area of the extension or app uninstallation prompt. Asks the user if they want to uninstall a particular extension or app.">
Uninstall "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>"?
</message>
+ <message name="IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING" desc="First bold line in the content area of the extension or app installation prompt. Asks the user if they want to re-enable a particular extension or app.">
+ The newest version of "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>" has been disabled because it requires more permissions.
+ </message>
<!-- Extension/App install dialog strings -->
<message name="IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO" desc="Second line in the content area of the extension or app installation prompt. Note that the exact wording is important. This should mean that the extension or app _can_ access the listed privileges, but not necessarily that it will or needs to.">
It can access:
</message>
+ <message name="IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO" desc="Second line in the content area of the extension or app re-enable prompt. Note that the exact wording is important. This should mean that the extension _can now_ access the listed privileges, but not necessarily that it will or needs to. This message appeared because the user must approve new permissions of the extension or app.">
+ It can now access:
+ </message>
<message name="IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS" desc="Permission string for full access to the computer and all websites.">
All data on your computer and the websites you visit
</message>
@@ -3863,6 +3872,9 @@ Keep your key file in a safe place. You will need it to create new versions of y
<message name="IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON" desc="Text for the uninstall button on the extension uninstall prompt">
Uninstall
</message>
+ <message name="IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON" desc="Text for the enable button on the extension re-enable prompt">
+ Re-enable
+ </message>
<message name="IDS_EXTENSION_GET_MORE_APPS" desc="Promo text for button to the Web Store">
Get More Apps
</message>
diff --git a/chrome/browser/extensions/extension_icon_source_apitest.cc b/chrome/browser/extensions/extension_icon_source_apitest.cc
new file mode 100644
index 0000000..ee2690c
--- /dev/null
+++ b/chrome/browser/extensions/extension_icon_source_apitest.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2011 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 "base/logging.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/test/ui_test_utils.h"
+#include "content/browser/tab_contents/tab_contents.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/mock_host_resolver.h"
+
+class ExtensionIconSourceTest : public ExtensionApiTest {
+};
+
+IN_PROC_BROWSER_TEST_F(ExtensionIconSourceTest, IconsLoaded) {
+ FilePath basedir = test_data_dir_.AppendASCII("icons");
+ ASSERT_TRUE(LoadExtension(basedir.AppendASCII("extension_with_permission")));
+ ASSERT_TRUE(LoadExtension(basedir.AppendASCII("extension_no_permission")));
+ std::string result;
+
+ // Test that the icons are loaded and that the chrome://extension-icon
+ // parameters work correctly.
+ ui_test_utils::NavigateToURL(
+ browser(),
+ GURL("chrome-extension://gbmgkahjioeacddebbnengilkgbkhodg/index.html"));
+ ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractString(
+ browser()->GetSelectedTabContents()->render_view_host(), L"",
+ L"window.domAutomationController.send(document.title)",
+ &result));
+ EXPECT_EQ(result, "Loaded");
+
+ // Verify that the an extension can't load chrome://extension-icon icons
+ // without the management permission.
+ ui_test_utils::NavigateToURL(
+ browser(),
+ GURL("chrome-extension://apocjbpjpkghdepdngjlknfpmabcmlao/index.html"));
+ ASSERT_TRUE(ui_test_utils::ExecuteJavaScriptAndExtractString(
+ browser()->GetSelectedTabContents()->render_view_host(), L"",
+ L"window.domAutomationController.send(document.title)",
+ &result));
+ EXPECT_EQ(result, "Not Loaded");
+}
diff --git a/chrome/browser/extensions/extension_install_ui.cc b/chrome/browser/extensions/extension_install_ui.cc
index 5635925..fdcc00a 100644
--- a/chrome/browser/extensions/extension_install_ui.cc
+++ b/chrome/browser/extensions/extension_install_ui.cc
@@ -39,17 +39,26 @@
// static
const int ExtensionInstallUI::kTitleIds[NUM_PROMPT_TYPES] = {
IDS_EXTENSION_INSTALL_PROMPT_TITLE,
- IDS_EXTENSION_UNINSTALL_PROMPT_TITLE
+ IDS_EXTENSION_UNINSTALL_PROMPT_TITLE,
+ IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE
};
// static
const int ExtensionInstallUI::kHeadingIds[NUM_PROMPT_TYPES] = {
IDS_EXTENSION_INSTALL_PROMPT_HEADING,
- IDS_EXTENSION_UNINSTALL_PROMPT_HEADING
+ IDS_EXTENSION_UNINSTALL_PROMPT_HEADING,
+ IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING
};
// static
const int ExtensionInstallUI::kButtonIds[NUM_PROMPT_TYPES] = {
IDS_EXTENSION_PROMPT_INSTALL_BUTTON,
- IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON
+ IDS_EXTENSION_PROMPT_UNINSTALL_BUTTON,
+ IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON
+};
+// static
+const int ExtensionInstallUI::kWarningIds[NUM_PROMPT_TYPES] = {
+ IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO,
+ 0, // No warning label when uninstalling.
+ IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO
};
namespace {
@@ -112,6 +121,15 @@ void ExtensionInstallUI::ConfirmUninstall(Delegate* delegate,
ShowConfirmation(UNINSTALL_PROMPT);
}
+void ExtensionInstallUI::ConfirmReEnable(Delegate* delegate,
+ const Extension* extension) {
+ DCHECK(ui_loop_ == MessageLoop::current());
+ extension_ = extension;
+ delegate_ = delegate;
+
+ ShowConfirmation(RE_ENABLE_PROMPT);
+}
+
void ExtensionInstallUI::OnInstallSuccess(const Extension* extension,
SkBitmap* icon) {
extension_ = extension;
@@ -177,6 +195,7 @@ void ExtensionInstallUI::OnImageLoaded(
SetIcon(image);
switch (prompt_type_) {
+ case RE_ENABLE_PROMPT:
case INSTALL_PROMPT: {
// TODO(jcivelli): http://crbug.com/44771 We should not show an install
// dialog when installing an app from the gallery.
@@ -187,7 +206,7 @@ void ExtensionInstallUI::OnImageLoaded(
std::vector<string16> warnings = extension_->GetPermissionMessages();
ShowExtensionInstallUIPrompt2Impl(profile_, delegate_, extension_, &icon_,
- warnings);
+ warnings, prompt_type_);
break;
}
case UNINSTALL_PROMPT: {
diff --git a/chrome/browser/extensions/extension_install_ui.h b/chrome/browser/extensions/extension_install_ui.h
index 0e41409..d332f60 100644
--- a/chrome/browser/extensions/extension_install_ui.h
+++ b/chrome/browser/extensions/extension_install_ui.h
@@ -27,6 +27,7 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer {
enum PromptType {
INSTALL_PROMPT = 0,
UNINSTALL_PROMPT,
+ RE_ENABLE_PROMPT,
NUM_PROMPT_TYPES
};
@@ -34,6 +35,7 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer {
static const int kTitleIds[NUM_PROMPT_TYPES];
static const int kHeadingIds[NUM_PROMPT_TYPES];
static const int kButtonIds[NUM_PROMPT_TYPES];
+ static const int kWarningIds[NUM_PROMPT_TYPES];
class Delegate {
public:
@@ -67,6 +69,12 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer {
// on |delegate|.
virtual void ConfirmUninstall(Delegate* delegate, const Extension* extension);
+ // This is called by the app handler launcher to verify whether the app
+ // should be re-enabled. This is declared virtual for testing.
+ //
+ // We *MUST* eventually call either Proceed() or Abort() on |delegate|.
+ virtual void ConfirmReEnable(Delegate* delegate, const Extension* extension);
+
// Installation was successful. This is declared virtual for testing.
virtual void OnInstallSuccess(const Extension* extension, SkBitmap* icon);
@@ -113,7 +121,8 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer {
// this function are platform-specific.
static void ShowExtensionInstallUIPrompt2Impl(
Profile* profile, Delegate* delegate, const Extension* extension,
- SkBitmap* icon, const std::vector<string16>& permissions);
+ SkBitmap* icon, const std::vector<string16>& permissions,
+ PromptType type);
Profile* profile_;
MessageLoop* ui_loop_;
diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc
index 73db0d8..1b3b080 100644
--- a/chrome/browser/extensions/extensions_ui.cc
+++ b/chrome/browser/extensions/extensions_ui.cc
@@ -30,6 +30,7 @@
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/background_contents.h"
+#include "chrome/browser/ui/webui/extension_icon_source.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_icon_set.h"
#include "chrome/common/extensions/user_script.h"
@@ -44,6 +45,7 @@
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_view.h"
+#include "googleurl/src/gurl.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
@@ -51,10 +53,6 @@
#include "net/base/net_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
-#include "ui/gfx/codec/png_codec.h"
-#include "ui/gfx/color_utils.h"
-#include "ui/gfx/skbitmap_operations.h"
-#include "webkit/glue/image_decoder.h"
namespace {
@@ -195,98 +193,6 @@ std::string ExtensionsUIHTMLSource::GetMimeType(const std::string&) const {
return "text/html";
}
-////////////////////////////////////////////////////////////////////////////////
-//
-// ExtensionsDOMHandler::IconLoader
-//
-////////////////////////////////////////////////////////////////////////////////
-
-ExtensionsDOMHandler::IconLoader::IconLoader(ExtensionsDOMHandler* handler)
- : handler_(handler) {
-}
-
-void ExtensionsDOMHandler::IconLoader::LoadIcons(
- std::vector<ExtensionResource>* icons, DictionaryValue* json) {
- BrowserThread::PostTask(
- BrowserThread::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("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, use the default icon. This is safe to do from
- // the file thread.
- // TODO(erikkay) Assuming we're going to keep showing apps in this list,
- // then we need to figure out when we should use the app default icon.
- file_contents = ResourceBundle::GetSharedInstance().GetRawDataResource(
- IDR_EXTENSION_DEFAULT_ICON).as_string();
- }
-
- // If the extension is disabled, we desaturate the icon to add to the
- // disabledness effect.
- bool enabled = false;
- CHECK(extension->GetBoolean("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;
- base::Base64Encode(file_contents, &base64_encoded);
- GURL icon_url("data:image/png;base64," + base64_encoded);
-
- extension->SetString("icon", icon_url.spec());
- }
-
- BrowserThread::PostTask(
- BrowserThread::UI, FROM_HERE,
- NewRunnableMethod(this, &IconLoader::ReportResultOnUIThread,
- json_deleter.release()));
-}
-
-void ExtensionsDOMHandler::IconLoader::ReportResultOnUIThread(
- DictionaryValue* json) {
- if (handler_)
- handler_->OnIconsLoaded(json);
-}
-
-
///////////////////////////////////////////////////////////////////////////////
//
// ExtensionsDOMHandler
@@ -297,6 +203,7 @@ ExtensionsDOMHandler::ExtensionsDOMHandler(ExtensionService* extension_service)
: extensions_service_(extension_service),
ignore_notifications_(false),
deleting_rvh_(NULL) {
+ RegisterForNotifications();
}
void ExtensionsDOMHandler::RegisterMessages() {
@@ -331,16 +238,10 @@ void ExtensionsDOMHandler::RegisterMessages() {
}
void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) {
- DictionaryValue* results = new DictionaryValue();
+ DictionaryValue results;
// 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>();
+ ListValue* extensions_list = new ListValue();
const ExtensionList* extensions = extensions_service_->extensions();
for (ExtensionList::const_iterator extension = extensions->begin();
@@ -351,7 +252,6 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) {
*extension,
GetActivePagesForExtension(*extension),
true, false)); // enabled, terminated
- extension_icons->push_back(PickExtensionIcon(*extension));
}
}
extensions = extensions_service_->disabled_extensions();
@@ -363,7 +263,6 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) {
*extension,
GetActivePagesForExtension(*extension),
false, false)); // enabled, terminated
- extension_icons->push_back(PickExtensionIcon(*extension));
}
}
extensions = extensions_service_->terminated_extensions();
@@ -376,28 +275,19 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) {
*extension,
empty_pages, // Terminated process has no active pages.
false, true)); // enabled, terminated
- extension_icons->push_back(PickExtensionIcon(*extension));
}
}
- results->Set("extensions", extensions_list);
+ results.Set("extensions", extensions_list);
bool developer_mode = web_ui_->GetProfile()->GetPrefs()
->GetBoolean(prefs::kExtensionsUIDeveloperMode);
- results->SetBoolean("developerMode", developer_mode);
+ results.SetBoolean("developerMode", developer_mode);
- if (icon_loader_.get())
- icon_loader_->Cancel();
-
- icon_loader_ = new IconLoader(this);
- icon_loader_->LoadIcons(extension_icons, results);
+ web_ui_->CallJavascriptFunction(L"returnExtensionsData", results);
}
-void ExtensionsDOMHandler::OnIconsLoaded(DictionaryValue* json) {
- web_ui_->CallJavascriptFunction(L"returnExtensionsData", *json);
- delete json;
-
+void ExtensionsDOMHandler::RegisterForNotifications() {
// Register for notifications that we need to reload the page.
- registrar_.RemoveAll();
registrar_.Add(this, NotificationType::EXTENSION_LOADED,
NotificationService::AllSources());
registrar_.Add(this, NotificationType::EXTENSION_PROCESS_CREATED,
@@ -428,12 +318,6 @@ void ExtensionsDOMHandler::OnIconsLoaded(DictionaryValue* json) {
NotificationService::AllSources());
}
-ExtensionResource ExtensionsDOMHandler::PickExtensionIcon(
- const Extension* extension) {
- return extension->GetIconResource(Extension::EXTENSION_ICON_MEDIUM,
- ExtensionIconSet::MATCH_BIGGER);
-}
-
ExtensionInstallUI* ExtensionsDOMHandler::GetExtensionInstallUI() {
if (!install_ui_.get())
install_ui_.reset(new ExtensionInstallUI(web_ui_->GetProfile()));
@@ -758,8 +642,11 @@ const Extension* ExtensionsDOMHandler::GetExtension(const ListValue* args) {
}
void ExtensionsDOMHandler::MaybeUpdateAfterNotification() {
- if (!ignore_notifications_ && web_ui_->tab_contents())
+ if (!ignore_notifications_ &&
+ web_ui_->tab_contents() &&
+ web_ui_->tab_contents()->render_view_host()) {
HandleRequestExtensionsData(NULL);
+ }
deleting_rvh_ = NULL;
}
@@ -822,11 +709,16 @@ DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue(
ExtensionService* service, const Extension* extension,
const std::vector<ExtensionPage>& pages, bool enabled, bool terminated) {
DictionaryValue* extension_data = new DictionaryValue();
-
+ GURL icon =
+ ExtensionIconSource::GetIconURL(extension,
+ Extension::EXTENSION_ICON_MEDIUM,
+ ExtensionIconSet::MATCH_BIGGER,
+ !enabled);
extension_data->SetString("id", extension->id());
extension_data->SetString("name", extension->name());
extension_data->SetString("description", extension->description());
extension_data->SetString("version", extension->version()->GetString());
+ extension_data->SetString("icon", icon.spec());
extension_data->SetBoolean("enabled", enabled);
extension_data->SetBoolean("terminated", terminated);
extension_data->SetBoolean("enabledIncognito",
@@ -958,8 +850,7 @@ ExtensionsDOMHandler::~ExtensionsDOMHandler() {
if (pack_job_.get())
pack_job_->ClearClient();
- if (icon_loader_.get())
- icon_loader_->Cancel();
+ registrar_.RemoveAll();
}
// ExtensionsDOMHandler, public: -----------------------------------------------
diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h
index b7f897e3..029840e 100644
--- a/chrome/browser/extensions/extensions_ui.h
+++ b/chrome/browser/extensions/extensions_ui.h
@@ -68,40 +68,6 @@ class ExtensionsDOMHandler
public ExtensionInstallUI::Delegate {
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(ExtensionService* extension_service);
virtual ~ExtensionsDOMHandler();
@@ -185,6 +151,9 @@ class ExtensionsDOMHandler
// Forces a UI update if appropriate after a notification is received.
void MaybeUpdateAfterNotification();
+ // Register for notifications that we need to reload the page.
+ void RegisterForNotifications();
+
// SelectFileDialog::Listener
virtual void FileSelected(const FilePath& path,
int index, void* params);
@@ -205,20 +174,6 @@ class ExtensionsDOMHandler
const Extension* extension,
std::vector<ExtensionPage> *result);
- // Returns the best icon to display in the UI for an extension, or an empty
- // ExtensionResource if no good icon exists.
- ExtensionResource PickExtensionIcon(const 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);
-
// Returns the ExtensionInstallUI object for this class, creating it if
// needed.
ExtensionInstallUI* GetExtensionInstallUI();
@@ -232,9 +187,6 @@ 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_;
-
// Used to show confirmation UI for uninstalling/enabling extensions in
// incognito mode.
scoped_ptr<ExtensionInstallUI> install_ui_;
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 4f5ee79..22e3911 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -71,6 +71,7 @@
#include "chrome/browser/transport_security_persister.h"
#include "chrome/browser/ui/find_bar/find_bar_state.h"
#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
+#include "chrome/browser/ui/webui/extension_icon_source.h"
#include "chrome/browser/ui/webui/ntp_resource_cache.h"
#include "chrome/browser/user_style_sheet_watcher.h"
#include "chrome/browser/visitedlink/visitedlink_event_listener.h"
@@ -386,6 +387,10 @@ void ProfileImpl::InitExtensions() {
FilePath path = command_line->GetSwitchValuePath(switches::kLoadExtension);
extensions_service_->LoadExtension(path);
}
+
+ // Make the chrome://extension-icon/ resource is available.
+ ExtensionIconSource* icon_source = new ExtensionIconSource(this);
+ GetChromeURLDataManager()->AddDataSource(icon_source);
}
void ProfileImpl::RegisterComponentExtensions() {
diff --git a/chrome/browser/resources/new_new_tab.js b/chrome/browser/resources/new_new_tab.js
index 77ae168..4167f66 100644
--- a/chrome/browser/resources/new_new_tab.js
+++ b/chrome/browser/resources/new_new_tab.js
@@ -18,7 +18,8 @@ var APP_LAUNCH = {
NTP_APPS_COLLAPSED: 1,
NTP_APPS_MENU: 2,
NTP_MOST_VISITED: 3,
- NTP_RECENTLY_CLOSED: 4
+ NTP_RECENTLY_CLOSED: 4,
+ NTP_APP_RE_ENABLE: 16
};
var APP_LAUNCH_URL = {
diff --git a/chrome/browser/resources/ntp/apps.css b/chrome/browser/resources/ntp/apps.css
index 66e0411..bbd5b38 100644
--- a/chrome/browser/resources/ntp/apps.css
+++ b/chrome/browser/resources/ntp/apps.css
@@ -32,6 +32,7 @@ enough extra space in the small grid layout.
position: absolute;
height: 136px;
width: 124px; /* 920 / 7 - margin * 2 */
+ visibility: hidden;
}
.app a {
@@ -101,6 +102,10 @@ enough extra space in the small grid layout.
-webkit-transition: top .2s, left .2s, right .2s, opacity .2s;
}
+#apps-content.visible .app {
+ visibility: visible;
+}
+
@-webkit-keyframes bounce {
0% {
-webkit-transform: scale(0, 0);
diff --git a/chrome/browser/resources/ntp/apps.js b/chrome/browser/resources/ntp/apps.js
index 9f5ed91..8c15386 100644
--- a/chrome/browser/resources/ntp/apps.js
+++ b/chrome/browser/resources/ntp/apps.js
@@ -48,6 +48,12 @@ function getAppsCallback(data) {
apps.data.push('web-store-entry');
clearClosedMenu(apps.menu);
+
+ // We wait for the app icons to load before displaying them, but never wait
+ // longer than 200ms.
+ apps.loadedImages = 0;
+ apps.imageTimer = setTimeout(apps.showImages.bind(apps), 200);
+
data.apps.forEach(function(app) {
appsSectionContent.appendChild(apps.createElement(app));
});
@@ -109,6 +115,16 @@ function appsPrefChangeCallback(data) {
});
}
+// Launches the specified app using the APP_LAUNCH_NTP_APP_RE_ENABLE histogram.
+// This should only be invoked from the AppLauncherHandler.
+function launchAppAfterEnable(appId) {
+ // TODO(jstritar): We can simplify the args to
+ // [appId, APP_LAUNCH.NTP_APP_RE_ENABLE] once this CL
+ // lands: http://codereview.chromium.org/6573003/
+ chrome.send('launchApp', [appId, String(APP_LAUNCH.NTP_APP_RE_ENABLE), 0,
+ 0, 0, 0, false, false, false, false, 0]);
+}
+
var apps = (function() {
function createElement(app) {
@@ -610,6 +626,44 @@ var apps = (function() {
return rects;
},
+ get loadedImages() {
+ return this.loadedImages_;
+ },
+
+ set loadedImages(value) {
+ this.loadedImages_ = value;
+ if (this.loadedImages_ == 0)
+ return;
+
+ // Each application icon is loaded asynchronously. Here, we display
+ // the icons once they've all been loaded to make it look nicer.
+ if (this.loadedImages_ == this.data.length) {
+ this.showImages();
+ return;
+ }
+
+ // We won't actually have the visible height until the sections have
+ // been layed out.
+ if (!maxiviewVisibleHeight)
+ return;
+
+ // If we know the visible height of the maxiview, then we can don't need
+ // to wait for all the icons. Instead, we wait until the visible portion
+ // have been loaded.
+ var appsPerRow = MAX_APPS_PER_ROW[layoutMode];
+ var rows = Math.ceil(maxiviewVisibleHeight / this.dimensions.height);
+ var count = Math.min(appsPerRow * rows, this.data.length);
+ if (this.loadedImages_ == count) {
+ this.showImages();
+ return;
+ }
+ },
+
+ showImages: function() {
+ $('apps-content').classList.add('visible');
+ clearTimeout(this.imageTimer);
+ },
+
createElement: function(app) {
var div = createElement(app);
var a = div.firstChild;
@@ -639,6 +693,11 @@ var apps = (function() {
});
}
+ // CSS background images don't fire 'load' events, so we use an Image.
+ var img = new Image();
+ img.onload = function() { this.loadedImages++; }.bind(this);
+ img.src = app['icon_big'];
+
var settingsButton = div.appendChild(new cr.ui.ContextMenuButton);
settingsButton.className = 'app-settings';
settingsButton.title = localStrings.getString('appsettings');
diff --git a/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.h b/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.h
index c24878d..b110217 100644
--- a/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.h
+++ b/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.h
@@ -36,6 +36,8 @@ class Profile;
scoped_nsobject<NSString> title_;
scoped_nsobject<NSString> warnings_;
+ scoped_nsobject<NSString> button_;
+ scoped_nsobject<NSString> subtitle_;
SkBitmap icon_;
}
@@ -52,7 +54,8 @@ class Profile;
extension:(const Extension*)extension
delegate:(ExtensionInstallUI::Delegate*)delegate
icon:(SkBitmap*)bitmap
- warnings:(const std::vector<string16>&)warnings;
+ warnings:(const std::vector<string16>&)warnings
+ type:(ExtensionInstallUI::PromptType)type;
- (void)runAsModalSheet;
- (IBAction)cancel:(id)sender;
- (IBAction)ok:(id)sender;
diff --git a/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.mm b/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.mm
index 6a8fb2b..84ca937 100644
--- a/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.mm
+++ b/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller.mm
@@ -61,7 +61,8 @@ void OffsetControlVertically(NSControl* control, CGFloat amount) {
extension:(const Extension*)extension
delegate:(ExtensionInstallUI::Delegate*)delegate
icon:(SkBitmap*)icon
- warnings:(const std::vector<string16>&)warnings {
+ warnings:(const std::vector<string16>&)warnings
+ type:(ExtensionInstallUI::PromptType)type {
NSString* nibpath = nil;
// We use a different XIB in the case of no warnings, that is a little bit
@@ -83,8 +84,13 @@ void OffsetControlVertically(NSControl* control, CGFloat amount) {
delegate_ = delegate;
title_.reset(
- [l10n_util::GetNSStringF(IDS_EXTENSION_INSTALL_PROMPT_HEADING,
+ [l10n_util::GetNSStringF(ExtensionInstallUI::kHeadingIds[type],
UTF8ToUTF16(extension->name())) retain]);
+ subtitle_.reset(
+ [l10n_util::GetNSString(ExtensionInstallUI::kWarningIds[type])
+ retain]);
+ button_.reset([l10n_util::GetNSString(ExtensionInstallUI::kButtonIds[type])
+ retain]);
// We display the warnings as a simple text string, separated by newlines.
if (!warnings.empty()) {
@@ -123,6 +129,8 @@ void OffsetControlVertically(NSControl* control, CGFloat amount) {
- (void)awakeFromNib {
[titleField_ setStringValue:title_.get()];
+ [subtitleField_ setStringValue:subtitle_.get()];
+ [okButton_ setTitle:button_.get()];
NSImage* image = gfx::SkBitmapToNSImage(icon_);
[iconView_ setImage:image];
@@ -189,7 +197,8 @@ void ExtensionInstallUI::ShowExtensionInstallUIPrompt2Impl(
Delegate* delegate,
const Extension* extension,
SkBitmap* icon,
- const std::vector<string16>& warnings) {
+ const std::vector<string16>& warnings,
+ ExtensionInstallUI::PromptType type) {
Browser* browser = BrowserList::GetLastActiveWithProfile(profile);
if (!browser) {
delegate->InstallUIAbort();
@@ -211,7 +220,8 @@ void ExtensionInstallUI::ShowExtensionInstallUIPrompt2Impl(
extension:extension
delegate:delegate
icon:icon
- warnings:warnings];
+ warnings:warnings
+ type:type];
[controller runAsModalSheet];
}
diff --git a/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller_unittest.mm b/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller_unittest.mm
index 225aad6..fb393af 100644
--- a/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller_unittest.mm
+++ b/chrome/browser/ui/cocoa/extensions/extension_install_prompt_controller_unittest.mm
@@ -116,7 +116,8 @@ TEST_F(ExtensionInstallPromptControllerTest, BasicsNormalCancel) {
extension:extension_.get()
delegate:delegate.get()
icon:&icon_
- warnings:warnings]);
+ warnings:warnings
+ type:ExtensionInstallUI::INSTALL_PROMPT]);
[controller window]; // force nib load
@@ -172,7 +173,8 @@ TEST_F(ExtensionInstallPromptControllerTest, BasicsNormalOK) {
extension:extension_.get()
delegate:delegate.get()
icon:&icon_
- warnings:warnings]);
+ warnings:warnings
+ type:ExtensionInstallUI::INSTALL_PROMPT]);
[controller window]; // force nib load
[controller ok:nil];
@@ -203,7 +205,8 @@ TEST_F(ExtensionInstallPromptControllerTest, MultipleWarnings) {
extension:extension_.get()
delegate:delegate1.get()
icon:&icon_
- warnings:one_warning]);
+ warnings:one_warning
+ type:ExtensionInstallUI::INSTALL_PROMPT]);
[controller1 window]; // force nib load
@@ -214,7 +217,8 @@ TEST_F(ExtensionInstallPromptControllerTest, MultipleWarnings) {
extension:extension_.get()
delegate:delegate2.get()
icon:&icon_
- warnings:two_warnings]);
+ warnings:two_warnings
+ type:ExtensionInstallUI::INSTALL_PROMPT]);
[controller2 window]; // force nib load
@@ -256,7 +260,8 @@ TEST_F(ExtensionInstallPromptControllerTest, BasicsSkinny) {
extension:extension_.get()
delegate:delegate.get()
icon:&icon_
- warnings:warnings]);
+ warnings:warnings
+ type:ExtensionInstallUI::INSTALL_PROMPT]);
[controller window]; // force nib load
diff --git a/chrome/browser/ui/gtk/extension_install_prompt2_gtk.cc b/chrome/browser/ui/gtk/extension_install_prompt2_gtk.cc
index f2cda7d..95246b4 100644
--- a/chrome/browser/ui/gtk/extension_install_prompt2_gtk.cc
+++ b/chrome/browser/ui/gtk/extension_install_prompt2_gtk.cc
@@ -55,17 +55,19 @@ void OnDialogResponse(GtkDialog* dialog, int response_id,
void ShowInstallPromptDialog2(GtkWindow* parent, SkBitmap* skia_icon,
const Extension* extension,
ExtensionInstallUI::Delegate *delegate,
- const std::vector<string16>& permissions) {
+ const std::vector<string16>& permissions,
+ ExtensionInstallUI::PromptType type) {
// Build the dialog.
GtkWidget* dialog = gtk_dialog_new_with_buttons(
- l10n_util::GetStringUTF8(IDS_EXTENSION_INSTALL_PROMPT_TITLE).c_str(),
+ l10n_util::GetStringUTF8(ExtensionInstallUI::kTitleIds[type]).c_str(),
parent,
GTK_DIALOG_MODAL,
NULL);
GtkWidget* close_button = gtk_dialog_add_button(GTK_DIALOG(dialog),
GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE);
- gtk_dialog_add_button(GTK_DIALOG(dialog),
- l10n_util::GetStringUTF8(IDS_EXTENSION_PROMPT_INSTALL_BUTTON).c_str(),
+ gtk_dialog_add_button(
+ GTK_DIALOG(dialog),
+ l10n_util::GetStringUTF8(ExtensionInstallUI::kButtonIds[type]).c_str(),
GTK_RESPONSE_ACCEPT);
gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE);
@@ -97,9 +99,10 @@ void ShowInstallPromptDialog2(GtkWindow* parent, SkBitmap* skia_icon,
gtk_box_pack_start(GTK_BOX(icon_hbox), right_column_area, TRUE, TRUE, 0);
std::string heading_text = l10n_util::GetStringFUTF8(
- IDS_EXTENSION_INSTALL_PROMPT_HEADING, UTF8ToUTF16(extension->name()));
+ ExtensionInstallUI::kHeadingIds[type], UTF8ToUTF16(extension->name()));
GtkWidget* heading_label = MakeMarkupLabel("<span weight=\"bold\">%s</span>",
heading_text);
+ gtk_label_set_line_wrap(GTK_LABEL(heading_label), true);
gtk_misc_set_alignment(GTK_MISC(heading_label), 0.0, 0.5);
bool show_permissions = !permissions.empty();
// If we are not going to show the permissions, vertically center the title.
@@ -108,7 +111,7 @@ void ShowInstallPromptDialog2(GtkWindow* parent, SkBitmap* skia_icon,
if (show_permissions) {
GtkWidget* warning_label = gtk_label_new(l10n_util::GetStringUTF8(
- IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO).c_str());
+ ExtensionInstallUI::kWarningIds[type]).c_str());
gtk_util::SetLabelWidth(warning_label, kRightColumnMinWidth);
gtk_box_pack_start(GTK_BOX(right_column_area), warning_label,
@@ -172,7 +175,8 @@ void ExtensionInstallUI::ShowExtensionInstallUIPrompt2Impl(
Delegate* delegate,
const Extension* extension,
SkBitmap* icon,
- const std::vector<string16>& permissions) {
+ const std::vector<string16>& permissions,
+ ExtensionInstallUI::PromptType type) {
Browser* browser = BrowserList::GetLastActiveWithProfile(profile);
if (!browser) {
delegate->InstallUIAbort();
@@ -187,5 +191,5 @@ void ExtensionInstallUI::ShowExtensionInstallUIPrompt2Impl(
}
ShowInstallPromptDialog2(browser_window->window(), icon, extension,
- delegate, permissions);
+ delegate, permissions, type);
}
diff --git a/chrome/browser/ui/views/extensions/extension_install_prompt2.cc b/chrome/browser/ui/views/extensions/extension_install_prompt2.cc
index c34b68e..7187802 100644
--- a/chrome/browser/ui/views/extensions/extension_install_prompt2.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_prompt2.cc
@@ -67,7 +67,8 @@ class InstallDialogContent2
InstallDialogContent2(ExtensionInstallUI::Delegate* delegate,
const Extension* extension,
SkBitmap* icon,
- const std::vector<string16>& permissions);
+ const std::vector<string16>& permissions,
+ ExtensionInstallUI::PromptType type);
private:
// DialogDelegate overrides.
@@ -111,19 +112,25 @@ class InstallDialogContent2
// whether the extension requires any permissions.
int right_column_width_;
+ // The type of install prompt, which must be INSTALL_PROMPT or
+ // RE_ENABLE_PROMPT.
+ ExtensionInstallUI::PromptType type_;
+
DISALLOW_COPY_AND_ASSIGN(InstallDialogContent2);
};
InstallDialogContent2::InstallDialogContent2(
ExtensionInstallUI::Delegate* delegate, const Extension* extension,
- SkBitmap* icon, const std::vector<string16>& permissions)
+ SkBitmap* icon, const std::vector<string16>& permissions,
+ ExtensionInstallUI::PromptType type)
: delegate_(delegate),
icon_(NULL),
heading_(NULL),
will_have_access_to_(NULL),
permission_box_(NULL),
- right_column_width_(0) {
+ right_column_width_(0),
+ type_(type) {
// Scale down to icon size, but allow smaller icons (don't scale up).
gfx::Size size(icon->width(), icon->height());
if (size.width() > kIconSize || size.height() > kIconSize)
@@ -136,7 +143,7 @@ InstallDialogContent2::InstallDialogContent2(
AddChildView(icon_);
heading_ = new views::Label(UTF16ToWide(
- l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALL_PROMPT_HEADING,
+ l10n_util::GetStringFUTF16(ExtensionInstallUI::kHeadingIds[type_],
UTF8ToUTF16(extension->name()))));
heading_->SetFont(heading_->font().DeriveFont(kHeadingFontSizeDelta,
gfx::Font::BOLD));
@@ -149,7 +156,7 @@ InstallDialogContent2::InstallDialogContent2(
} else {
right_column_width_ = kPermissionBoxWidth;
will_have_access_to_ = new views::Label(UTF16ToWide(
- l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO)));
+ l10n_util::GetStringUTF16(ExtensionInstallUI::kWarningIds[type_])));
will_have_access_to_->SetMultiLine(true);
will_have_access_to_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
AddChildView(will_have_access_to_);
@@ -177,7 +184,7 @@ std::wstring InstallDialogContent2::GetDialogButtonLabel(
switch (button) {
case MessageBoxFlags::DIALOGBUTTON_OK:
return UTF16ToWide(
- l10n_util::GetStringUTF16(IDS_EXTENSION_PROMPT_INSTALL_BUTTON));
+ l10n_util::GetStringUTF16(ExtensionInstallUI::kButtonIds[type_]));
case MessageBoxFlags::DIALOGBUTTON_CANCEL:
return UTF16ToWide(l10n_util::GetStringUTF16(IDS_CANCEL));
default:
@@ -206,7 +213,7 @@ bool InstallDialogContent2::IsModal() const {
std::wstring InstallDialogContent2::GetWindowTitle() const {
return UTF16ToWide(
- l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALL_PROMPT_TITLE));
+ l10n_util::GetStringUTF16(ExtensionInstallUI::kTitleIds[type_]));
}
views::View* InstallDialogContent2::GetContentsView() {
@@ -303,7 +310,8 @@ void InstallDialogContent2::Layout() {
void ExtensionInstallUI::ShowExtensionInstallUIPrompt2Impl(
Profile* profile, Delegate* delegate, const Extension* extension,
SkBitmap* icon,
- const std::vector<string16>& permissions) {
+ const std::vector<string16>& permissions,
+ ExtensionInstallUI::PromptType type) {
#if defined(OS_CHROMEOS)
// Use a normal browser window as parent on ChromeOS.
Browser* browser = BrowserList::FindBrowserWithType(profile,
@@ -324,6 +332,6 @@ void ExtensionInstallUI::ShowExtensionInstallUIPrompt2Impl(
}
browser::CreateViewsWindow(window->GetNativeHandle(), gfx::Rect(),
- new InstallDialogContent2(delegate, extension, icon, permissions))
+ new InstallDialogContent2(delegate, extension, icon, permissions, type))
->Show();
}
diff --git a/chrome/browser/ui/webui/app_launcher_handler.cc b/chrome/browser/ui/webui/app_launcher_handler.cc
index ddde438..955e8f9 100644
--- a/chrome/browser/ui/webui/app_launcher_handler.cc
+++ b/chrome/browser/ui/webui/app_launcher_handler.cc
@@ -21,6 +21,7 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/webui/extension_icon_source.h"
#include "chrome/browser/ui/webui/shown_sections_handler.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
@@ -31,6 +32,8 @@
#include "chrome/common/url_constants.h"
#include "content/browser/disposition_utils.h"
#include "content/browser/tab_contents/tab_contents.h"
+#include "googleurl/src/gurl.h"
+#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
#include "net/base/escape.h"
#include "ui/base/animation/animation.h"
@@ -48,15 +51,6 @@ const char* kPingLaunchAppByURL = "record-app-launch-by-url";
const UnescapeRule::Type kUnescapeRules =
UnescapeRule::NORMAL | UnescapeRule::URL_SPECIAL_CHARS;
-std::string GetIconURL(const Extension* extension, Extension::Icons icon,
- const std::string& default_val) {
- GURL url = extension->GetIconURL(icon, ExtensionIconSet::MATCH_EXACTLY);
- if (!url.is_empty())
- return url.spec();
- else
- return default_val;
-}
-
extension_misc::AppLaunchBucket ParseLaunchSource(std::string launch_source) {
int bucket_num = extension_misc::APP_LAUNCH_BUCKET_INVALID;
base::StringToInt(launch_source, &bucket_num);
@@ -78,30 +72,39 @@ AppLauncherHandler::~AppLauncherHandler() {}
// static
void AppLauncherHandler::CreateAppInfo(const Extension* extension,
- ExtensionPrefs* extension_prefs,
+ ExtensionPrefs* prefs,
DictionaryValue* value) {
+ bool enabled =
+ prefs->GetExtensionState(extension->id()) != Extension::DISABLED;
+ GURL icon_big =
+ ExtensionIconSource::GetIconURL(extension,
+ Extension::EXTENSION_ICON_LARGE,
+ ExtensionIconSet::MATCH_EXACTLY,
+ !enabled);
+ GURL icon_small =
+ ExtensionIconSource::GetIconURL(extension,
+ Extension::EXTENSION_ICON_BITTY,
+ ExtensionIconSet::MATCH_BIGGER,
+ !enabled);
+
value->Clear();
value->SetString("id", extension->id());
value->SetString("name", extension->name());
value->SetString("description", extension->description());
value->SetString("launch_url", extension->GetFullLaunchURL().spec());
value->SetString("options_url", extension->options_url().spec());
- value->SetString("icon_big", GetIconURL(
- extension, Extension::EXTENSION_ICON_LARGE,
- "chrome://theme/IDR_APP_DEFAULT_ICON"));
- value->SetString("icon_small", GetIconURL(
- extension, Extension::EXTENSION_ICON_BITTY,
- std::string("chrome://favicon/") + extension->GetFullLaunchURL().spec()));
+ value->SetString("icon_big", icon_big.spec());
+ value->SetString("icon_small", icon_small.spec());
value->SetInteger("launch_container", extension->launch_container());
value->SetInteger("launch_type",
- extension_prefs->GetLaunchType(extension->id(),
+ prefs->GetLaunchType(extension->id(),
ExtensionPrefs::LAUNCH_DEFAULT));
- int app_launch_index = extension_prefs->GetAppLaunchIndex(extension->id());
+ int app_launch_index = prefs->GetAppLaunchIndex(extension->id());
if (app_launch_index == -1) {
// Make sure every app has a launch index (some predate the launch index).
- app_launch_index = extension_prefs->GetNextAppLaunchIndex();
- extension_prefs->SetAppLaunchIndex(extension->id(), app_launch_index);
+ app_launch_index = prefs->GetNextAppLaunchIndex();
+ prefs->SetAppLaunchIndex(extension->id(), app_launch_index);
}
value->SetInteger("app_launch_index", app_launch_index);
}
@@ -199,8 +202,8 @@ void AppLauncherHandler::Observe(NotificationType type,
void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) {
ListValue* list = new ListValue();
const ExtensionList* extensions = extensions_service_->extensions();
- for (ExtensionList::const_iterator it = extensions->begin();
- it != extensions->end(); ++it) {
+ ExtensionList::const_iterator it;
+ for (it = extensions->begin(); it != extensions->end(); ++it) {
// Don't include the WebStore and other component apps.
// The WebStore launcher gets special treatment in ntp/apps.js.
if ((*it)->is_app() && (*it)->location() != Extension::COMPONENT) {
@@ -209,6 +212,16 @@ void AppLauncherHandler::FillAppDictionary(DictionaryValue* dictionary) {
list->Append(app_info);
}
}
+
+ extensions = extensions_service_->disabled_extensions();
+ for (it = extensions->begin(); it != extensions->end(); ++it) {
+ if ((*it)->is_app() && (*it)->location() != Extension::COMPONENT) {
+ DictionaryValue* app_info = new DictionaryValue();
+ CreateAppInfo(*it, extensions_service_->extension_prefs(), app_info);
+ list->Append(app_info);
+ }
+ }
+
dictionary->Set("apps", list);
#if defined(OS_MACOSX)
@@ -315,7 +328,13 @@ void AppLauncherHandler::HandleLaunchApp(const ListValue* args) {
const Extension* extension =
extensions_service_->GetExtensionById(extension_id, false);
- DCHECK(extension);
+
+ // Prompt the user to re-enable the application if disabled.
+ if (!extension) {
+ PromptToEnableApp(extension_id);
+ return;
+ }
+
Profile* profile = extensions_service_->profile();
// If the user pressed special keys when clicking, override the saved
@@ -366,8 +385,8 @@ void AppLauncherHandler::HandleSetLaunchType(const ListValue* args) {
CHECK(args->GetDouble(1, &launch_type));
const Extension* extension =
- extensions_service_->GetExtensionById(extension_id, false);
- DCHECK(extension);
+ extensions_service_->GetExtensionById(extension_id, true);
+ CHECK(extension);
extensions_service_->extension_prefs()->SetLaunchType(
extension_id,
@@ -386,6 +405,7 @@ void AppLauncherHandler::HandleUninstallApp(const ListValue* args) {
return; // Only one prompt at a time.
extension_id_prompting_ = extension_id;
+ extension_prompt_type_ = ExtensionInstallUI::UNINSTALL_PROMPT;
GetExtensionInstallUI()->ConfirmUninstall(this, extension);
}
@@ -414,7 +434,7 @@ void AppLauncherHandler::HandleCreateAppShortcut(const ListValue* args) {
}
const Extension* extension =
- extensions_service_->GetExtensionById(extension_id, false);
+ extensions_service_->GetExtensionById(extension_id, true);
CHECK(extension);
Browser* browser = BrowserList::GetLastActive();
@@ -487,6 +507,19 @@ void AppLauncherHandler::RecordAppLaunchByURL(
extension_misc::APP_LAUNCH_BUCKET_BOUNDARY);
}
+void AppLauncherHandler::PromptToEnableApp(std::string extension_id) {
+ const Extension* extension =
+ extensions_service_->GetExtensionById(extension_id, true);
+ CHECK(extension);
+
+ if (!extension_id_prompting_.empty())
+ return; // Only one prompt at a time.
+
+ extension_id_prompting_ = extension_id;
+ extension_prompt_type_ = ExtensionInstallUI::RE_ENABLE_PROMPT;
+ GetExtensionInstallUI()->ConfirmReEnable(this, extension);
+}
+
void AppLauncherHandler::InstallUIProceed() {
DCHECK(!extension_id_prompting_.empty());
@@ -497,8 +530,27 @@ void AppLauncherHandler::InstallUIProceed() {
if (!extension)
return;
- extensions_service_->UninstallExtension(extension_id_prompting_,
- false /* external_uninstall */);
+ switch (extension_prompt_type_) {
+ case ExtensionInstallUI::UNINSTALL_PROMPT:
+ extensions_service_->UninstallExtension(extension_id_prompting_,
+ false /* external_uninstall */);
+ break;
+ case ExtensionInstallUI::RE_ENABLE_PROMPT: {
+ extensions_service_->GrantPermissionsAndEnableExtension(extension);
+
+ // We bounce this off the NTP so the browser can update the apps icon.
+ // If we don't launch the app asynchronously, then the app's disabled
+ // icon disappears but isn't replaced by the enabled icon, making a poor
+ // visual experience.
+ StringValue* app_id = Value::CreateStringValue(extension->id());
+ web_ui_->CallJavascriptFunction(L"launchAppAfterEnable", *app_id);
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
extension_id_prompting_ = "";
}
diff --git a/chrome/browser/ui/webui/app_launcher_handler.h b/chrome/browser/ui/webui/app_launcher_handler.h
index 737199b..ba058ca 100644
--- a/chrome/browser/ui/webui/app_launcher_handler.h
+++ b/chrome/browser/ui/webui/app_launcher_handler.h
@@ -9,12 +9,12 @@
#include "base/scoped_ptr.h"
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/prefs/pref_change_registrar.h"
+#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "content/browser/webui/web_ui.h"
-class Extension;
class ExtensionPrefs;
class ExtensionService;
class NotificationRegistrar;
@@ -91,6 +91,9 @@ class AppLauncherHandler
std::string escaped_url,
extension_misc::AppLaunchBucket bucket);
+ // Prompts the user to re-enable the app for |extension_id|.
+ void PromptToEnableApp(std::string extension_id);
+
// ExtensionInstallUI::Delegate implementation, used for receiving
// notification about uninstall confirmation dialog selections.
virtual void InstallUIProceed();
@@ -120,6 +123,9 @@ class AppLauncherHandler
// The id of the extension we are prompting the user about.
std::string extension_id_prompting_;
+ // The type of prompt we are showing the user.
+ ExtensionInstallUI::PromptType extension_prompt_type_;
+
// Whether the promo is currently being shown.
bool promo_active_;
diff --git a/chrome/browser/ui/webui/extension_icon_source.cc b/chrome/browser/ui/webui/extension_icon_source.cc
new file mode 100644
index 0000000..321cd55
--- /dev/null
+++ b/chrome/browser/ui/webui/extension_icon_source.cc
@@ -0,0 +1,318 @@
+// Copyright (c) 2011 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/ui/webui/extension_icon_source.h"
+
+#include "base/callback.h"
+#include "base/ref_counted_memory.h"
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/task.h"
+#include "base/threading/thread.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_resource.h"
+#include "chrome/common/url_constants.h"
+#include "grit/theme_resources.h"
+#include "googleurl/src/gurl.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/skbitmap_operations.h"
+#include "webkit/glue/image_decoder.h"
+
+namespace {
+
+scoped_refptr<RefCountedMemory> BitmapToMemory(SkBitmap* image) {
+ std::vector<unsigned char> output;
+ gfx::PNGCodec::EncodeBGRASkBitmap(*image, false, &output);
+
+ scoped_refptr<RefCountedBytes> image_bytes(new RefCountedBytes);
+ image_bytes->data.resize(output.size());
+ std::copy(output.begin(), output.end(), image_bytes->data.begin());
+ return image_bytes;
+}
+
+void DesaturateImage(SkBitmap* image) {
+ color_utils::HSL shift = {-1, 0, 0.6};
+ *image = SkBitmapOperations::CreateHSLShiftedBitmap(*image, shift);
+}
+
+SkBitmap* ToBitmap(const unsigned char* data, size_t size) {
+ webkit_glue::ImageDecoder decoder;
+ SkBitmap* decoded = new SkBitmap();
+ *decoded = decoder.Decode(data, size);
+ return decoded;
+}
+
+SkBitmap* LoadImageByResourceId(int resource_id) {
+ std::string contents = ResourceBundle::GetSharedInstance()
+ .GetRawDataResource(resource_id).as_string();
+
+ // Convert and return it.
+ const unsigned char* data =
+ reinterpret_cast<const unsigned char*>(contents.data());
+ return ToBitmap(data, contents.length());
+}
+
+} // namespace
+
+
+ExtensionIconSource::ExtensionIconSource(Profile* profile)
+ : DataSource(chrome::kChromeUIExtensionIconHost, MessageLoop::current()),
+ profile_(profile),
+ next_tracker_id_(0) {
+ tracker_.reset(new ImageLoadingTracker(this));
+}
+
+struct ExtensionIconSource::ExtensionIconRequest {
+ int request_id;
+ const Extension* extension;
+ bool grayscale;
+ Extension::Icons size;
+ ExtensionIconSet::MatchType match;
+};
+
+ExtensionIconSource::~ExtensionIconSource() {
+ // Clean up all the temporary data we're holding for requests.
+ std::map<int, ExtensionIconRequest*>::iterator i;
+ for (i = request_map_.begin(); i != request_map_.end(); i++) {
+ delete i->second;
+ request_map_.erase(i);
+ }
+}
+
+// static
+GURL ExtensionIconSource::GetIconURL(const Extension* extension,
+ Extension::Icons icon_size,
+ ExtensionIconSet::MatchType match,
+ bool grayscale) {
+ GURL icon_url(base::StringPrintf("%s%s/%d/%d%s",
+ chrome::kChromeUIExtensionIconURL,
+ extension->id().c_str(),
+ icon_size,
+ match,
+ grayscale ? "?grayscale=true" : ""));
+ CHECK(icon_url.is_valid());
+ return icon_url;
+}
+
+std::string ExtensionIconSource::GetMimeType(const std::string&) const {
+ // We need to explicitly return a mime type, otherwise if the user tries to
+ // drag the image they get no extension.
+ return "image/png";
+}
+
+void ExtensionIconSource::StartDataRequest(const std::string& path,
+ bool is_off_the_record,
+ int request_id) {
+ // This is where everything gets started. First, parse the request and make
+ // the request data available for later.
+ if (!ParseData(path, request_id)) {
+ SendDefaultResponse(request_id);
+ return;
+ }
+
+ ExtensionIconRequest* request = GetData(request_id);
+ ExtensionResource icon =
+ request->extension->GetIconResource(request->size, request->match);
+
+ // 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.
+ if (!icon.relative_path().empty()) {
+ LoadExtensionImage(icon, request_id);
+ return;
+ }
+
+ if (request->size == Extension::EXTENSION_ICON_BITTY)
+ LoadFaviconImage(request_id);
+ else
+ LoadDefaultImage(request_id);
+}
+
+SkBitmap* ExtensionIconSource::GetDefaultAppImage() {
+ if (!default_app_data_.get())
+ default_app_data_.reset(LoadImageByResourceId(IDR_APP_DEFAULT_ICON));
+
+ return default_app_data_.get();
+}
+
+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(SkBitmap* image,
+ int request_id) {
+ if (GetData(request_id)->grayscale)
+ DesaturateImage(image);
+
+ ClearData(request_id);
+ SendResponse(request_id, BitmapToMemory(image));
+}
+
+void ExtensionIconSource::LoadDefaultImage(int request_id) {
+ ExtensionIconRequest* request = GetData(request_id);
+ SkBitmap* decoded = NULL;
+
+ if (request->extension->is_app())
+ decoded = GetDefaultAppImage();
+ else
+ decoded = GetDefaultExtensionImage();
+
+ *decoded = skia::ImageOperations::Resize(
+ *decoded, skia::ImageOperations::RESIZE_LANCZOS3,
+ request->size, request->size);
+
+ FinalizeImage(decoded, request_id);
+}
+
+void ExtensionIconSource::LoadExtensionImage(ExtensionResource icon,
+ int request_id) {
+ ExtensionIconRequest* request = GetData(request_id);
+ tracker_map_[next_tracker_id_++] = request_id;
+ tracker_->LoadImage(request->extension,
+ icon,
+ gfx::Size(request->size, request->size),
+ ImageLoadingTracker::DONT_CACHE);
+}
+
+void ExtensionIconSource::LoadFaviconImage(int request_id) {
+ FaviconService* favicon_service =
+ profile_->GetFaviconService(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 = GetData(request_id)->extension->GetFullLaunchURL();
+ FaviconService::Handle handle = favicon_service->GetFaviconForURL(
+ favicon_url, &cancelable_consumer_,
+ NewCallback(this, &ExtensionIconSource::OnFaviconDataAvailable));
+ cancelable_consumer_.SetClientData(favicon_service, handle, request_id);
+}
+
+void ExtensionIconSource::OnFaviconDataAvailable(
+ FaviconService::Handle request_handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedMemory> data,
+ bool expired,
+ GURL icon_url) {
+ int request_id = cancelable_consumer_.GetClientData(
+ profile_->GetFaviconService(Profile::EXPLICIT_ACCESS), request_handle);
+ ExtensionIconRequest* request = GetData(request_id);
+
+ // Fallback to the default icon if there wasn't a favicon.
+ if (!know_favicon || !data.get() || !data->size()) {
+ LoadDefaultImage(request_id);
+ return;
+ }
+
+ if (!request->grayscale) {
+ // If we don't need a grayscale image, then we can bypass FinalizeImage
+ // to avoid unnecessary conversions.
+ ClearData(request_id);
+ SendResponse(request_id, data);
+ } else {
+ FinalizeImage(ToBitmap(data->front(), data->size()), request_id);
+ }
+}
+
+void ExtensionIconSource::OnImageLoaded(SkBitmap* image,
+ ExtensionResource resource,
+ int index) {
+ int request_id = tracker_map_[index];
+ tracker_map_.erase(tracker_map_.find(index));
+ FinalizeImage(image, request_id);
+}
+
+bool ExtensionIconSource::ParseData(const std::string& path,
+ int request_id) {
+ // Extract the parameters from the path by lower casing and splitting.
+ std::string path_lower = StringToLowerASCII(path);
+ std::vector<std::string> path_parts;
+
+ base::SplitString(path_lower, '/', &path_parts);
+ if (path_lower.empty() || path_parts.size() < 3)
+ return false;
+
+ std::string size_param = path_parts.at(1);
+ std::string match_param = path_parts.at(2);
+ match_param = match_param.substr(0, match_param.find('?'));
+
+ // The icon size and match types are encoded as string representations of
+ // their enum values, so to get the enum back, we read the string as an int
+ // and then cast to the enum.
+ Extension::Icons size;
+ int size_num;
+ if (!base::StringToInt(size_param, &size_num))
+ return false;
+ size = static_cast<Extension::Icons>(size_num);
+
+ ExtensionIconSet::MatchType match_type;
+ int match_num;
+ if (!base::StringToInt(match_param, &match_num))
+ return false;
+ match_type = static_cast<ExtensionIconSet::MatchType>(match_num);
+
+ std::string extension_id = path_parts.at(0);
+ const Extension* extension =
+ profile_->GetExtensionService()->GetExtensionById(extension_id, true);
+ if (!extension)
+ return false;
+
+ bool grayscale = path_lower.find("grayscale=true") != std::string::npos;
+
+ SetData(request_id, extension, grayscale, size, match_type);
+
+ return true;
+}
+
+void ExtensionIconSource::SendDefaultResponse(int request_id) {
+ // We send back the default application icon (not resized or desaturated)
+ // as the default response, like when there is no data.
+ ClearData(request_id);
+ SendResponse(request_id, BitmapToMemory(GetDefaultAppImage()));
+}
+
+void ExtensionIconSource::SetData(int request_id,
+ const Extension* extension,
+ bool grayscale,
+ Extension::Icons size,
+ ExtensionIconSet::MatchType match) {
+ ExtensionIconRequest* request = new ExtensionIconRequest();
+ request->request_id = request_id;
+ request->extension = extension;
+ request->grayscale = grayscale;
+ request->size = size;
+ request->match = match;
+ request_map_[request_id] = request;
+}
+
+ExtensionIconSource::ExtensionIconRequest* ExtensionIconSource::GetData(
+ int request_id) {
+ return request_map_[request_id];
+}
+
+void ExtensionIconSource::ClearData(int request_id) {
+ std::map<int, ExtensionIconRequest*>::iterator i =
+ request_map_.find(request_id);
+ if (i == request_map_.end())
+ return;
+
+ delete i->second;
+ request_map_.erase(i);
+}
diff --git a/chrome/browser/ui/webui/extension_icon_source.h b/chrome/browser/ui/webui/extension_icon_source.h
new file mode 100644
index 0000000..487ecb2
--- /dev/null
+++ b/chrome/browser/ui/webui/extension_icon_source.h
@@ -0,0 +1,155 @@
+// Copyright (c) 2011 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.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_EXTENSION_ICON_SOURCE_H_
+#define CHROME_BROWSER_UI_WEBUI_EXTENSION_ICON_SOURCE_H_
+#pragma once
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "chrome/browser/extensions/image_loading_tracker.h"
+#include "chrome/browser/favicon_service.h"
+#include "chrome/browser/ui/webui/chrome_url_data_manager.h"
+#include "chrome/common/extensions/extension.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+
+class ExtensionIconSet;
+class Profile;
+class RefCountedMemory;
+
+namespace gfx {
+class Size;
+}
+
+// ExtensionIconSource serves extension icons through network level chrome:
+// requests. Icons can be retrieved for any installed extension or app.
+//
+// The format for requesting an icon is as follows:
+// chrome://extension-icon/<extension_id>/<icon_size>/<match_type>?[options]
+//
+// Parameters (<> required, [] optional):
+// <extension_id> = the id of the extension
+// <icon_size> = the size of the icon, as the integer value of the
+// corresponding Extension:Icons enum.
+// <match_type> = the fallback matching policy, as the integer value of
+// the corresponding ExtensionIconSet::MatchType enum.
+// [options] = Optional transformations to apply. Supported options:
+// grayscale=true to desaturate the image.
+//
+// Examples:
+// chrome-extension://gbmgkahjioeacddebbnengilkgbkhodg/32/1?grayscale=true
+// (ICON_SMALL, MATCH_BIGGER, grayscale)
+// chrome-extension://gbmgkahjioeacddebbnengilkgbkhodg/128/0
+// (ICON_LARGE, MATCH_EXACTLY)
+//
+// We attempt to load icons from the following sources in order:
+// 1) The icons as listed in the extension / app manifests.
+// 2) If a 16px icon was requested, the favicon for extension's launch URL.
+// 3) The default extension / application icon if there are still no matches.
+//
+class ExtensionIconSource : public ChromeURLDataManager::DataSource,
+ public ImageLoadingTracker::Observer {
+ public:
+ explicit ExtensionIconSource(Profile* profile);
+ ~ExtensionIconSource();
+
+ // Gets the URL of the |extension| icon in the given |size|, falling back
+ // based on the |match| type. If |grayscale|, the URL will be for the
+ // desaturated version of the icon.
+ static GURL GetIconURL(const Extension* extension,
+ Extension::Icons icon_size,
+ ExtensionIconSet::MatchType match,
+ bool grayscale);
+
+ // ChromeURLDataManager::DataSource
+
+ virtual std::string GetMimeType(const std::string&) const;
+
+ virtual void StartDataRequest(const std::string& path,
+ bool is_off_the_record,
+ int request_id);
+
+
+ private:
+ // Encapsulates the request parameters for |request_id|.
+ struct ExtensionIconRequest;
+
+ // Returns the bitmap for the default app image.
+ SkBitmap* GetDefaultAppImage();
+
+ // Returns the bitmap for the default extension.
+ 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(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
+ // image to the client.
+ void LoadExtensionImage(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(FaviconService::Handle request_handle,
+ bool know_favicon,
+ scoped_refptr<RefCountedMemory> data,
+ bool expired,
+ GURL icon_url);
+
+ // ImageLoadingTracker::Observer
+ void OnImageLoaded(SkBitmap* image,
+ ExtensionResource resource,
+ int id);
+
+ // Parses and savse an ExtensionIconRequest for the URL |path| for the
+ // specified |request_id|.
+ bool ParseData(const std::string& path, int request_id);
+
+ // Sends the default response to |request_id|, used for invalid requests.
+ void SendDefaultResponse(int request_id);
+
+ // Stores the parameters associated with the |request_id|, making them
+ // as an ExtensionIconRequest via GetData.
+ void SetData(int request_id,
+ const Extension* extension,
+ bool grayscale,
+ Extension::Icons size,
+ ExtensionIconSet::MatchType match);
+
+ // Returns the ExtensionIconRequest for the given |request_id|.
+ ExtensionIconRequest* GetData(int request_id);
+
+ // Removes temporary data associated with |request_id|.
+ void ClearData(int request_id);
+
+ 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<ImageLoadingTracker> tracker_;
+
+ int next_tracker_id_;
+
+ scoped_ptr<SkBitmap> default_app_data_;
+
+ scoped_ptr<SkBitmap> default_extension_data_;
+
+ CancelableRequestConsumerT<int, 0> cancelable_consumer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionIconSource);
+};
+
+#endif // CHROME_BROWSER_UI_WEBUI_EXTENSION_ICON_SOURCE_H_
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 570d442..6b20d38 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -3209,6 +3209,8 @@
'browser/ui/webui/downloads_dom_handler.h',
'browser/ui/webui/downloads_ui.cc',
'browser/ui/webui/downloads_ui.h',
+ 'browser/ui/webui/extension_icon_source.cc',
+ 'browser/ui/webui/extension_icon_source.h',
'browser/ui/webui/favicon_source.cc',
'browser/ui/webui/favicon_source.h',
'browser/ui/webui/fileicon_source.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index eb7661e..651996e 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2217,6 +2217,7 @@
'browser/extensions/extension_geolocation_apitest.cc',
'browser/extensions/extension_get_views_apitest.cc',
'browser/extensions/extension_history_apitest.cc',
+ 'browser/extensions/extension_icon_source_apitest.cc',
'browser/extensions/extension_idle_apitest.cc',
'browser/extensions/extension_i18n_apitest.cc',
'browser/extensions/extension_incognito_apitest.cc',
diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h
index 5720b5f..756a888 100644
--- a/chrome/common/extensions/extension_constants.h
+++ b/chrome/common/extensions/extension_constants.h
@@ -322,6 +322,9 @@ namespace extension_misc {
// User clicked web store launcher on NTP.
APP_LAUNCH_NTP_WEBSTORE,
+ // App launched after the user re-enabled it on the NTP.
+ APP_LAUNCH_NTP_APP_RE_ENABLE,
+
APP_LAUNCH_BUCKET_BOUNDARY,
APP_LAUNCH_BUCKET_INVALID
};
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 5ac9ea1..a2aaeef 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -84,6 +84,7 @@ const char kChromeUIConstrainedHTMLTestURL[] = "chrome://constrained-test/";
const char kChromeUICrashesURL[] = "chrome://crashes/";
const char kChromeUIDevToolsURL[] = "chrome-devtools://devtools/";
const char kChromeUIDownloadsURL[] = "chrome://downloads/";
+const char kChromeUIExtensionIconURL[] = "chrome://extension-icon/";
const char kChromeUIExtensionsURL[] = "chrome://extensions/";
const char kChromeUIFavIconURL[] = "chrome://favicon/";
const char kChromeUIFlagsURL[] = "chrome://flags/";
@@ -119,6 +120,7 @@ const char kChromeUICrashesHost[] = "crashes";
const char kChromeUIDevToolsHost[] = "devtools";
const char kChromeUIDialogHost[] = "dialog";
const char kChromeUIDownloadsHost[] = "downloads";
+const char kChromeUIExtensionIconHost[] = "extension-icon";
const char kChromeUIExtensionsHost[] = "extensions";
const char kChromeUIFavIconHost[] = "favicon";
const char kChromeUIFlagsHost[] = "flags";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index 85d4731..41dd782 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -76,6 +76,7 @@ extern const char kChromeUIConstrainedHTMLTestURL[];
extern const char kChromeUICrashesURL[];
extern const char kChromeUIDevToolsURL[];
extern const char kChromeUIDownloadsURL[];
+extern const char kChromeUIExtensionIconURL[];
extern const char kChromeUIExtensionsURL[];
extern const char kChromeUIFlagsURL[];
extern const char kChromeUIFavIconURL[];
@@ -112,6 +113,7 @@ extern const char kChromeUICrashesHost[];
extern const char kChromeUIDevToolsHost[];
extern const char kChromeUIDialogHost[];
extern const char kChromeUIDownloadsHost[];
+extern const char kChromeUIExtensionIconHost[];
extern const char kChromeUIExtensionsHost[];
extern const char kChromeUIFavIconHost[];
extern const char kChromeUIFlagsHost[];
diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc
index e2e8fb3..dc2484f 100644
--- a/chrome/renderer/extensions/extension_process_bindings.cc
+++ b/chrome/renderer/extensions/extension_process_bindings.cc
@@ -571,6 +571,21 @@ void ExtensionProcessBindings::SetAPIPermissions(
PermissionsList& permissions_list = *GetPermissionsList(extension_id);
permissions_list.clear();
permissions_list.insert(permissions.begin(), permissions.end());
+
+ // The RenderViewTests set API permissions without an |extension_id|. If
+ // there's no ID, there will be no extension URL and no need to proceed.
+ if (extension_id.empty()) return;
+
+ // Grant access to chrome://extension-icon resources if they have the
+ // 'management' permission.
+ if (permissions_list.find(Extension::kManagementPermission) !=
+ permissions_list.end()) {
+ WebSecurityPolicy::addOriginAccessWhitelistEntry(
+ Extension::GetBaseURLFromExtensionId(extension_id),
+ WebKit::WebString::fromUTF8(chrome::kChromeUIScheme),
+ WebKit::WebString::fromUTF8(chrome::kChromeUIExtensionIconHost),
+ false);
+ }
}
// static
diff --git a/chrome/test/data/extensions/api_test/icons/extension_no_permission/24.png b/chrome/test/data/extensions/api_test/icons/extension_no_permission/24.png
new file mode 100644
index 0000000..79d2452
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_no_permission/24.png
Binary files differ
diff --git a/chrome/test/data/extensions/api_test/icons/extension_no_permission/index.html b/chrome/test/data/extensions/api_test/icons/extension_no_permission/index.html
new file mode 100644
index 0000000..47d3506
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_no_permission/index.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+<title>Not Loaded</title>
+<script type="text/javascript">
+
+function load(e) {
+ document.title = "Loaded";
+}
+
+function error(e) {
+ document.title = "Not Loaded";
+}
+
+</script>
+
+</head>
+<body>
+
+<img src="chrome://extension-icon/apocjbpjpkghdepdngjlknfpmabcmlao/24/0"
+ testsize="24px"
+ onload="load(event);"
+ onerror="error(event);"/>
+
+</body>
+</head>
diff --git a/chrome/test/data/extensions/api_test/icons/extension_no_permission/manifest.json b/chrome/test/data/extensions/api_test/icons/extension_no_permission/manifest.json
new file mode 100644
index 0000000..8b56002
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_no_permission/manifest.json
@@ -0,0 +1,10 @@
+{
+ "description": "Extension with no management permission",
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCk/WWuKvmfJx/Q0ldDrtXsP7JbFOPtP7rfasiAlmQoJpW4ECHpqTqj/i/E68MVLzAagp790vUeSgyJF4U0P3eHf8e6pskyrEz0+UYt6PElEIjyOnMu5JY3c6l7NdeJK5DVI0SIsspeA1HmxUQ+7CCv7A4fTCwwp1UNjQsG56feeQIDAQAB",
+ "name": "test",
+ "version": "0.1",
+ "permissions": [],
+ "icons": {
+ "24": "24.png"
+ }
+}
diff --git a/chrome/test/data/extensions/api_test/icons/extension_with_permission/128.png b/chrome/test/data/extensions/api_test/icons/extension_with_permission/128.png
new file mode 100644
index 0000000..569d785
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/128.png
Binary files differ
diff --git a/chrome/test/data/extensions/api_test/icons/extension_with_permission/24.png b/chrome/test/data/extensions/api_test/icons/extension_with_permission/24.png
new file mode 100644
index 0000000..79d2452
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/24.png
Binary files differ
diff --git a/chrome/test/data/extensions/api_test/icons/extension_with_permission/32.png b/chrome/test/data/extensions/api_test/icons/extension_with_permission/32.png
new file mode 100644
index 0000000..2828d25
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/32.png
Binary files differ
diff --git a/chrome/test/data/extensions/api_test/icons/extension_with_permission/index.html b/chrome/test/data/extensions/api_test/icons/extension_with_permission/index.html
new file mode 100644
index 0000000..9642617
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/index.html
@@ -0,0 +1,63 @@
+<script type="text/javascript">
+
+var TOTAL = 4;
+var count = 0;
+
+function load(e) {
+ if (++count < TOTAL)
+ return;
+
+ // Good. All the images have loaded. Now make sure they're the correct size.
+ var imgs = document.getElementsByTagName('img');
+ for (var x = 0; x < imgs.length; x++) {
+ var style = getComputedStyle(imgs[x]);
+ var size = imgs[x].getAttribute('testsize');
+ if (style.height != size || style.width != size) {
+ document.title = "Incorrect size on " + imgs[x].src;
+ return;
+ }
+ }
+
+ // Success!
+ document.title = "Loaded";
+}
+
+function error(e) {
+ // We failed to load an image that should have loaded.
+ document.title = "Not loaded: " + e.target.src;
+}
+
+</script>
+
+<!-- Tests loading a standard 128px icon. -->
+<img src="chrome://extension-icon/gbmgkahjioeacddebbnengilkgbkhodg/128/0"
+ testsize="128px"
+ onload="load(event);"
+ onerror="error(event);"/>
+
+<!--
+ Tests loading a standard 48px icon with a MATCH_SMALLER.
+ This should not be resized to 48px.
+-->
+<img src="chrome://extension-icon/gbmgkahjioeacddebbnengilkgbkhodg/48/2"
+ testsize="32px"
+ onload="load(event);"
+ onerror="error(event);"/>
+
+<!--
+ Tests loading a standard 32px icon, grayscale. We assume that we
+ actually got a grayscale image back here.
+-->
+<img src="chrome://extension-icon/gbmgkahjioeacddebbnengilkgbkhodg/32/1?grayscale=true"
+ testsize="32px"
+ onload="load(event);"
+ onerror="error(event);"/>
+
+<!--
+ Tests loading a 16px by resizing the 32px version (MATCH_BIGGER).
+ This should be resized to 16px.
+-->
+<img src="chrome://extension-icon/gbmgkahjioeacddebbnengilkgbkhodg/16/1"
+ testsize="16px"
+ onload="load(event);"
+ onerror="error(event);"/>
diff --git a/chrome/test/data/extensions/api_test/icons/extension_with_permission/manifest.json b/chrome/test/data/extensions/api_test/icons/extension_with_permission/manifest.json
new file mode 100644
index 0000000..e2bb86e
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/manifest.json
@@ -0,0 +1,12 @@
+{
+ "description": "Extension with management permission",
+ "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuBACa7q+sBalhnUT3lsGuuZ9PEzfoJCBoPJdCS0cfQGqAlgbBsKKqnLLgrUuK9g23PYcGTOryxnJ0eLr3Wl+gkV+CcZ4i64qfBSPt+WCTO4F9XHnqJVWRDjNY+7q0ytf8X6wdgA6ebTx4OE1t52nudNhGgYcFkRYwNAjwV5PKzQIDAQAB",
+ "name": "test",
+ "version": "0.1",
+ "permissions": [ "management" ],
+ "icons": {
+ "128": "128.png",
+ "32": "32.png",
+ "24": "24.png"
+ }
+}
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 ab9e575..43bc2f8 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
@@ -38,5 +38,6 @@
}
],
"hasPopupAction": false,
- "homepageUrl": ""
+ "homepageUrl": "",
+ "icon": "chrome://extension-icon/behllobkkfkfnphdnhnkndlbkcpglgmj/48/1"
}
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 078d343..5be7ec9 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
@@ -28,5 +28,6 @@
}
],
"hasPopupAction": false,
- "homepageUrl": ""
+ "homepageUrl": "",
+ "icon": "chrome://extension-icon/hpiknbiabeeppbpihjehijgoemciehgk/48/1"
}
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 7cfb429..62e5722 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
@@ -15,5 +15,6 @@
"content_scripts": [],
"views": [],
"hasPopupAction": false,
- "homepageUrl": ""
+ "homepageUrl": "",
+ "icon": "chrome://extension-icon/bjafgdebaacbbbecmhlhpofkepfkgcpa/48/1"
}