summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
authorrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-15 18:09:41 +0000
committerrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-15 18:09:41 +0000
commit8c000882d15a528fee4e9f7ca81b9644eb8cf1c0 (patch)
tree21d4e10bbc6f87877ed96fd574e63969db58bbc4 /chrome/browser/extensions
parent10ad40dad0875a5eb9f1a87b0681728a618d8465 (diff)
downloadchromium_src-8c000882d15a528fee4e9f7ca81b9644eb8cf1c0.zip
chromium_src-8c000882d15a528fee4e9f7ca81b9644eb8cf1c0.tar.gz
chromium_src-8c000882d15a528fee4e9f7ca81b9644eb8cf1c0.tar.bz2
Cleaning Up Extensions When Local Content Removed
This fixes an issue where removing an extension's local content, e.g. deleting the user-data-dir/Default/Extensions/<id> directory, would result in a broken extension. Chrome will now uninstall/remove any extensions where the path to the extension cannot be resolved. BUG=31910 TEST=ExtensionServiceTest.CleanupInternalExtensionsMissingLocalContent; ExtensionServiceTest.CleanupUnpackedExtensionsMissingLocalContent; test by hand by loading an extension, deleting the local directory, and restarting chrome Review URL: https://chromiumcodereview.appspot.com/10541126 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@142427 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r--chrome/browser/extensions/extension_garbage_collector.cc192
-rw-r--r--chrome/browser/extensions/extension_garbage_collector.h53
-rw-r--r--chrome/browser/extensions/extension_service.cc25
-rw-r--r--chrome/browser/extensions/extension_service.h6
-rw-r--r--chrome/browser/extensions/extension_service_unittest.cc134
5 files changed, 385 insertions, 25 deletions
diff --git a/chrome/browser/extensions/extension_garbage_collector.cc b/chrome/browser/extensions/extension_garbage_collector.cc
new file mode 100644
index 0000000..933f25c
--- /dev/null
+++ b/chrome/browser/extensions/extension_garbage_collector.cc
@@ -0,0 +1,192 @@
+// Copyright (c) 2012 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_garbage_collector.h"
+
+#include <map>
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "chrome/browser/extensions/extension_prefs.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/common/extensions/extension_file_util.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+
+ExtensionGarbageCollector::ExtensionGarbageCollector(
+ ExtensionService* extension_service)
+ : extension_service_(extension_service) {
+}
+
+ExtensionGarbageCollector::~ExtensionGarbageCollector() {
+}
+
+void ExtensionGarbageCollector::GarbageCollectExtensions() {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ FilePath install_directory = extension_service_->install_directory();
+
+ // NOTE: It's important to use the file thread here because when the
+ // ExtensionService installs an extension, it begins on the file thread, and
+ // there are ordering dependencies between all of the extension-file-
+ // management tasks. If we do not follow the same thread pattern as the
+ // ExtensionService, then a race condition develops.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(
+ &ExtensionGarbageCollector::
+ CheckExtensionDirectoriesOnBackgroundThread,
+ base::Unretained(this),
+ install_directory));
+}
+
+void ExtensionGarbageCollector::CheckExtensionDirectoriesOnBackgroundThread(
+ const FilePath& install_directory) {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+
+ DVLOG(1) << "Garbage collecting extensions...";
+
+ if (!file_util::DirectoryExists(install_directory))
+ return;
+
+ std::set<FilePath> extension_paths;
+
+ file_util::FileEnumerator enumerator(install_directory,
+ false, // Not recursive.
+ file_util::FileEnumerator::DIRECTORIES);
+
+ for (FilePath extension_path = enumerator.Next(); !extension_path.empty();
+ extension_path = enumerator.Next()) {
+ std::string extension_id = extension_path.BaseName().MaybeAsASCII();
+ if (!Extension::IdIsValid(extension_id))
+ extension_id.clear();
+
+ // Delete directories that aren't valid IDs.
+ if (extension_id.empty()) {
+ DVLOG(1) << "Deleting invalid extension directory "
+ << extension_path.value() << ".";
+ file_util::Delete(extension_path, true); // Recursive.
+ continue;
+ }
+
+ extension_paths.insert(extension_path);
+ }
+
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE,
+ base::Bind(
+ &ExtensionGarbageCollector::CheckExtensionPreferences,
+ base::Unretained(this),
+ extension_paths));
+}
+
+void ExtensionGarbageCollector::CheckExtensionPreferences(
+ const std::set<FilePath>& extension_paths) {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ ExtensionPrefs* extension_prefs = extension_service_->extension_prefs();
+
+ if (extension_prefs->pref_service()->ReadOnly())
+ return;
+
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> info(
+ extension_prefs->GetInstalledExtensionsInfo());
+
+ std::map<std::string, FilePath> extension_pref_paths;
+ for (size_t i = 0; i < info->size(); ++i) {
+ extension_pref_paths[info->at(i)->extension_id] =
+ info->at(i)->extension_path;
+ }
+
+ std::set<std::string> extension_ids;
+
+ // Check each directory for a reference in the preferences.
+ for (std::set<FilePath>::const_iterator path = extension_paths.begin();
+ path != extension_paths.end();
+ ++path) {
+ std::string extension_id;
+ extension_id = path->BaseName().MaybeAsASCII();
+ if (extension_id.empty())
+ continue;
+
+ extension_ids.insert(extension_id);
+
+ std::map<std::string, FilePath>::const_iterator iter =
+ extension_pref_paths.find(extension_id);
+
+ if (iter == extension_pref_paths.end()) {
+ DVLOG(1) << "Deleting unreferenced install for directory "
+ << path->LossyDisplayName() << ".";
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(
+ &extension_file_util::DeleteFile,
+ *path,
+ true)); // recursive.
+ continue;
+ }
+
+ // Clean up old version directories.
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE, FROM_HERE,
+ base::Bind(
+ &ExtensionGarbageCollector::CleanupOldVersionsOnBackgroundThread,
+ base::Unretained(this), *path, iter->second.BaseName()));
+ }
+
+ // Check each entry in the preferences for an existing path.
+ for (std::map<std::string, FilePath>::const_iterator extension =
+ extension_pref_paths.begin();
+ extension != extension_pref_paths.end();
+ ++extension) {
+ std::set<std::string>::const_iterator iter = extension_ids.find(
+ extension->first);
+
+ if (iter != extension_ids.end())
+ continue;
+
+ std::string extension_id = extension->first;
+
+ DVLOG(1) << "Could not access local content for extension with id "
+ << extension_id << "; uninstalling extension.";
+
+ // If the extension failed to load fully (e.g. the user deleted an unpacked
+ // extension's manifest or the manifest points to the wrong path), we cannot
+ // use UninstallExtension, which relies on a valid Extension object.
+ if (!extension_service_->GetInstalledExtension(extension_id)) {
+ scoped_ptr<ExtensionInfo> info(extension_service_->extension_prefs()
+ ->GetInstalledExtensionInfo(extension_id));
+ extension_service_->extension_prefs()->OnExtensionUninstalled(
+ extension_id, info->extension_location, false);
+ } else {
+ extension_service_->UninstallExtension(extension_id, false, NULL);
+ }
+ }
+}
+
+void ExtensionGarbageCollector::CleanupOldVersionsOnBackgroundThread(
+ const FilePath& path,
+ const FilePath& current_version) {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+ file_util::FileEnumerator versions_enumerator(
+ path,
+ false, // Not recursive.
+ file_util::FileEnumerator::DIRECTORIES);
+ for (FilePath version_dir = versions_enumerator.Next();
+ !version_dir.value().empty();
+ version_dir = versions_enumerator.Next()) {
+ if (version_dir.BaseName() != current_version) {
+ DVLOG(1) << "Deleting old version for directory "
+ << version_dir.LossyDisplayName() << ".";
+ if (!file_util::Delete(version_dir, true)) // Recursive.
+ NOTREACHED();
+ }
+ }
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/extension_garbage_collector.h b/chrome/browser/extensions/extension_garbage_collector.h
new file mode 100644
index 0000000..bf75b1a
--- /dev/null
+++ b/chrome/browser/extensions/extension_garbage_collector.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 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_GARBAGE_COLLECTOR_H_
+#define CHROME_BROWSER_EXTENSIONS_EXTENSION_GARBAGE_COLLECTOR_H_
+#pragma once
+
+#include <set>
+
+#include "base/file_path.h"
+
+class ExtensionService;
+
+// Garbage collection for extensions. This will delete any directories in the
+// installation directory of |extension_service_| that aren't valid, as well
+// as removing any references in the preferences to extensions that are
+// missing local content.
+namespace extensions {
+
+class ExtensionGarbageCollector {
+ public:
+ explicit ExtensionGarbageCollector(ExtensionService* extension_service);
+ virtual ~ExtensionGarbageCollector();
+
+ // Begin garbage collection; fetch the installed extensions' ids.
+ void GarbageCollectExtensions();
+
+ private:
+ // Check the installation directory for directories with invalid extension
+ // ids, deleting them if found. Call CheckExtensionPreferences with a list of
+ // present directories.
+ void CheckExtensionDirectoriesOnBackgroundThread(
+ const FilePath& installation_directory);
+
+ // Check for any extension directories which:
+ // - Are an old version of a current extension;
+ // - Are present in the preferences but not on the file system;
+ // - Are present on the file system, but not in the preferences
+ // and delete them if found.
+ void CheckExtensionPreferences(const std::set<FilePath>& extension_paths);
+
+ // Helper function to cleanup any old versions of an extension in the
+ // extension's base directory.
+ void CleanupOldVersionsOnBackgroundThread(
+ const FilePath& path, const FilePath& current_version);
+
+ ExtensionService* extension_service_;
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_GARBAGE_COLLECTOR_H_
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 62a32a0..61aedb6 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -119,7 +119,6 @@
#include "chrome/browser/chromeos/extensions/input_method_event_router.h"
#include "chrome/browser/chromeos/extensions/media_player_event_router.h"
#include "chrome/browser/chromeos/input_method/input_method_manager.h"
-#include "chrome/browser/extensions/extension_input_ime_api.h"
#include "webkit/fileapi/file_system_context.h"
#include "webkit/fileapi/file_system_mount_point_provider.h"
#endif
@@ -325,6 +324,8 @@ ExtensionService::ExtensionService(Profile* profile,
: profile_(profile),
system_(ExtensionSystem::Get(profile)),
extension_prefs_(extension_prefs),
+ extension_garbage_collector_(
+ new extensions::ExtensionGarbageCollector(this)),
settings_frontend_(extensions::SettingsFrontend::Create(profile)),
pending_extension_manager_(*ALLOW_THIS_IN_INITIALIZER_LIST(this)),
install_directory_(install_directory),
@@ -1803,7 +1804,7 @@ void ExtensionService::UnloadExtension(
// Clean up runtime data.
extension_runtime_data_.erase(extension_id);
-if (disabled_extensions_.Contains(extension->id())) {
+ if (disabled_extensions_.Contains(extension->id())) {
UnloadedExtensionInfo details(extension, reason);
details.already_disabled = true;
disabled_extensions_.Remove(extension->id());
@@ -1845,23 +1846,7 @@ void ExtensionService::ReloadExtensions() {
}
void ExtensionService::GarbageCollectExtensions() {
- if (extension_prefs_->pref_service()->ReadOnly())
- return;
-
- scoped_ptr<ExtensionPrefs::ExtensionsInfo> info(
- extension_prefs_->GetInstalledExtensionsInfo());
-
- std::map<std::string, FilePath> extension_paths;
- for (size_t i = 0; i < info->size(); ++i)
- extension_paths[info->at(i)->extension_id] = info->at(i)->extension_path;
-
- if (!BrowserThread::PostTask(
- BrowserThread::FILE, FROM_HERE,
- base::Bind(
- &extension_file_util::GarbageCollectExtensions,
- install_directory_,
- extension_paths)))
- NOTREACHED();
+ extension_garbage_collector_->GarbageCollectExtensions();
// Also garbage-collect themes. We check |profile_| to be
// defensive; in the future, we may call GarbageCollectExtensions()
@@ -2027,7 +2012,7 @@ void ExtensionService::InitializePermissions(const Extension* extension) {
// Other than for unpacked extensions, CrxInstaller should have guaranteed
// that we aren't downgrading.
if (extension->location() != Extension::LOAD)
- CHECK(extension->version()->CompareTo(*(old->version())) >= 0);
+ CHECK_GE(extension->version()->CompareTo(*(old->version())), 0);
// Extensions get upgraded if the privileges are allowed to increase or
// the privileges haven't increased.
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index b79ed33..f46ed87 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -27,6 +27,7 @@
#include "chrome/browser/extensions/app_shortcut_manager.h"
#include "chrome/browser/extensions/app_sync_bundle.h"
#include "chrome/browser/extensions/apps_promo.h"
+#include "chrome/browser/extensions/extension_garbage_collector.h"
#include "chrome/browser/extensions/extension_icon_manager.h"
#include "chrome/browser/extensions/extension_menu_manager.h"
#include "chrome/browser/extensions/extension_prefs.h"
@@ -711,6 +712,11 @@ class ExtensionService
// Preferences for the owning profile (weak reference).
ExtensionPrefs* extension_prefs_;
+ // The ExtensionGarbageCollector associated with this service; this is
+ // responsible for cleaning up old or partially deleted extensions.
+ scoped_ptr<extensions::ExtensionGarbageCollector>
+ extension_garbage_collector_;
+
// Settings for the owning profile.
scoped_ptr<extensions::SettingsFrontend> settings_frontend_;
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index c23230a..c657c10 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -235,7 +235,6 @@ class MockExtensionProvider : public ExternalExtensionProviderInterface {
class MockProviderVisitor
: public ExternalExtensionProviderInterface::VisitorInterface {
public:
-
// The provider will return |fake_base_path| from
// GetBaseCrxFilePath(). User can test the behavior with
// and without an empty path using this parameter.
@@ -1007,7 +1006,6 @@ void PackExtensionTestClient::OnPackFailure(const std::string& error_message,
FAIL() << "Packing should not fail.";
else
FAIL() << "Existing CRX should have been overwritten.";
-
}
// Test loading good extensions from the profile directory.
@@ -1151,9 +1149,46 @@ TEST_F(ExtensionServiceTest, LoadAllExtensionsFromDirectoryFail) {
UTF16ToUTF8(GetErrors()[3]);
};
-// Test that partially deleted extensions are cleaned up during startup
+// Test that old versions of extensions are deleted during garbage collection.
+TEST_F(ExtensionServiceTest, GarbageCollectOldVersions) {
+ PluginService::GetInstance()->Init();
+
+ FilePath source_install_dir = data_dir_
+ .AppendASCII("garbage_collection")
+ .AppendASCII("Extensions");
+
+ FilePath pref_path = source_install_dir
+ .DirName()
+ .AppendASCII("Preferences");
+
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ // Verify that there are two versions present initially.
+ ASSERT_TRUE(file_util::PathExists(
+ extensions_install_dir_.AppendASCII("fdoajpacpdeapbmhbblepcnilfkpmkff")
+ .AppendASCII("1.0_0")));
+ ASSERT_TRUE(file_util::PathExists(
+ extensions_install_dir_.AppendASCII("fdoajpacpdeapbmhbblepcnilfkpmkff")
+ .AppendASCII("1.1_0")));
+
+ ValidatePrefKeyCount(1u);
+
+ service_->Init();
+ loop_.RunAllPending();
+
+ // Garbage collection should have deleted the old version.
+ ASSERT_FALSE(file_util::PathExists(
+ extensions_install_dir_.AppendASCII("fdoajpacpdeapbmhbblepcnilfkpmkff")
+ .AppendASCII("1.0_0")));
+ ASSERT_TRUE(file_util::PathExists(
+ extensions_install_dir_.AppendASCII("fdoajpacpdeapbmhbblepcnilfkpmkff")
+ .AppendASCII("1.1_0")));
+}
+
+// Test that extensions which were deleted from the preferences are cleaned up
+// during startup.
// Test loading bad extensions from the profile directory.
-TEST_F(ExtensionServiceTest, CleanupOnStartup) {
+TEST_F(ExtensionServiceTest, GarbageCollectOnStartup) {
PluginService::GetInstance()->Init();
FilePath source_install_dir = data_dir_
@@ -1165,11 +1200,13 @@ TEST_F(ExtensionServiceTest, CleanupOnStartup) {
InitializeInstalledExtensionService(pref_path, source_install_dir);
+ ValidatePrefKeyCount(3u);
+
// Simulate that one of them got partially deleted by clearing its pref.
{
DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings");
DictionaryValue* dict = update.Get();
- ASSERT_TRUE(dict != NULL);
+ ASSERT_TRUE(dict);
dict->Remove("behllobkkfkfnphdnhnkndlbkcpglgmj", NULL);
}
@@ -1192,6 +1229,93 @@ TEST_F(ExtensionServiceTest, CleanupOnStartup) {
ASSERT_FALSE(file_util::PathExists(extension_dir));
}
+// Test that internal extensions which are referenced in the preferences but
+// had their content deleted are cleaned up during startup.
+TEST_F(ExtensionServiceTest, GarbageCollectInternalExtensionsMissingContent) {
+ PluginService::GetInstance()->Init();
+
+ FilePath source_dir = data_dir_.AppendASCII("good").AppendASCII("Extensions");
+ FilePath pref_path = source_dir.DirName().AppendASCII("Preferences");
+
+ InitializeInstalledExtensionService(pref_path, source_dir);
+
+ ValidatePrefKeyCount(3u);
+
+ // Delete the extension's directory.
+ FilePath extension_dir = extensions_install_dir_
+ .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj");
+ ASSERT_TRUE(file_util::Delete(extension_dir, true));
+
+ // Run GarbageCollectExtensions.
+ service_->Init();
+ loop_.RunAllPending();
+
+ file_util::FileEnumerator dirs(extensions_install_dir_, false,
+ file_util::FileEnumerator::DIRECTORIES);
+ size_t count = 0;
+ while (!dirs.Next().empty())
+ count++;
+
+ // We should have only gotten two extensions now.
+ EXPECT_EQ(2u, count);
+
+ const DictionaryValue* dictionary =
+ profile_->GetPrefs()->GetDictionary("extensions.settings");
+ ASSERT_TRUE(dictionary);
+ ASSERT_FALSE(dictionary->HasKey("behllobkkfkfnphdnhnkndlbkcpglgmj"));
+}
+
+// Test that unpacked extensions which are referenced in the preferences but
+// had their content deleted are cleaned up during startup.
+TEST_F(ExtensionServiceTest, GarbageCollectUnpackedExtensionsMissingContent) {
+ InitializeEmptyExtensionService();
+
+ ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ // Write the manifest dynamically, since we have to delete the file anyway.
+ FilePath extension_path = temp.path();
+ FilePath manifest_path = extension_path.Append(Extension::kManifestFilename);
+ ASSERT_FALSE(file_util::PathExists(manifest_path));
+
+ // Construct a simple manifest and install it.
+ DictionaryValue manifest;
+ manifest.SetString("version", "1.0");
+ manifest.SetString("name", "Cleanup Unpacked Extensions Missing Content");
+ manifest.SetInteger("manifest_version", 2);
+
+ JSONFileValueSerializer serializer(manifest_path);
+ ASSERT_TRUE(serializer.Serialize(manifest));
+
+ extensions::UnpackedInstaller::Create(service_)->Load(extension_path);
+ loop_.RunAllPending();
+
+ // Make sure it installed correctly.
+ EXPECT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ EXPECT_EQ(Extension::LOAD, loaded_[0]->location());
+ EXPECT_EQ(1u, service_->extensions()->size());
+
+ std::string extension_id = loaded_[0]->id();
+
+ // Make sure it is in the preferences at this point.
+ const DictionaryValue* initial_dictionary =
+ profile_->GetPrefs()->GetDictionary("extensions.settings");
+ ASSERT_TRUE(initial_dictionary);
+ ASSERT_TRUE(initial_dictionary->HasKey(extension_id));
+
+ // Delete local content, GarbageCollectExtensions, and test whether the key
+ // is still in the preferences.
+ ASSERT_TRUE(file_util::Delete(extension_path, true));
+ service_->GarbageCollectExtensions();
+ loop_.RunAllPending();
+
+ const DictionaryValue* final_dictionary =
+ profile_->GetPrefs()->GetDictionary("extensions.settings");
+ ASSERT_TRUE(final_dictionary);
+ ASSERT_FALSE(final_dictionary->HasKey(extension_id));
+}
+
// Test installing extensions. This test tries to install few extensions using
// crx files. If you need to change those crx files, feel free to repackage
// them, throw away the key used and change the id's above.