diff options
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 Binary files differnew file mode 100644 index 0000000..79d2452 --- /dev/null +++ b/chrome/test/data/extensions/api_test/icons/extension_no_permission/24.png 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 Binary files differnew file mode 100644 index 0000000..569d785 --- /dev/null +++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/128.png 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 Binary files differnew file mode 100644 index 0000000..79d2452 --- /dev/null +++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/24.png 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 Binary files differnew file mode 100644 index 0000000..2828d25 --- /dev/null +++ b/chrome/test/data/extensions/api_test/icons/extension_with_permission/32.png 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" } |