summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-19 19:17:47 +0000
committeryoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-06-19 19:17:47 +0000
commit2f8757c314dbae44b10fa19f15d1ccf69361680f (patch)
tree92081b1c26e05981198331cb7143a2facf355ec6
parent06c7dacb8a9e610da95b7a5bc5b5d2458b430b3d (diff)
downloadchromium_src-2f8757c314dbae44b10fa19f15d1ccf69361680f.zip
chromium_src-2f8757c314dbae44b10fa19f15d1ccf69361680f.tar.gz
chromium_src-2f8757c314dbae44b10fa19f15d1ccf69361680f.tar.bz2
Revert 142427 - Cleaning Up Extensions When Local Content Removed
Reverted because unpacked extensions appear to unintentionally get removed: crbug.com/133381. 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 TBR=rdevlin.cronin@chromium.org Review URL: https://chromiumcodereview.appspot.com/10581021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@143012 0039d316-1c4b-4281-b951-d872f2087c98
-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.cc23
-rw-r--r--chrome/browser/extensions/extension_service.h6
-rw-r--r--chrome/browser/extensions/extension_service_unittest.cc134
-rw-r--r--chrome/browser/sync/test/integration/sync_extension_helper.cc12
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/common/extensions/extension_file_util.cc63
-rw-r--r--chrome/common/extensions/extension_file_util.h13
-rw-r--r--chrome/common/extensions/extension_file_util_unittest.cc57
-rw-r--r--chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.0_0/manifest.json6
-rw-r--r--chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.1_0/manifest.json6
-rw-r--r--chrome/test/data/extensions/garbage_collection/Preferences20
13 files changed, 157 insertions, 430 deletions
diff --git a/chrome/browser/extensions/extension_garbage_collector.cc b/chrome/browser/extensions/extension_garbage_collector.cc
deleted file mode 100644
index 933f25c..0000000
--- a/chrome/browser/extensions/extension_garbage_collector.cc
+++ /dev/null
@@ -1,192 +0,0 @@
-// 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
deleted file mode 100644
index bf75b1a..0000000
--- a/chrome/browser/extensions/extension_garbage_collector.h
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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 710643b..f4063bc 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -119,6 +119,7 @@
#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
@@ -324,8 +325,6 @@ 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),
@@ -1847,7 +1846,23 @@ void ExtensionService::ReloadExtensions() {
}
void ExtensionService::GarbageCollectExtensions() {
- extension_garbage_collector_->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();
// Also garbage-collect themes. We check |profile_| to be
// defensive; in the future, we may call GarbageCollectExtensions()
@@ -2013,7 +2028,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_GE(extension->version()->CompareTo(*(old->version())), 0);
+ CHECK(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 f46ed87..b79ed33 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -27,7 +27,6 @@
#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"
@@ -712,11 +711,6 @@ 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 c657c10..c23230a 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -235,6 +235,7 @@ 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.
@@ -1006,6 +1007,7 @@ 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.
@@ -1149,46 +1151,9 @@ TEST_F(ExtensionServiceTest, LoadAllExtensionsFromDirectoryFail) {
UTF16ToUTF8(GetErrors()[3]);
};
-// 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 that partially deleted extensions are cleaned up during startup
// Test loading bad extensions from the profile directory.
-TEST_F(ExtensionServiceTest, GarbageCollectOnStartup) {
+TEST_F(ExtensionServiceTest, CleanupOnStartup) {
PluginService::GetInstance()->Init();
FilePath source_install_dir = data_dir_
@@ -1200,13 +1165,11 @@ TEST_F(ExtensionServiceTest, GarbageCollectOnStartup) {
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);
+ ASSERT_TRUE(dict != NULL);
dict->Remove("behllobkkfkfnphdnhnkndlbkcpglgmj", NULL);
}
@@ -1229,93 +1192,6 @@ TEST_F(ExtensionServiceTest, GarbageCollectOnStartup) {
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.
diff --git a/chrome/browser/sync/test/integration/sync_extension_helper.cc b/chrome/browser/sync/test/integration/sync_extension_helper.cc
index c719aa0..a2151ac 100644
--- a/chrome/browser/sync/test/integration/sync_extension_helper.cc
+++ b/chrome/browser/sync/test/integration/sync_extension_helper.cc
@@ -18,8 +18,6 @@
#include "chrome/common/string_ordinal.h"
#include "chrome/browser/sync/test/integration/sync_test.h"
#include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "content/public/browser/browser_thread.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::Extension;
@@ -72,16 +70,6 @@ std::string SyncExtensionHelper::InstallExtension(
NOTREACHED() << "Could not install extension " << name;
return "";
}
- // extensions::ExtensionGarbageCollector requires that there be a file present
- // on the file system for all extensions; we mimic this here. This also better
- // simulates the actual install process.
- content::BrowserThread::PostTask(
- content::BrowserThread::FILE, FROM_HERE,
- base::Bind(
- base::IgnoreResult(&file_util::CreateDirectory),
- profile->GetExtensionService()->install_directory()
- .AppendASCII(extension->id())));
- ui_test_utils::RunAllPendingInMessageLoop(content::BrowserThread::FILE);
profile->GetExtensionService()->OnExtensionInstalled(
extension, extension->UpdatesFromGallery(), StringOrdinal());
return extension->id();
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index a1dea0b..7fa79f3 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -288,8 +288,6 @@
'browser/extensions/extension_function_registry.h',
'browser/extensions/extension_function_util.cc',
'browser/extensions/extension_function_util.h',
- 'browser/extensions/extension_garbage_collector.cc',
- 'browser/extensions/extension_garbage_collector.h',
'browser/extensions/extension_global_error.cc',
'browser/extensions/extension_global_error.h',
'browser/extensions/extension_global_error_badge.cc',
diff --git a/chrome/common/extensions/extension_file_util.cc b/chrome/common/extensions/extension_file_util.cc
index 8cb95b9..4a6f150 100644
--- a/chrome/common/extensions/extension_file_util.cc
+++ b/chrome/common/extensions/extension_file_util.cc
@@ -419,6 +419,69 @@ bool ValidateExtension(const Extension* extension,
return true;
}
+void GarbageCollectExtensions(
+ const FilePath& install_directory,
+ const std::map<std::string, FilePath>& extension_paths) {
+ // Nothing to clean up if it doesn't exist.
+ if (!file_util::DirectoryExists(install_directory))
+ return;
+
+ DVLOG(1) << "Garbage collecting extensions...";
+ file_util::FileEnumerator enumerator(install_directory,
+ false, // Not recursive.
+ file_util::FileEnumerator::DIRECTORIES);
+ FilePath extension_path;
+ for (extension_path = enumerator.Next(); !extension_path.value().empty();
+ extension_path = enumerator.Next()) {
+ std::string extension_id;
+
+ FilePath basename = extension_path.BaseName();
+ if (IsStringASCII(basename.value())) {
+ extension_id = UTF16ToASCII(basename.LossyDisplayName());
+ if (!Extension::IdIsValid(extension_id))
+ extension_id.clear();
+ }
+
+ // Delete directories that aren't valid IDs.
+ if (extension_id.empty()) {
+ DLOG(WARNING) << "Invalid extension ID encountered in extensions "
+ "directory: " << basename.value();
+ DVLOG(1) << "Deleting invalid extension directory "
+ << extension_path.value() << ".";
+ file_util::Delete(extension_path, true); // Recursive.
+ continue;
+ }
+
+ std::map<std::string, FilePath>::const_iterator iter =
+ extension_paths.find(extension_id);
+
+ // If there is no entry in the prefs file, just delete the directory and
+ // move on. This can legitimately happen when an uninstall does not
+ // complete, for example, when a plugin is in use at uninstall time.
+ if (iter == extension_paths.end()) {
+ DVLOG(1) << "Deleting unreferenced install for directory "
+ << extension_path.LossyDisplayName() << ".";
+ file_util::Delete(extension_path, true); // Recursive.
+ continue;
+ }
+
+ // Clean up old version directories.
+ file_util::FileEnumerator versions_enumerator(
+ extension_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() != iter->second.BaseName()) {
+ DVLOG(1) << "Deleting old version for directory "
+ << version_dir.LossyDisplayName() << ".";
+ file_util::Delete(version_dir, true); // Recursive.
+ }
+ }
+ }
+}
+
ExtensionMessageBundle* LoadExtensionMessageBundle(
const FilePath& extension_path,
const std::string& default_locale,
diff --git a/chrome/common/extensions/extension_file_util.h b/chrome/common/extensions/extension_file_util.h
index 07cd4ec..72a5069 100644
--- a/chrome/common/extensions/extension_file_util.h
+++ b/chrome/common/extensions/extension_file_util.h
@@ -72,6 +72,19 @@ bool ValidateExtension(const extensions::Extension* extension,
// Returns a list of files that contain private keys inside |extension_dir|.
std::vector<FilePath> FindPrivateKeyFiles(const FilePath& extension_dir);
+// Cleans up the extension install directory. It can end up with garbage in it
+// if extensions can't initially be removed when they are uninstalled (eg if a
+// file is in use).
+//
+// |extensions_dir| is the install directory to look in. |extension_paths| is a
+// map from extension id to full installation path.
+//
+// Obsolete version directories are removed, as are directories that aren't
+// found in |extension_paths|.
+void GarbageCollectExtensions(
+ const FilePath& extensions_dir,
+ const std::map<std::string, FilePath>& extension_paths);
+
// Loads extension message catalogs and returns message bundle.
// Returns NULL on error, or if extension is not localized.
ExtensionMessageBundle* LoadExtensionMessageBundle(
diff --git a/chrome/common/extensions/extension_file_util_unittest.cc b/chrome/common/extensions/extension_file_util_unittest.cc
index 0b3d12b..5551db1 100644
--- a/chrome/common/extensions/extension_file_util_unittest.cc
+++ b/chrome/common/extensions/extension_file_util_unittest.cc
@@ -22,6 +22,63 @@ using extensions::Extension;
namespace keys = extension_manifest_keys;
+#if defined(OS_WIN)
+// http://crbug.com/106381
+#define InstallUninstallGarbageCollect DISABLED_InstallUninstallGarbageCollect
+#endif
+TEST(ExtensionFileUtil, InstallUninstallGarbageCollect) {
+ ScopedTempDir temp;
+ ASSERT_TRUE(temp.CreateUniqueTempDir());
+
+ // Create a source extension.
+ std::string extension_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ std::string version("1.0");
+ FilePath src = temp.path().AppendASCII(extension_id);
+ ASSERT_TRUE(file_util::CreateDirectory(src));
+
+ // Create a extensions tree.
+ FilePath all_extensions = temp.path().AppendASCII("extensions");
+ ASSERT_TRUE(file_util::CreateDirectory(all_extensions));
+
+ // Install in empty directory. Should create parent directories as needed.
+ FilePath version_1 = extension_file_util::InstallExtension(src,
+ extension_id,
+ version,
+ all_extensions);
+ ASSERT_EQ(version_1.value(),
+ all_extensions.AppendASCII(extension_id).AppendASCII("1.0_0")
+ .value());
+ ASSERT_TRUE(file_util::DirectoryExists(version_1));
+
+ // Should have moved the source.
+ ASSERT_FALSE(file_util::DirectoryExists(src));
+
+ // Install again. Should create a new one with different name.
+ ASSERT_TRUE(file_util::CreateDirectory(src));
+ FilePath version_2 = extension_file_util::InstallExtension(src,
+ extension_id,
+ version,
+ all_extensions);
+ ASSERT_EQ(version_2.value(),
+ all_extensions.AppendASCII(extension_id).AppendASCII("1.0_1")
+ .value());
+ ASSERT_TRUE(file_util::DirectoryExists(version_2));
+
+ // Collect garbage. Should remove first one.
+ std::map<std::string, FilePath> extension_paths;
+ extension_paths[extension_id] =
+ FilePath().AppendASCII(extension_id).Append(version_2.BaseName());
+ extension_file_util::GarbageCollectExtensions(all_extensions,
+ extension_paths);
+ ASSERT_FALSE(file_util::DirectoryExists(version_1));
+ ASSERT_TRUE(file_util::DirectoryExists(version_2));
+
+ // Uninstall. Should remove entire extension subtree.
+ extension_file_util::UninstallExtension(all_extensions, extension_id);
+ ASSERT_FALSE(file_util::DirectoryExists(version_2.DirName()));
+ ASSERT_TRUE(file_util::DirectoryExists(all_extensions));
+}
+
TEST(ExtensionFileUtil, LoadExtensionWithValidLocales) {
FilePath install_dir;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir));
diff --git a/chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.0_0/manifest.json b/chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.0_0/manifest.json
deleted file mode 100644
index e69d36b..0000000
--- a/chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.0_0/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "description": "An extension to test garbage collection of old versions.",
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3Yox7PDjjZl7upms+MLnOuswzlX63YH5jVvzug1WoAHVnq/djfHsViG0WgTrxxPiMhuTNqOh2OzdqHRko+xbkBBgpSjV+X1lIvUgZGOx9ImH2pz4kntyRv1z9abuN+rQh+gjglPvLJQuWU6VvjH0zypTaXZiQ4GCojRqD/dWacQIDAQAB",
- "name": "GarbageCollectOldVersions Extension",
- "version": "1.0"
-}
diff --git a/chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.1_0/manifest.json b/chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.1_0/manifest.json
deleted file mode 100644
index 20a4b3b..0000000
--- a/chrome/test/data/extensions/garbage_collection/Extensions/fdoajpacpdeapbmhbblepcnilfkpmkff/1.1_0/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "description": "An extension to test garbage collection of old versions.",
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3Yox7PDjjZl7upms+MLnOuswzlX63YH5jVvzug1WoAHVnq/djfHsViG0WgTrxxPiMhuTNqOh2OzdqHRko+xbkBBgpSjV+X1lIvUgZGOx9ImH2pz4kntyRv1z9abuN+rQh+gjglPvLJQuWU6VvjH0zypTaXZiQ4GCojRqD/dWacQIDAQAB",
- "name": "GarbageCollectOldVersions Extension",
- "version": "1.1"
-}
diff --git a/chrome/test/data/extensions/garbage_collection/Preferences b/chrome/test/data/extensions/garbage_collection/Preferences
deleted file mode 100644
index 92e3b6e..0000000
--- a/chrome/test/data/extensions/garbage_collection/Preferences
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "extensions": {
- "settings": {
- "fdoajpacpdeapbmhbblepcnilfkpmkff": {
- "from_bookmark": false,
- "from_webstore": false,
- "install_time": "12977681506510306",
- "location": 1,
- "manifest": {
- "description": "An extension to test garbage collection of old versions.",
- "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3Yox7PDjjZl7upms+MLnOuswzlX63YH5jVvzug1WoAHVnq/djfHsViG0WgTrxxPiMhuTNqOh2OzdqHRko+xbkBBgpSjV+X1lIvUgZGOx9ImH2pz4kntyRv1z9abuN+rQh+gjglPvLJQuWU6VvjH0zypTaXZiQ4GCojRqD/dWacQIDAQAB",
- "name": "GarbageCollectOldVersions Extension",
- "version": "1.1"
- },
- "path": "fdoajpacpdeapbmhbblepcnilfkpmkff/1.1_0",
- "state": 1
- }
- }
- }
-}