diff options
author | mpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-14 22:32:39 +0000 |
---|---|---|
committer | mpcomplete@chromium.org <mpcomplete@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-14 22:32:39 +0000 |
commit | 0c6da50c299be943b4c04a3953e0931734af7eaf (patch) | |
tree | b5fa120b75591c1fa56a07232cf685ac1b2a8eb8 /chrome | |
parent | a7918784c2ac95844ae775be2fef06cd3efb092c (diff) | |
download | chromium_src-0c6da50c299be943b4c04a3953e0931734af7eaf.zip chromium_src-0c6da50c299be943b4c04a3953e0931734af7eaf.tar.gz chromium_src-0c6da50c299be943b4c04a3953e0931734af7eaf.tar.bz2 |
Disable an extension when it is upgraded to a version that requires more
permissions then before, and prompt the user to re-enable.
Incidentally, this required adding support for disabling extensions.
BUG=12140
TEST=covered by unit tests
Review URL: http://codereview.chromium.org/165414
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23480 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
25 files changed, 460 insertions, 74 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 3974b03..e2026c3 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -2063,8 +2063,16 @@ each locale. --> <message name="IDS_THEME_INSTALL_INFOBAR_UNDO_BUTTON" desc="Text displayed on the button to undo a theme installation and go back to the previous theme."> Undo </message> - - <!-- Extesion install prompt --> + + <!-- Extension disabled info bar --> + <message name="IDS_EXTENSION_DISABLED_INFOBAR_LABEL" desc="Text displayed on an infobar when an extension was disabled due to a new upgrade requiring an explicit permission check from the user."> + The newest version of the extension "<ph name="EXTENSION_NAME">$1<ex>Flashblock</ex></ph>" requires more permissions, so it has been disabled. + </message> + <message name="IDS_EXTENSION_DISABLED_INFOBAR_ENABLE_BUTTON" desc="Text displayed on the button to re-enable the disabled extension."> + Reenable + </message> + + <!-- Extension install prompt --> <message name="IDS_EXTENSION_PROMPT_TITLE" desc="Titlebar of the extension installation prompt window"> Confirm Installation </message> diff --git a/chrome/browser/browser.cc b/chrome/browser/browser.cc index 8183090..5478464 100644 --- a/chrome/browser/browser.cc +++ b/chrome/browser/browser.cc @@ -23,6 +23,7 @@ #include "chrome/browser/download/download_manager.h" #include "chrome/browser/download/download_shelf.h" #include "chrome/browser/download/download_started_animation.h" +#include "chrome/browser/extensions/extension_disabled_infobar_delegate.h" #include "chrome/browser/find_bar.h" #include "chrome/browser/find_bar_controller.h" #include "chrome/browser/location_bar.h" @@ -188,6 +189,8 @@ Browser::Browser(Type type, Profile* profile) registrar_.Add(this, NotificationType::SSL_VISIBLE_STATE_CHANGED, NotificationService::AllSources()); + registrar_.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, + NotificationService::AllSources()); registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, NotificationService::AllSources()); registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, @@ -2079,6 +2082,14 @@ void Browser::Observe(NotificationType type, UpdateToolbar(false); break; + case NotificationType::EXTENSION_UPDATE_DISABLED: { + // Show the UI. + ExtensionsService* service = Source<ExtensionsService>(source).ptr(); + Extension* extension = Details<Extension>(details).ptr(); + ShowExtensionDisabledUI(service, profile_, extension); + break; + } + case NotificationType::EXTENSION_UNLOADED: { // Close any tabs from the unloaded extension. Extension* extension = Details<Extension>(details).ptr(); diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index 1165f13..385d5d7 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. aa1 --> +without changes to the corresponding grd file. mp6 --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/browser_resources.h" type="rc_header"> diff --git a/chrome/browser/extensions/extension_browsertest.cc b/chrome/browser/extensions/extension_browsertest.cc index 7ef3239..04d496a 100644 --- a/chrome/browser/extensions/extension_browsertest.cc +++ b/chrome/browser/extensions/extension_browsertest.cc @@ -42,14 +42,15 @@ void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) { bool ExtensionBrowserTest::LoadExtension(const FilePath& path) { ExtensionsService* service = browser()->profile()->GetExtensionsService(); size_t num_before = service->extensions()->size(); - registrar_.Add(this, NotificationType::EXTENSIONS_LOADED, - NotificationService::AllSources()); - service->LoadExtension(path); - MessageLoop::current()->PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, - kTimeoutMs); - ui_test_utils::RunMessageLoop(); - registrar_.Remove(this, NotificationType::EXTENSIONS_LOADED, - NotificationService::AllSources()); + { + NotificationRegistrar registrar; + registrar.Add(this, NotificationType::EXTENSIONS_LOADED, + NotificationService::AllSources()); + service->LoadExtension(path); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, new MessageLoop::QuitTask, kTimeoutMs); + ui_test_utils::RunMessageLoop(); + } size_t num_after = service->extensions()->size(); if (num_after != (num_before + 1)) return false; @@ -57,21 +58,26 @@ bool ExtensionBrowserTest::LoadExtension(const FilePath& path) { return WaitForExtensionHostsToLoad(); } -bool ExtensionBrowserTest::InstallExtension(const FilePath& path) { +bool ExtensionBrowserTest::InstallExtension(const FilePath& path, + int expected_change) { ExtensionsService* service = browser()->profile()->GetExtensionsService(); service->set_show_extensions_prompts(false); size_t num_before = service->extensions()->size(); - registrar_.Add(this, NotificationType::EXTENSIONS_LOADED, - NotificationService::AllSources()); - service->InstallExtension(path); - MessageLoop::current()->PostDelayedTask(FROM_HERE, new MessageLoop::QuitTask, - kTimeoutMs); - ui_test_utils::RunMessageLoop(); - registrar_.Remove(this, NotificationType::EXTENSIONS_LOADED, - NotificationService::AllSources()); + { + NotificationRegistrar registrar; + registrar.Add(this, NotificationType::EXTENSIONS_LOADED, + NotificationService::AllSources()); + registrar.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED, + NotificationService::AllSources()); + service->InstallExtension(path); + MessageLoop::current()->PostDelayedTask( + FROM_HERE, new MessageLoop::QuitTask, kTimeoutMs); + ui_test_utils::RunMessageLoop(); + } + size_t num_after = service->extensions()->size(); - if (num_after != (num_before + 1)) { + if (num_after != (num_before + expected_change)) { std::cout << "Num extensions before: " << IntToString(num_before) << " " << "num after: " << IntToString(num_after) << " " << "Installed extensions follow:\n"; @@ -147,14 +153,19 @@ bool ExtensionBrowserTest::WaitForExtensionHostsToLoad() { } void ExtensionBrowserTest::Observe(NotificationType type, - const NotificationSource& source, - const NotificationDetails& details) { + const NotificationSource& source, + const NotificationDetails& details) { switch (type.value) { case NotificationType::EXTENSIONS_LOADED: std::cout << "Got EXTENSION_LOADED notification.\n"; MessageLoopForUI::current()->Quit(); break; + case NotificationType::EXTENSION_UPDATE_DISABLED: + std::cout << "Got EXTENSIONS_DISABLED_LOAD notification.\n"; + MessageLoopForUI::current()->Quit(); + break; + default: NOTREACHED(); break; diff --git a/chrome/browser/extensions/extension_browsertest.h b/chrome/browser/extensions/extension_browsertest.h index 5990f5c..72b0de4 100644 --- a/chrome/browser/extensions/extension_browsertest.h +++ b/chrome/browser/extensions/extension_browsertest.h @@ -22,7 +22,11 @@ class ExtensionBrowserTest protected: virtual void SetUpCommandLine(CommandLine* command_line); bool LoadExtension(const FilePath& path); - bool InstallExtension(const FilePath& path); + // |expected_change| indicates how many extensions should be installed (or + // disabled, if negative). + // 1 means you expect a new install, 0 means you expect an upgrade, -1 means + // you expect a failed upgrade. + bool InstallExtension(const FilePath& path, int expected_change); void UninstallExtension(const std::string& extension_id); // Wait for the number of visible page actions to change to |count|. @@ -37,8 +41,6 @@ class ExtensionBrowserTest const NotificationSource& source, const NotificationDetails& details); bool WaitForExtensionHostsToLoad(); - - NotificationRegistrar registrar_; }; #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_BROWSER_TEST_H_ diff --git a/chrome/browser/extensions/extension_browsertests_misc.cc b/chrome/browser/extensions/extension_browsertests_misc.cc index ae3ece7..a630f52 100644 --- a/chrome/browser/extensions/extension_browsertests_misc.cc +++ b/chrome/browser/extensions/extension_browsertests_misc.cc @@ -151,7 +151,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, Incognito) { Browser::OpenURLOffTheRecord(browser()->profile(), GURL(chrome::kChromeUIExtensionsURL)); - ASSERT_TRUE(InstallExtension(test_data_dir_.AppendASCII("good.crx"))); + ASSERT_TRUE(InstallExtension(test_data_dir_.AppendASCII("good.crx"), 1)); UninstallExtension("ldnnhddmnhbkjipkidpdiheffobcpfmf"); } @@ -415,3 +415,49 @@ IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, MessagingContentScript) { host->render_view_host(), L"", L"testDisconnectOnClose()", &result); EXPECT_TRUE(result); } + +// Tests the process of updating an extension to one that requires higher +// permissions. +IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, UpdatePermissions) { + TabContents* contents = browser()->GetSelectedTabContents(); + ASSERT_TRUE(contents); + + // Install the initial version, which should happen just fine. + ASSERT_TRUE(InstallExtension( + test_data_dir_.AppendASCII("permissions-low-v1.crx"), 1)); + DCHECK_EQ(0, contents->infobar_delegate_count()); + + // Upgrade to a version that wants more permissions. We should disable the + // extension and prompt the user to reenable. + ASSERT_TRUE(InstallExtension( + test_data_dir_.AppendASCII("permissions-high-v2.crx"), -1)); + EXPECT_EQ(1, contents->infobar_delegate_count()); + + ExtensionsService* service = browser()->profile()->GetExtensionsService(); + EXPECT_EQ(0u, service->extensions()->size()); + ASSERT_EQ(1u, service->disabled_extensions()->size()); + + // Now try reenabling it, which should also dismiss the infobar. + service->EnableExtension(service->disabled_extensions()->at(0)->id()); + EXPECT_EQ(0, contents->infobar_delegate_count()); + EXPECT_EQ(1u, service->extensions()->size()); + EXPECT_EQ(0u, service->disabled_extensions()->size()); +} + +// Tests that we can uninstall a disabled extension. +IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, UninstallDisabled) { + // Install and upgrade, so that we have a disabled extension. + ASSERT_TRUE(InstallExtension( + test_data_dir_.AppendASCII("permissions-low-v1.crx"), 1)); + ASSERT_TRUE(InstallExtension( + test_data_dir_.AppendASCII("permissions-high-v2.crx"), -1)); + + ExtensionsService* service = browser()->profile()->GetExtensionsService(); + EXPECT_EQ(0u, service->extensions()->size()); + ASSERT_EQ(1u, service->disabled_extensions()->size()); + + // Now try uninstalling it. + UninstallExtension(service->disabled_extensions()->at(0)->id()); + EXPECT_EQ(0u, service->extensions()->size()); + EXPECT_EQ(0u, service->disabled_extensions()->size()); +} diff --git a/chrome/browser/extensions/extension_disabled_infobar_delegate.cc b/chrome/browser/extensions/extension_disabled_infobar_delegate.cc new file mode 100644 index 0000000..f812392 --- /dev/null +++ b/chrome/browser/extensions/extension_disabled_infobar_delegate.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2009 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/extensions/extension_disabled_infobar_delegate.h" + +#include "app/l10n_util.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/tab_contents/infobar_delegate.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/browser_list.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "grit/generated_resources.h" + +class ExtensionDisabledInfobarDelegate + : public ConfirmInfoBarDelegate, + public NotificationObserver { + public: + ExtensionDisabledInfobarDelegate(TabContents* tab_contents, + ExtensionsService* service, + const std::string& extension_id, + const std::string& extension_name) + : ConfirmInfoBarDelegate(tab_contents), + tab_contents_(tab_contents), + service_(service), + extension_id_(extension_id), + extension_name_(extension_name) { + // The user might re-enable the extension in other ways, so watch for that. + registrar_.Add(this, NotificationType::EXTENSIONS_LOADED, + Source<ExtensionsService>(service)); + } + virtual void InfoBarClosed() { + delete this; + } + virtual std::wstring GetMessageText() const { + return l10n_util::GetStringF(IDS_EXTENSION_DISABLED_INFOBAR_LABEL, + UTF8ToWide(extension_name_)); + } + virtual SkBitmap* GetIcon() const { + return NULL; + } + virtual int GetButtons() const { + return BUTTON_OK; + } + virtual std::wstring GetButtonLabel( + ConfirmInfoBarDelegate::InfoBarButton button) const { + return l10n_util::GetString(IDS_EXTENSION_DISABLED_INFOBAR_ENABLE_BUTTON); + } + virtual bool Accept() { + service_->EnableExtension(extension_id_); + return true; + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK(type == NotificationType::EXTENSIONS_LOADED); + ExtensionList* extensions = Details<ExtensionList>(details).ptr(); + + for (ExtensionList::iterator iter = extensions->begin(); + iter != extensions->end(); ++iter) { + if ((*iter)->id() == extension_id_) { + // TODO(mpcomplete): This doesn't seem to always result in us getting + // deleted. + tab_contents_->RemoveInfoBar(this); + return; + } + } + } + + private: + NotificationRegistrar registrar_; + TabContents* tab_contents_; + ExtensionsService* service_; + std::string extension_id_; + std::string extension_name_; +}; + +void ShowExtensionDisabledUI(ExtensionsService* service, Profile* profile, + Extension* extension) { + Browser* browser = BrowserList::GetLastActiveWithProfile(profile); + if (!browser) + return; + + TabContents* tab_contents = browser->GetSelectedTabContents(); + if (!tab_contents) + return; + + tab_contents->AddInfoBar(new ExtensionDisabledInfobarDelegate( + tab_contents, service, extension->id(), extension->name())); +} diff --git a/chrome/browser/extensions/extension_disabled_infobar_delegate.h b/chrome/browser/extensions/extension_disabled_infobar_delegate.h new file mode 100644 index 0000000..0c66ce9 --- /dev/null +++ b/chrome/browser/extensions/extension_disabled_infobar_delegate.h @@ -0,0 +1,19 @@ +// Copyright (c) 2009 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_EXTENSIONS_EXTENSION_DISABLED_INFOBAR_DELEGATE_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_DISABLED_INFOBAR_DELEGATE_H_ + +#include <string> + +class Extension; +class ExtensionsService; +class Profile; + +// Shows UI to inform the user that an extension was disabled after upgrading +// to higher permissions. +void ShowExtensionDisabledUI(ExtensionsService* service, Profile* profile, + Extension* extension); + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_DISABLED_INFOBAR_DELEGATE_H_ diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc index bc88831..84ee99d 100644 --- a/chrome/browser/extensions/extension_prefs.cc +++ b/chrome/browser/extensions/extension_prefs.cc @@ -299,7 +299,7 @@ void ExtensionPrefs::SetShelfToolstripOrder(const URLList& urls) { } void ExtensionPrefs::OnExtensionInstalled(Extension* extension) { - std::string id = extension->id(); + const std::string& id = extension->id(); UpdateExtensionPref(id, kPrefState, Value::CreateIntegerValue(Extension::ENABLED)); UpdateExtensionPref(id, kPrefLocation, @@ -326,12 +326,37 @@ void ExtensionPrefs::OnExtensionUninstalled(const Extension* extension, } } +Extension::State ExtensionPrefs::GetExtensionState( + const std::string& extension_id) { + DictionaryValue* extension = GetExtensionPref(extension_id); + + // If the extension doesn't have a pref, it's a --load-extension. + if (!extension) + return Extension::ENABLED; + + int state = -1; + if (!extension->GetInteger(kPrefState, &state) || + state < 0 || state >= Extension::NUM_STATES) { + LOG(ERROR) << "Bad or missing pref 'state' for extension '" + << extension_id << "'"; + return Extension::ENABLED; + } + return static_cast<Extension::State>(state); +} + +void ExtensionPrefs::SetExtensionState(Extension* extension, + Extension::State state) { + UpdateExtensionPref(extension->id(), kPrefState, + Value::CreateIntegerValue(state)); + prefs_->SavePersistentPrefs(); +} + bool ExtensionPrefs::UpdateExtensionPref(const std::string& extension_id, const std::wstring& key, Value* data_value) { DictionaryValue* extension = GetOrCreateExtensionPref(extension_id); if (!extension->Set(key, data_value)) { - NOTREACHED() << L"Cannot modify key: '" << key.c_str() + NOTREACHED() << "Cannot modify key: '" << key.c_str() << "' for extension: '" << extension_id.c_str() << "'"; return false; } @@ -359,3 +384,12 @@ DictionaryValue* ExtensionPrefs::GetOrCreateExtensionPref( } return extension; } + +DictionaryValue* ExtensionPrefs::GetExtensionPref( + const std::string& extension_id) { + const DictionaryValue* dict = prefs_->GetDictionary(kExtensionsPref); + DictionaryValue* extension = NULL; + std::wstring id = ASCIIToWide(extension_id); + dict->GetDictionary(id, &extension); + return extension; +} diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h index c819bcd..291556c 100644 --- a/chrome/browser/extensions/extension_prefs.h +++ b/chrome/browser/extensions/extension_prefs.h @@ -43,6 +43,12 @@ class ExtensionPrefs { void OnExtensionUninstalled(const Extension* extension, bool external_uninstall); + // Returns the state (enabled/disabled) of the given extension. + Extension::State GetExtensionState(const std::string& extension_id); + + // Called to change the extension's state when it is enabled/disabled. + void SetExtensionState(Extension* extension, Extension::State); + // Returns base extensions install directory. const FilePath& install_directory() const { return install_directory_; } @@ -73,6 +79,9 @@ class ExtensionPrefs { // Ensures and returns a mutable dictionary for extension |id|'s prefs. DictionaryValue* GetOrCreateExtensionPref(const std::string& id); + // Same as above, but returns NULL if it doesn't exist. + DictionaryValue* GetExtensionPref(const std::string& id); + // Checks if kPrefBlacklist is set to true in the DictionaryValue. // Return false if the value is false or kPrefBlacklist does not exist. // This is used to decide if an extension is blacklisted. @@ -106,6 +115,10 @@ class InstalledExtensions { // the callback. void VisitInstalledExtensions(Callback *callback); + // Same as above, but only for the given extension. + void VisitInstalledExtension(const std::string& extension_id, + Callback *callback); + private: // A copy of the extensions pref dictionary so that this can be passed // around without a dependency on prefs. diff --git a/chrome/browser/extensions/extension_ui_unittest.cc b/chrome/browser/extensions/extension_ui_unittest.cc index 022382b..f193b6a 100644 --- a/chrome/browser/extensions/extension_ui_unittest.cc +++ b/chrome/browser/extensions/extension_ui_unittest.cc @@ -44,13 +44,14 @@ namespace { EXPECT_TRUE(extension.InitFromValue(*extension_data, true, &error)); EXPECT_EQ("", error); - scoped_ptr<DictionaryValue>expected_output_data(DeserializeJSONTestData( + scoped_ptr<DictionaryValue> expected_output_data(DeserializeJSONTestData( expected_output_path, &error)); EXPECT_EQ("", error); // Produce test output. scoped_ptr<DictionaryValue> actual_output_data( - ExtensionsDOMHandler::CreateExtensionDetailValue(&extension, pages)); + ExtensionsDOMHandler::CreateExtensionDetailValue(&extension, pages, + true)); // Compare the outputs. return expected_output_data->Equals(actual_output_data.get()); diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 70fa3db..f70d7cf 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -118,7 +118,7 @@ void ExtensionsService::InstallExtension(const FilePath& extension_path) { void ExtensionsService::UpdateExtension(const std::string& id, const FilePath& extension_path) { - if (!GetExtensionById(id)) { + if (!GetExtensionByIdInternal(id, true, true)) { LOG(WARNING) << "Will not update extension " << id << " because it is not " << "installed"; return; @@ -142,7 +142,7 @@ void ExtensionsService::ReloadExtension(const std::string& extension_id) { void ExtensionsService::UninstallExtension(const std::string& extension_id, bool external_uninstall) { - Extension* extension = GetExtensionById(extension_id); + Extension* extension = GetExtensionByIdInternal(extension_id, true, true); // Callers should not send us nonexistant extensions. DCHECK(extension); @@ -159,6 +159,29 @@ void ExtensionsService::UninstallExtension(const std::string& extension_id, UnloadExtension(extension_id); } +void ExtensionsService::EnableExtension(const std::string& extension_id) { + Extension* extension = GetExtensionByIdInternal(extension_id, false, true); + if (!extension) { + NOTREACHED() << "Trying to enable an extension that isn't disabled."; + return; + } + + // Move it over to the enabled list. + extension_prefs_->SetExtensionState(extension, Extension::ENABLED); + extensions_.push_back(extension); + ExtensionList::iterator iter = std::find(disabled_extensions_.begin(), + disabled_extensions_.end(), + extension); + disabled_extensions_.erase(iter); + + ExtensionList extensions; + extensions.push_back(extension); + NotificationService::current()->Notify( + NotificationType::EXTENSIONS_LOADED, + Source<ExtensionsService>(this), + Details<ExtensionList>(&extensions)); +} + void ExtensionsService::LoadExtension(const FilePath& extension_path) { backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), &ExtensionsServiceBackend::LoadSingleExtension, @@ -213,17 +236,22 @@ void ExtensionsService::CheckForExternalUpdates() { } void ExtensionsService::UnloadExtension(const std::string& extension_id) { - Extension* extension = NULL; - ExtensionList::iterator iter; - for (iter = extensions_.begin(); iter != extensions_.end(); ++iter) { - if ((*iter)->id() == extension_id) { - extension = *iter; - break; - } - } + scoped_ptr<Extension> extension( + GetExtensionByIdInternal(extension_id, true, true)); // Callers should not send us nonexistant extensions. - CHECK(extension); + CHECK(extension.get()); + + ExtensionList::iterator iter = std::find(disabled_extensions_.begin(), + disabled_extensions_.end(), + extension.get()); + if (iter != disabled_extensions_.end()) { + // It's disabled, so don't send the unload notification. + disabled_extensions_.erase(iter); + return; + } + + iter = std::find(extensions_.begin(), extensions_.end(), extension.get()); // Remove the extension from our list. extensions_.erase(iter); @@ -231,9 +259,7 @@ void ExtensionsService::UnloadExtension(const std::string& extension_id) { // Tell other services the extension is gone. NotificationService::current()->Notify(NotificationType::EXTENSION_UNLOADED, Source<ExtensionsService>(this), - Details<Extension>(extension)); - - delete extension; + Details<Extension>(extension.get())); } void ExtensionsService::UnloadAllExtensions() { @@ -277,32 +303,56 @@ void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) { // - --load-extension // - externally installed extensions ExtensionList enabled_extensions; + ExtensionList disabled_extensions; for (ExtensionList::iterator iter = new_extensions->begin(); iter != new_extensions->end(); ++iter) { + // Extensions that get enabled get added to extensions_ and deleted later. + // Anything skipped must be deleted now so we don't leak. + scoped_ptr<Extension> extension(*iter); if (extensions_enabled() || - (*iter)->IsTheme() || - (*iter)->location() == Extension::LOAD || - Extension::IsExternalLocation((*iter)->location())) { - Extension* old = GetExtensionById((*iter)->id()); + extension->IsTheme() || + extension->location() == Extension::LOAD || + Extension::IsExternalLocation(extension->location())) { + Extension* old = GetExtensionById(extension->id()); if (old) { - if ((*iter)->version()->CompareTo(*(old->version())) > 0) { + if (extension->version()->CompareTo(*(old->version())) > 0) { + bool higher_permissions = + (extension->GetPermissionClass() > old->GetPermissionClass()); + // To upgrade an extension in place, unload the old one and // then load the new one. - // TODO(erikkay) issue 12399 UnloadExtension(old->id()); + old = NULL; + + if (higher_permissions) { + // Extension was upgraded to a high permission class. Disable it and + // notify the user. + extension_prefs_->SetExtensionState(extension.get(), + Extension::DISABLED); + NotificationService::current()->Notify( + NotificationType::EXTENSION_UPDATE_DISABLED, + Source<ExtensionsService>(this), + Details<Extension>(extension.get())); + } } else { // We already have the extension of the same or older version. LOG(WARNING) << "Duplicate extension load attempt: " << (*iter)->id(); - delete *iter; continue; } } - enabled_extensions.push_back(*iter); - extensions_.push_back(*iter); - } else { - // Extensions that get enabled get added to extensions_ and deleted later. - // Anything skipped must be deleted now so we don't leak. - delete *iter; + + switch (extension_prefs_->GetExtensionState(extension->id())) { + case Extension::ENABLED: + enabled_extensions.push_back(extension.get()); + extensions_.push_back(extension.release()); + break; + case Extension::DISABLED: + disabled_extensions.push_back(extension.get()); + disabled_extensions_.push_back(extension.release()); + break; + default: + break; + } } } @@ -346,7 +396,6 @@ void ExtensionsService::OnExtensionInstalled(Extension* extension) { OnExtensionsLoaded(list); } - void ExtensionsService::OnExtensionOverinstallAttempted(const std::string& id) { Extension* extension = GetExtensionById(id); if (extension && extension->IsTheme()) { @@ -357,12 +406,23 @@ void ExtensionsService::OnExtensionOverinstallAttempted(const std::string& id) { } } -Extension* ExtensionsService::GetExtensionById(const std::string& id) { +Extension* ExtensionsService::GetExtensionByIdInternal(const std::string& id, + bool include_enabled, + bool include_disabled) { std::string lowercase_id = StringToLowerASCII(id); - for (ExtensionList::const_iterator iter = extensions_.begin(); - iter != extensions_.end(); ++iter) { - if ((*iter)->id() == lowercase_id) - return *iter; + if (include_enabled) { + for (ExtensionList::const_iterator iter = extensions_.begin(); + iter != extensions_.end(); ++iter) { + if ((*iter)->id() == lowercase_id) + return *iter; + } + } + if (include_disabled) { + for (ExtensionList::const_iterator iter = disabled_extensions_.begin(); + iter != disabled_extensions_.end(); ++iter) { + if ((*iter)->id() == lowercase_id) + return *iter; + } } return NULL; } diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index fe0a545..fafe97e 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -87,8 +87,9 @@ class ExtensionsService virtual ~ExtensionsService(); // Gets the list of currently installed extensions. - virtual const ExtensionList* extensions() const { - return &extensions_; + virtual const ExtensionList* extensions() const { return &extensions_; } + virtual const ExtensionList* disabled_extensions() const { + return &disabled_extensions_; } const FilePath& install_directory() const { return install_directory_; } @@ -96,6 +97,11 @@ class ExtensionsService // Initialize and start all installed extensions. void Init(); + // Look up an extension by ID. + Extension* GetExtensionById(const std::string& id) { + return GetExtensionByIdInternal(id, true, false); + } + // Install the extension file at |extension_path|. Will install as an // update if an older version is already installed. // For fresh installs, this method also causes the extension to be @@ -123,6 +129,11 @@ class ExtensionsService void UninstallExtension(const std::string& extension_id, bool external_uninstall); + // Enable a previously disabled extension and reload it. The extension should + // already exist in the extension prefs. + // TODO(mpcomplete): add DisableExtension. + void EnableExtension(const std::string& extension_id); + // Load the extension from the directory |extension_path|. void LoadExtension(const FilePath& extension_path); @@ -144,9 +155,6 @@ class ExtensionsService // Scan the extension directory and clean up the cruft. void GarbageCollectExtensions(); - // Lookup an extension by |id|. - virtual Extension* GetExtensionById(const std::string& id); - // Lookup an extension by |url|. This uses the host of the URL as the id. Extension* GetExtensionByURL(const GURL& url); @@ -202,6 +210,12 @@ class ExtensionsService bool is_ready() { return ready_; } private: + // Look up an extension by ID, optionally including either or both of enabled + // and disabled extensions. + Extension* GetExtensionByIdInternal(const std::string& id, + bool include_enabled, + bool include_disabled); + // The profile this ExtensionsService is part of. Profile* profile_; @@ -214,6 +228,9 @@ class ExtensionsService // The current list of installed extensions. ExtensionList extensions_; + // The list of installed extensions that have been disabled. + ExtensionList disabled_extensions_; + // The full path to the directory where extensions are installed. FilePath install_directory_; diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc index 849fc8f..126fa05 100644 --- a/chrome/browser/extensions/extensions_ui.cc +++ b/chrome/browser/extensions/extensions_ui.cc @@ -77,6 +77,8 @@ void ExtensionsDOMHandler::RegisterMessages() { NewCallback(this, &ExtensionsDOMHandler::HandleInspectMessage)); dom_ui_->RegisterMessageCallback("reload", NewCallback(this, &ExtensionsDOMHandler::HandleReloadMessage)); + dom_ui_->RegisterMessageCallback("enable", + NewCallback(this, &ExtensionsDOMHandler::HandleEnableMessage)); dom_ui_->RegisterMessageCallback("uninstall", NewCallback(this, &ExtensionsDOMHandler::HandleUninstallMessage)); } @@ -93,7 +95,15 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const Value* value) { // themes. if (!(*extension)->IsTheme()) { extensions_list->Append(CreateExtensionDetailValue( - *extension, GetActivePagesForExtension((*extension)->id()))); + *extension, GetActivePagesForExtension((*extension)->id()), true)); + } + } + extensions = extensions_service_->disabled_extensions(); + for (ExtensionList::const_iterator extension = extensions->begin(); + extension != extensions->end(); ++extension) { + if (!(*extension)->IsTheme()) { + extensions_list->Append(CreateExtensionDetailValue( + *extension, GetActivePagesForExtension((*extension)->id()), false)); } } results.Set(L"extensions", extensions_list); @@ -132,6 +142,15 @@ void ExtensionsDOMHandler::HandleReloadMessage(const Value* value) { extensions_service_->ReloadExtension(extension_id); } +void ExtensionsDOMHandler::HandleEnableMessage(const Value* value) { + CHECK(value->IsType(Value::TYPE_LIST)); + const ListValue* list = static_cast<const ListValue*>(value); + CHECK(list->GetSize() == 1); + std::string extension_id; + CHECK(list->GetString(0, &extension_id)); + extensions_service_->EnableExtension(extension_id); +} + void ExtensionsDOMHandler::HandleUninstallMessage(const Value* value) { CHECK(value->IsType(Value::TYPE_LIST)); const ListValue* list = static_cast<const ListValue*>(value); @@ -187,13 +206,15 @@ DictionaryValue* ExtensionsDOMHandler::CreateContentScriptDetailValue( // Static DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( - const Extension *extension, const std::vector<ExtensionPage>& pages) { + const Extension *extension, const std::vector<ExtensionPage>& pages, + bool enabled) { DictionaryValue* extension_data = new DictionaryValue(); extension_data->SetString(L"id", extension->id()); extension_data->SetString(L"name", extension->name()); extension_data->SetString(L"description", extension->description()); extension_data->SetString(L"version", extension->version()->GetString()); + extension_data->SetBoolean(L"enabled", enabled); // Add list of content_script detail DictionaryValues ListValue *content_script_list = new ListValue(); diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h index c92c2ca..b63b12b 100644 --- a/chrome/browser/extensions/extensions_ui.h +++ b/chrome/browser/extensions/extensions_ui.h @@ -57,7 +57,8 @@ class ExtensionsDOMHandler : public DOMMessageHandler { // Extension Detail JSON Struct for page. (static for ease of testing). static DictionaryValue* CreateExtensionDetailValue( const Extension *extension, - const std::vector<ExtensionPage>&); + const std::vector<ExtensionPage>&, + bool enabled); // ContentScript JSON Struct for page. (static for ease of testing). static DictionaryValue* CreateContentScriptDetailValue( @@ -74,6 +75,9 @@ class ExtensionsDOMHandler : public DOMMessageHandler { // Callback for "reload" message. void HandleReloadMessage(const Value* value); + // Callback for "enable" message. + void HandleEnableMessage(const Value* value); + // Callback for "uninstall" message. void HandleUninstallMessage(const Value* value); diff --git a/chrome/browser/resources/extensions_ui.html b/chrome/browser/resources/extensions_ui.html index 8cd383c..566a989 100644 --- a/chrome/browser/resources/extensions_ui.html +++ b/chrome/browser/resources/extensions_ui.html @@ -15,6 +15,7 @@ var extensionDataFormat = { "name": "Extension Name", "description": "Extension long format description", "version": "1.0.231", + "enabled": "true", "content_scripts": [ { "js": ["script1_file1.js", "script1_file2.js"], @@ -49,6 +50,7 @@ var extensionDataFormat = { "name": "Extension Name", "description": "Extension long format description", "version": "1.0.231", + "enabled": "true", "content_scripts": [ { "js": ["script1_file1.js", "script1_file2.js"], @@ -129,6 +131,15 @@ function handleReloadExtension(node) { } /** + * Handles a 'reenable' button getting clicked. + */ +function handleEnableExtension(node) { + // Tell the C++ ExtensionDOMHandler to reload the extension. + chrome.send('enable', [node.extensionId]); + requestExtensionsData(); +} + +/** * Handles an 'uninstall' button getting clicked. */ function handleUninstallExtension(node) { @@ -247,10 +258,19 @@ th.desc { <div jsdisplay="extensions.length > 0"> <div class="extension" jsselect="extensions"> - <div class="extension-name" jscontent="name">Extension Name</div> + <div class="extension-name"> + <span jscontent="name">Extension Name</span> + <span jsdisplay="!enabled">(Disabled)</span> + </div> <div class="extension-actions"> <button jsvalues=".extensionId:id" + jsdisplay="!enabled" + onclick="handleEnableExtension(this)" + >Enable</button> + <button + jsvalues=".extensionId:id" + jsdisplay="enabled" onclick="handleReloadExtension(this)" >Reload</button> <button diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 74839ad..2f17cf7 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -1001,6 +1001,8 @@ 'browser/extensions/extension_bookmarks_module_constants.h', 'browser/extensions/extension_creator.cc', 'browser/extensions/extension_creator.h', + 'browser/extensions/extension_disabled_infobar_delegate.cc', + 'browser/extensions/extension_disabled_infobar_delegate.h', 'browser/extensions/extension_dom_ui.cc', 'browser/extensions/extension_dom_ui.h', 'browser/extensions/extension_event_names.cc', diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 5bcc456..b9761d0 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -33,9 +33,11 @@ class Extension { }; enum State { - DISABLED, + DISABLED = 0, ENABLED, KILLBIT, // Don't install/upgrade (applies to external extensions only). + + NUM_STATES }; enum InstallType { diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h index 2a04ffe..8ba2d84 100644 --- a/chrome/common/notification_type.h +++ b/chrome/common/notification_type.h @@ -603,6 +603,10 @@ class NotificationType { // Sent when new extensions are loaded. The details are an ExtensionList*. EXTENSIONS_LOADED, + // Sent when attempting to load a new extension, but they are disabled. The + // details are an Extension*. + EXTENSION_UPDATE_DISABLED, + // Sent when a new theme is installed. The details are an Extension. THEME_INSTALLED, diff --git a/chrome/test/data/extensions/permissions-high-v2.crx b/chrome/test/data/extensions/permissions-high-v2.crx Binary files differnew file mode 100755 index 0000000..3dd4959 --- /dev/null +++ b/chrome/test/data/extensions/permissions-high-v2.crx diff --git a/chrome/test/data/extensions/permissions-low-v1.crx b/chrome/test/data/extensions/permissions-low-v1.crx Binary files differnew file mode 100755 index 0000000..fbea4d0 --- /dev/null +++ b/chrome/test/data/extensions/permissions-low-v1.crx diff --git a/chrome/test/data/extensions/permissions-low-v1.pem b/chrome/test/data/extensions/permissions-low-v1.pem new file mode 100755 index 0000000..c875aca --- /dev/null +++ b/chrome/test/data/extensions/permissions-low-v1.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAIoqnmSYqIbYzFW66 +7SBmmaeWjYL4C6ik1palszZSI+ZmAmOhsWuOH7i8y1DHwUUSjzEGFlgH2RIe0xFie +Kjj/4KX0qQXbfNOpvhlk3OZGLVDgAFZoo7d/IMjR40w9myLaBTYAEO1PlKSkSyTBD +ubjo6NrO+/PKSveqRk0MyioU/AgMBAAECgYAZI/ogSdrYdphvvQxokvCaXZQCo5SO +R8zSMwufiKX4YzVT/9gsHjBvfjJLeRwq229KsU/Q63mq0LmGvlyBnct0ZNLiQcYx7 +W+aXbGcAbaUOEfWOh50iIFiP/YkDTrHrbdaE6HtgZyeiKC/o1QTcC4rxjWvXDeSAA +LHYtIPoHlhEQJBAL1qExbv/KHEsFzoy3+EozIwjcpl2yGJmmlULofroXHbReT4OPd +E9A7WNjhV6f+wxBxO3L6Bhno2dfOqTUzPWrcCQQC6vJrrTdY6DBFm7KFB36SGl1KL +nra5HUaKwzVJTaug4cK4gtliHdTnUO4UjiAkskGe316SP3r116ZNHkE3IsG5AkBNc +JLBa/iTgsDAG4Una2j1Whh+SUpf2cxBh+NGOrXUwNtAk6NmpNBLSJT+T1HN8c0b7b +oeQQJj8OQkbNoRryzdAkEAujoJjYysjmsml6x5DUbJv2f97Du2Ilpt1UjkRVxuQx2 +ioXvs6wqxHpb5OAqdR7t18cj1eYhRSgdsVBBOHXxOEQJATrAOD8w1CjUzXRJjRacs +ub+1+ED6jYYQz4fo0pkHAyTG7MgL0WP9WrvsCJJFdOcdMgGLasNvu9wr5XYuecZ3M +A== +-----END PRIVATE KEY----- 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 6c50242..91d1ca8 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 @@ -2,6 +2,7 @@ "id": "behllobkkfkfnphdnhnkndlbkcpglgmj", "version": "1.0.0.0", "name": "My extension 1", + "enabled": true, "description": "The first extension that I made.", "permissions": ["http://*.google.com/*", "https://*.google.com/*"], "content_scripts": [ diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json index 63ea47a..807b5d0 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 @@ -2,6 +2,7 @@ "id": "hpiknbiabeeppbpihjehijgoemciehgk", "version": "2", "name": "My extension 2", + "enabled": true, "description": "", "permissions": [], "content_scripts": [], 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 a444d4b..fef5a50 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 @@ -2,6 +2,7 @@ "id": "bjafgdebaacbbbecmhlhpofkepfkgcpa", "version": "1.0", "name": "My extension 3", + "enabled": true, "description": "", "permissions": [], "content_scripts": [ |