diff options
author | yoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-19 19:17:47 +0000 |
---|---|---|
committer | yoz@chromium.org <yoz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-06-19 19:17:47 +0000 |
commit | 2f8757c314dbae44b10fa19f15d1ccf69361680f (patch) | |
tree | 92081b1c26e05981198331cb7143a2facf355ec6 | |
parent | 06c7dacb8a9e610da95b7a5bc5b5d2458b430b3d (diff) | |
download | chromium_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
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 - } - } - } -} |