summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions
diff options
context:
space:
mode:
authorrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-28 18:28:07 +0000
committerrdevlin.cronin@chromium.org <rdevlin.cronin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-28 18:28:07 +0000
commitb2a3c073b8b5c367e5ca9fa5b05dc59e2400f18a (patch)
tree2b98e48475f1f4a4ad2604c9d0e928b6fd39ce05 /chrome/browser/extensions
parent5faf328f60b55df3b62a687ccf4b2660460679ae (diff)
downloadchromium_src-b2a3c073b8b5c367e5ca9fa5b05dc59e2400f18a.zip
chromium_src-b2a3c073b8b5c367e5ca9fa5b05dc59e2400f18a.tar.gz
chromium_src-b2a3c073b8b5c367e5ca9fa5b05dc59e2400f18a.tar.bz2
Remove ExtensionService Garbage-Collecting methods.
Make a new class, ExtensionGarbageCollector, which performs: - ExtensionService::GarbageCollectExtensions(), - extension_file_util::GarbageCollectExtensions() (the file-thread impl of ExtensionService::GarbageCollectExtensions()), - ExtensionService::GarbageCollectIsolatedStorage() BUG=351891 TBR=rkc@chromium.org (for c/b/chromeos/kiosk_mode - no functional changes, but the TODO has moved from ExtensionService to ExtensionGarbageCollector). Review URL: https://codereview.chromium.org/204983020 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@260207 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r--chrome/browser/extensions/extension_garbage_collector.cc249
-rw-r--r--chrome/browser/extensions/extension_garbage_collector.h88
-rw-r--r--chrome/browser/extensions/extension_garbage_collector_unittest.cc138
-rw-r--r--chrome/browser/extensions/extension_service.cc112
-rw-r--r--chrome/browser/extensions/extension_service.h49
-rw-r--r--chrome/browser/extensions/extension_service_unittest.cc112
6 files changed, 501 insertions, 247 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..91a077a
--- /dev/null
+++ b/chrome/browser/extensions/extension_garbage_collector.cc
@@ -0,0 +1,249 @@
+// Copyright 2014 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 "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/pending_extension_manager.h"
+#include "chrome/common/extensions/extension_file_util.h"
+#include "chrome/common/extensions/manifest_handlers/app_isolation_info.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/storage_partition.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+namespace {
+
+// Wait this many seconds before trying to garbage collect extensions again.
+const int kGarbageCollectRetryDelayInSeconds = 30;
+
+// Wait this many seconds after startup to see if there are any extensions
+// which can be garbage collected.
+const int kGarbageCollectStartupDelay = 30;
+
+typedef std::multimap<std::string, base::FilePath> ExtensionPathsMultimap;
+
+void CheckExtensionDirectory(const base::FilePath& path,
+ const ExtensionPathsMultimap& extension_paths,
+ bool clean_temp_dir) {
+ base::FilePath basename = path.BaseName();
+ // Clean up temporary files left if Chrome crashed or quit in the middle
+ // of an extension install.
+ if (basename.value() == extension_file_util::kTempDirectoryName) {
+ if (clean_temp_dir)
+ base::DeleteFile(path, true); // Recursive.
+ return;
+ }
+
+ // Parse directory name as a potential extension ID.
+ std::string extension_id;
+ if (IsStringASCII(basename.value())) {
+ extension_id = base::UTF16ToASCII(basename.LossyDisplayName());
+ if (!Extension::IdIsValid(extension_id))
+ extension_id.clear();
+ }
+
+ // Delete directories that aren't valid IDs.
+ if (extension_id.empty()) {
+ base::DeleteFile(path, true); // Recursive.
+ return;
+ }
+
+ typedef ExtensionPathsMultimap::const_iterator Iter;
+ std::pair<Iter, Iter> iter_pair = extension_paths.equal_range(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_pair.first == iter_pair.second) {
+ base::DeleteFile(path, true); // Recursive.
+ return;
+ }
+
+ // Clean up old version directories.
+ base::FileEnumerator versions_enumerator(
+ path, false /* Not recursive */, base::FileEnumerator::DIRECTORIES);
+ for (base::FilePath version_dir = versions_enumerator.Next();
+ !version_dir.empty();
+ version_dir = versions_enumerator.Next()) {
+ bool known_version = false;
+ for (Iter iter = iter_pair.first; iter != iter_pair.second; ++iter) {
+ if (version_dir.BaseName() == iter->second.BaseName()) {
+ known_version = true;
+ break;
+ }
+ }
+ if (!known_version)
+ base::DeleteFile(version_dir, true); // Recursive.
+ }
+}
+
+} // namespace
+
+ExtensionGarbageCollector::ExtensionGarbageCollector(
+ ExtensionService* extension_service)
+ : extension_service_(extension_service),
+ context_(extension_service->GetBrowserContext()),
+ install_directory_(extension_service->install_directory()),
+ weak_factory_(this) {
+#if defined(OS_CHROMEOS)
+ disable_garbage_collection_ = false;
+#endif
+
+ ExtensionSystem* extension_system = ExtensionSystem::Get(context_);
+ DCHECK(extension_system);
+
+ extension_system->ready().PostDelayed(
+ FROM_HERE,
+ base::Bind(&ExtensionGarbageCollector::GarbageCollectExtensions,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kGarbageCollectStartupDelay));
+
+ extension_system->ready().Post(
+ FROM_HERE,
+ base::Bind(
+ &ExtensionGarbageCollector::GarbageCollectIsolatedStorageIfNeeded,
+ weak_factory_.GetWeakPtr()));
+}
+
+ExtensionGarbageCollector::~ExtensionGarbageCollector() {}
+
+void ExtensionGarbageCollector::GarbageCollectExtensionsForTest() {
+ GarbageCollectExtensions();
+}
+
+void ExtensionGarbageCollector::GarbageCollectExtensions() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+#if defined(OS_CHROMEOS)
+ if (disable_garbage_collection_)
+ return;
+#endif
+
+ ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
+ DCHECK(extension_prefs);
+
+ if (extension_prefs->pref_service()->ReadOnly())
+ return;
+
+ bool clean_temp_dir = true;
+
+ if (extension_service_->pending_extension_manager()->HasPendingExtensions()) {
+ // Don't garbage collect temp dir while there are pending installations,
+ // which may be using the temporary installation directory. Try to garbage
+ // collect again later.
+ clean_temp_dir = false;
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ExtensionGarbageCollector::GarbageCollectExtensions,
+ weak_factory_.GetWeakPtr()),
+ base::TimeDelta::FromSeconds(kGarbageCollectRetryDelayInSeconds));
+ }
+
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> info(
+ extension_prefs->GetInstalledExtensionsInfo());
+ std::multimap<std::string, base::FilePath> extension_paths;
+ for (size_t i = 0; i < info->size(); ++i) {
+ extension_paths.insert(
+ std::make_pair(info->at(i)->extension_id, info->at(i)->extension_path));
+ }
+
+ info = extension_prefs->GetAllDelayedInstallInfo();
+ for (size_t i = 0; i < info->size(); ++i) {
+ extension_paths.insert(
+ std::make_pair(info->at(i)->extension_id, info->at(i)->extension_path));
+ }
+
+ if (!extension_service_->GetFileTaskRunner()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &ExtensionGarbageCollector::GarbageCollectExtensionsOnFileThread,
+ weak_factory_.GetWeakPtr(),
+ extension_paths,
+ clean_temp_dir))) {
+ NOTREACHED();
+ }
+}
+
+void ExtensionGarbageCollector::GarbageCollectIsolatedStorageIfNeeded() {
+ ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
+ DCHECK(extension_prefs);
+ if (!extension_prefs->NeedsStorageGarbageCollection())
+ return;
+ extension_prefs->SetNeedsStorageGarbageCollection(false);
+
+ scoped_ptr<base::hash_set<base::FilePath> > active_paths(
+ new base::hash_set<base::FilePath>());
+ const ExtensionSet& extensions =
+ ExtensionRegistry::Get(context_)->enabled_extensions();
+ for (ExtensionSet::const_iterator iter = extensions.begin();
+ iter != extensions.end();
+ ++iter) {
+ if (AppIsolationInfo::HasIsolatedStorage(iter->get())) {
+ active_paths->insert(
+ content::BrowserContext::GetStoragePartitionForSite(
+ context_, util::GetSiteForExtensionId((*iter)->id(), context_))
+ ->GetPath());
+ }
+ }
+
+ // The data of ephemeral apps can outlive their cache lifetime. Ensure
+ // they are not garbage collected.
+ scoped_ptr<ExtensionPrefs::ExtensionsInfo> evicted_apps_info(
+ extension_prefs->GetEvictedEphemeralAppsInfo());
+ for (size_t i = 0; i < evicted_apps_info->size(); ++i) {
+ ExtensionInfo* info = evicted_apps_info->at(i).get();
+ if (util::HasIsolatedStorage(*info)) {
+ active_paths->insert(content::BrowserContext::GetStoragePartitionForSite(
+ context_,
+ util::GetSiteForExtensionId(
+ info->extension_id, context_))->GetPath());
+ }
+ }
+
+ extension_service_->OnGarbageCollectIsolatedStorageStart();
+ content::BrowserContext::GarbageCollectStoragePartitions(
+ context_,
+ active_paths.Pass(),
+ base::Bind(&ExtensionService::OnGarbageCollectIsolatedStorageFinished,
+ extension_service_->AsWeakPtr()));
+}
+
+void ExtensionGarbageCollector::GarbageCollectExtensionsOnFileThread(
+ const ExtensionPathsMultimap& extension_paths,
+ bool clean_temp_dir) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
+
+ // Nothing to clean up if it doesn't exist.
+ if (!base::DirectoryExists(install_directory_))
+ return;
+
+ base::FileEnumerator enumerator(install_directory_,
+ false, // Not recursive.
+ base::FileEnumerator::DIRECTORIES);
+
+ for (base::FilePath extension_path = enumerator.Next();
+ !extension_path.empty();
+ extension_path = enumerator.Next()) {
+ CheckExtensionDirectory(extension_path, extension_paths, clean_temp_dir);
+ }
+}
+
+} // 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..c682c80
--- /dev/null
+++ b/chrome/browser/extensions/extension_garbage_collector.h
@@ -0,0 +1,88 @@
+// Copyright 2014 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_
+
+#include <map>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+
+namespace content {
+class BrowserContext;
+}
+
+class ExtensionService;
+
+namespace extensions {
+
+// The class responsible for cleaning up the cruft left behind on the file
+// system by uninstalled (or failed install) extensions.
+// The class is owned by ExtensionService, but is mostly independent. Tasks to
+// garbage collect extensions and isolated storage are posted once the
+// ExtensionSystem signals ready.
+class ExtensionGarbageCollector {
+ public:
+ explicit ExtensionGarbageCollector(ExtensionService* extension_service);
+ ~ExtensionGarbageCollector();
+
+#if defined(OS_CHROMEOS)
+ // Enable or disable garbage collection. See |disable_garbage_collection_|.
+ void disable_garbage_collection() { disable_garbage_collection_ = true; }
+ void enable_garbage_collection() { disable_garbage_collection_ = false; }
+#endif
+
+ // Manually trigger GarbageCollectExtensions() for testing.
+ void GarbageCollectExtensionsForTest();
+
+ private:
+ // 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).
+ // Obsolete version directories are removed, as are directories that aren't
+ // found in the ExtensionPrefs.
+ // The "Temp" directory that is used during extension installation will get
+ // removed iff there are no pending installations.
+ void GarbageCollectExtensions();
+
+ // The FILE-thread implementation of GarbageCollectExtensions().
+ void GarbageCollectExtensionsOnFileThread(
+ const std::multimap<std::string, base::FilePath>& extension_paths,
+ bool clean_temp_dir);
+
+ // Garbage collects apps/extensions isolated storage, if it is not currently
+ // active (i.e. is not in ExtensionRegistry::ENABLED). There is an exception
+ // for ephemeral apps, because they can outlive their cache lifetimes.
+ void GarbageCollectIsolatedStorageIfNeeded();
+
+ // The ExtensionService which owns this GarbageCollector.
+ ExtensionService* extension_service_;
+
+ // The BrowserContext associated with the GarbageCollector, for convenience.
+ // (This is equivalent to extension_service_->GetBrowserContext().)
+ content::BrowserContext* context_;
+
+ // The root extensions installation directory.
+ base::FilePath install_directory_;
+
+#if defined(OS_CHROMEOS)
+ // TODO(rkc): HACK alert - this is only in place to allow the
+ // kiosk_mode_screensaver to prevent its extension from getting garbage
+ // collected. Remove this once KioskModeScreensaver is removed.
+ // See crbug.com/280363
+ bool disable_garbage_collection_;
+#endif
+
+ // Generate weak pointers for safely posting to the file thread for garbage
+ // collection.
+ base::WeakPtrFactory<ExtensionGarbageCollector> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionGarbageCollector);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_GARBAGE_COLLECTOR_H_
diff --git a/chrome/browser/extensions/extension_garbage_collector_unittest.cc b/chrome/browser/extensions/extension_garbage_collector_unittest.cc
new file mode 100644
index 0000000..dc92256
--- /dev/null
+++ b/chrome/browser/extensions/extension_garbage_collector_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_path.h"
+#include "base/prefs/scoped_user_pref_update.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_garbage_collector.h"
+#include "chrome/browser/extensions/extension_service_unittest.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/plugin_service.h"
+
+namespace extensions {
+
+class ExtensionGarbageCollectorUnitTest : public ExtensionServiceTestBase {
+ protected:
+ void InitPluginService() {
+#if defined(ENABLE_PLUGINS)
+ content::PluginService::GetInstance()->Init();
+#endif
+ }
+
+ // A delayed task to call GarbageCollectExtensions is posted by
+ // ExtensionGarbageCollector's constructor. But, as the test won't wait for
+ // the delayed task to be called, we have to call it manually instead.
+ void GarbageCollectExtensions() {
+ service_->garbage_collector()->GarbageCollectExtensionsForTest();
+ // Wait for GarbageCollectExtensions task to complete.
+ base::RunLoop().RunUntilIdle();
+ }
+};
+
+// Test that partially deleted extensions are cleaned up during startup
+// Test loading bad extensions from the profile directory.
+TEST_F(ExtensionGarbageCollectorUnitTest, CleanupOnStartup) {
+ const std::string kExtensionId = "behllobkkfkfnphdnhnkndlbkcpglgmj";
+
+ InitPluginService();
+ InitializeGoodInstalledExtensionService();
+
+ // Simulate that one of them got partially deleted by clearing its pref.
+ {
+ DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings");
+ base::DictionaryValue* dict = update.Get();
+ ASSERT_TRUE(dict != NULL);
+ dict->Remove(kExtensionId, NULL);
+ }
+
+ service_->Init();
+ GarbageCollectExtensions();
+
+ base::FileEnumerator dirs(extensions_install_dir_,
+ false, // not recursive
+ base::FileEnumerator::DIRECTORIES);
+ size_t count = 0;
+ while (!dirs.Next().empty())
+ count++;
+
+ // We should have only gotten two extensions now.
+ EXPECT_EQ(2u, count);
+
+ // And extension1 dir should now be toast.
+ base::FilePath extension_dir =
+ extensions_install_dir_.AppendASCII(kExtensionId);
+ ASSERT_FALSE(base::PathExists(extension_dir));
+}
+
+// Test that GarbageCollectExtensions deletes the right versions of an
+// extension.
+TEST_F(ExtensionGarbageCollectorUnitTest, GarbageCollectWithPendingUpdates) {
+ InitPluginService();
+
+ base::FilePath source_install_dir =
+ data_dir_.AppendASCII("pending_updates").AppendASCII("Extensions");
+ base::FilePath pref_path =
+ source_install_dir.DirName().Append(chrome::kPreferencesFilename);
+
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ // This is the directory that is going to be deleted, so make sure it actually
+ // is there before the garbage collection.
+ ASSERT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "hpiknbiabeeppbpihjehijgoemciehgk/3")));
+
+ GarbageCollectExtensions();
+
+ // Verify that the pending update for the first extension didn't get
+ // deleted.
+ EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0")));
+ EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0")));
+ EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "hpiknbiabeeppbpihjehijgoemciehgk/2")));
+ EXPECT_FALSE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "hpiknbiabeeppbpihjehijgoemciehgk/3")));
+}
+
+// Test that pending updates are properly handled on startup.
+TEST_F(ExtensionGarbageCollectorUnitTest, UpdateOnStartup) {
+ InitPluginService();
+
+ base::FilePath source_install_dir =
+ data_dir_.AppendASCII("pending_updates").AppendASCII("Extensions");
+ base::FilePath pref_path =
+ source_install_dir.DirName().Append(chrome::kPreferencesFilename);
+
+ InitializeInstalledExtensionService(pref_path, source_install_dir);
+
+ // This is the directory that is going to be deleted, so make sure it actually
+ // is there before the garbage collection.
+ ASSERT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "hpiknbiabeeppbpihjehijgoemciehgk/3")));
+
+ service_->Init();
+ GarbageCollectExtensions();
+
+ // Verify that the pending update for the first extension got installed.
+ EXPECT_FALSE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0")));
+ EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0")));
+ EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "hpiknbiabeeppbpihjehijgoemciehgk/2")));
+ EXPECT_FALSE(base::PathExists(extensions_install_dir_.AppendASCII(
+ "hpiknbiabeeppbpihjehijgoemciehgk/3")));
+
+ // Make sure update information got deleted.
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_.get());
+ EXPECT_FALSE(
+ prefs->GetDelayedInstallInfo("bjafgdebaacbbbecmhlhpofkepfkgcpa"));
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 99962eb..a7cbda85 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -34,6 +34,7 @@
#include "chrome/browser/extensions/extension_disabled_ui.h"
#include "chrome/browser/extensions/extension_error_reporter.h"
#include "chrome/browser/extensions/extension_error_ui.h"
+#include "chrome/browser/extensions/extension_garbage_collector.h"
#include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/extensions/extension_special_storage_policy.h"
#include "chrome/browser/extensions/extension_sync_service.h"
@@ -57,7 +58,6 @@
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/features/feature_channel.h"
-#include "chrome/common/extensions/manifest_handlers/app_isolation_info.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
@@ -147,13 +147,6 @@ static const int kMaxExtensionAcknowledgePromptCount = 3;
// Wait this many seconds after an extensions becomes idle before updating it.
static const int kUpdateIdleDelay = 5;
-// Wait this many seconds before trying to garbage collect extensions again.
-static const int kGarbageCollectRetryDelay = 30;
-
-// Wait this many seconds after startup to see if there are any extensions
-// which can be garbage collected.
-static const int kGarbageCollectStartupDelay = 30;
-
static bool IsSharedModule(const Extension* extension) {
return SharedModuleInfo::IsSharedModule(extension);
}
@@ -340,10 +333,8 @@ ExtensionService::ExtensionService(Profile* profile,
update_once_all_providers_are_ready_(false),
browser_terminating_(false),
installs_delayed_for_gc_(false),
- is_first_run_(false) {
-#if defined(OS_CHROMEOS)
- disable_garbage_collection_ = false;
-#endif
+ is_first_run_(false),
+ garbage_collector_(new extensions::ExtensionGarbageCollector(this)) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Figure out if extension installation should be enabled.
@@ -529,15 +520,6 @@ void ExtensionService::Init() {
// rather than running immediately at startup.
CheckForExternalUpdates();
- base::MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&ExtensionService::GarbageCollectExtensions, AsWeakPtr()),
- base::TimeDelta::FromSeconds(kGarbageCollectStartupDelay));
-
- if (extension_prefs_->NeedsStorageGarbageCollection()) {
- GarbageCollectIsolatedStorage();
- extension_prefs_->SetNeedsStorageGarbageCollection(false);
- }
system_->management_policy()->RegisterProvider(
shared_module_policy_provider_.get());
@@ -1563,52 +1545,6 @@ void ExtensionService::ReloadExtensionsForTest() {
// times.
}
-void ExtensionService::GarbageCollectExtensions() {
-#if defined(OS_CHROMEOS)
- if (disable_garbage_collection_)
- return;
-#endif
-
- if (extension_prefs_->pref_service()->ReadOnly())
- return;
-
- bool clean_temp_dir = true;
-
- if (pending_extension_manager()->HasPendingExtensions()) {
- // Don't garbage collect temp dir while there are pending installations,
- // which may be using the temporary installation directory. Try to garbage
- // collect again later.
- clean_temp_dir = false;
- base::MessageLoop::current()->PostDelayedTask(
- FROM_HERE,
- base::Bind(&ExtensionService::GarbageCollectExtensions, AsWeakPtr()),
- base::TimeDelta::FromSeconds(kGarbageCollectRetryDelay));
- }
-
- scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> info(
- extension_prefs_->GetInstalledExtensionsInfo());
-
- std::multimap<std::string, base::FilePath> extension_paths;
- for (size_t i = 0; i < info->size(); ++i)
- extension_paths.insert(std::make_pair(info->at(i)->extension_id,
- info->at(i)->extension_path));
-
- info = extension_prefs_->GetAllDelayedInstallInfo();
- for (size_t i = 0; i < info->size(); ++i)
- extension_paths.insert(std::make_pair(info->at(i)->extension_id,
- info->at(i)->extension_path));
-
- if (!GetFileTaskRunner()->PostTask(
- FROM_HERE,
- base::Bind(
- &extension_file_util::GarbageCollectExtensions,
- install_directory_,
- extension_paths,
- clean_temp_dir))) {
- NOTREACHED();
- }
-}
-
void ExtensionService::SetReadyAndNotifyListeners() {
ready_->Signal();
content::NotificationService::current()->Notify(
@@ -2094,7 +2030,7 @@ void ExtensionService::OnExtensionInstalled(
}
ImportStatus status = SatisfyImports(extension);
- if (installs_delayed_for_gc()) {
+ if (installs_delayed_for_gc_) {
extension_prefs_->SetDelayedInstallInfo(
extension,
initial_state,
@@ -2549,44 +2485,14 @@ bool ExtensionService::ShouldDelayExtensionUpdate(
}
}
-void ExtensionService::GarbageCollectIsolatedStorage() {
- scoped_ptr<base::hash_set<base::FilePath> > active_paths(
- new base::hash_set<base::FilePath>());
- const ExtensionSet& extensions = registry_->enabled_extensions();
- for (ExtensionSet::const_iterator it = extensions.begin();
- it != extensions.end(); ++it) {
- if (extensions::AppIsolationInfo::HasIsolatedStorage(it->get())) {
- active_paths->insert(BrowserContext::GetStoragePartitionForSite(
- profile_,
- extensions::util::GetSiteForExtensionId(
- (*it)->id(), profile()))->GetPath());
- }
- }
-
- // The data of ephemeral apps can outlive their cache lifetime. Ensure
- // they are not garbage collected.
- scoped_ptr<extensions::ExtensionPrefs::ExtensionsInfo> evicted_apps_info(
- extension_prefs_->GetEvictedEphemeralAppsInfo());
- for (size_t i = 0; i < evicted_apps_info->size(); ++i) {
- extensions::ExtensionInfo* info = evicted_apps_info->at(i).get();
- if (extensions::util::HasIsolatedStorage(*info)) {
- active_paths->insert(BrowserContext::GetStoragePartitionForSite(
- profile_,
- extensions::util::GetSiteForExtensionId(
- info->extension_id, profile()))->GetPath());
- }
- }
-
- DCHECK(!installs_delayed_for_gc());
- set_installs_delayed_for_gc(true);
- BrowserContext::GarbageCollectStoragePartitions(
- profile_, active_paths.Pass(),
- base::Bind(&ExtensionService::OnGarbageCollectIsolatedStorageFinished,
- AsWeakPtr()));
+void ExtensionService::OnGarbageCollectIsolatedStorageStart() {
+ DCHECK(!installs_delayed_for_gc_);
+ installs_delayed_for_gc_ = true;
}
void ExtensionService::OnGarbageCollectIsolatedStorageFinished() {
- set_installs_delayed_for_gc(false);
+ DCHECK(installs_delayed_for_gc_);
+ installs_delayed_for_gc_ = false;
MaybeFinishDelayedInstallations();
}
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 266f47c..2c02b4f 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -51,6 +51,7 @@ class BrowserEventRouter;
class ComponentLoader;
class CrxInstaller;
class ExtensionActionStorageManager;
+class ExtensionGarbageCollector;
class ExtensionRegistry;
class ExtensionSystem;
class ExtensionToolbarModel;
@@ -271,9 +272,6 @@ class ExtensionService
// Reloads all extensions. Does not notify that extensions are ready.
void ReloadExtensionsForTest();
- // Scan the extension directory and clean up the cruft.
- void GarbageCollectExtensions();
-
// Returns true if |url| should get extension api bindings and be permitted
// to make api calls. Note that this is independent of what extension
// permissions the given extension has been granted.
@@ -486,6 +484,14 @@ class ExtensionService
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
+ // Postpone installations so that we don't have to worry about race
+ // conditions.
+ void OnGarbageCollectIsolatedStorageStart();
+
+ // Restart any extension installs which were delayed for isolated storage
+ // garbage collection.
+ void OnGarbageCollectIsolatedStorageFinished();
+
// Record a histogram using the PermissionMessage enum values for each
// permission in |e|.
// NOTE: If this is ever called with high frequency, the implementation may
@@ -505,6 +511,10 @@ class ExtensionService
base::WeakPtr<ExtensionService> AsWeakPtr() { return base::AsWeakPtr(this); }
+ extensions::ExtensionGarbageCollector* garbage_collector() {
+ return garbage_collector_.get();
+ }
+
bool browser_terminating() const { return browser_terminating_; }
// For testing.
@@ -530,15 +540,6 @@ class ExtensionService
void AddUpdateObserver(extensions::UpdateObserver* observer);
void RemoveUpdateObserver(extensions::UpdateObserver* observer);
-#if defined(OS_CHROMEOS)
- void disable_garbage_collection() {
- disable_garbage_collection_ = true;
- }
- void enable_garbage_collection() {
- disable_garbage_collection_ = false;
- }
-#endif
-
private:
// Populates greylist_.
void LoadGreylistFromPrefs();
@@ -600,11 +601,6 @@ class ExtensionService
bool ShouldDelayExtensionUpdate(const std::string& extension_id,
bool wait_for_idle) const;
- // Helper to search storage directories for extensions with isolated storage
- // that have been orphaned by an uninstall.
- void GarbageCollectIsolatedStorage();
- void OnGarbageCollectIsolatedStorageFinished();
-
// extensions::Blacklist::Observer implementation.
virtual void OnBlacklistUpdated() OVERRIDE;
@@ -623,13 +619,6 @@ class ExtensionService
const ExtensionIdSet& unchanged,
const extensions::Blacklist::BlacklistStateMap& state_map);
- // Controls if installs are delayed. See comment for
- // |installs_delayed_for_gc_|.
- void set_installs_delayed_for_gc(bool value) {
- installs_delayed_for_gc_ = value;
- }
- bool installs_delayed_for_gc() const { return installs_delayed_for_gc_; }
-
// Used only by test code.
void UnloadAllExtensionsInternal();
@@ -759,15 +748,11 @@ class ExtensionService
scoped_ptr<extensions::ManagementPolicy::Provider>
shared_module_policy_provider_;
- ObserverList<extensions::UpdateObserver, true> update_observers_;
+ // The ExtensionGarbageCollector to clean up all the garbage that leaks into
+ // the extensions directory.
+ scoped_ptr<extensions::ExtensionGarbageCollector> garbage_collector_;
-#if defined(OS_CHROMEOS)
- // TODO(rkc): HACK alert - this is only in place to allow the
- // kiosk_mode_screensaver to prevent its extension from getting garbage
- // collected. Remove this once KioskModeScreensaver is removed.
- // See crbug.com/280363
- bool disable_garbage_collection_;
-#endif
+ ObserverList<extensions::UpdateObserver, true> update_observers_;
FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest,
InstallAppsWithUnlimtedStorage);
diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc
index 3834a7c..b88d74f 100644
--- a/chrome/browser/extensions/extension_service_unittest.cc
+++ b/chrome/browser/extensions/extension_service_unittest.cc
@@ -1465,118 +1465,6 @@ TEST_F(ExtensionServiceTest, LoadAllExtensionsFromDirectoryFail) {
base::UTF16ToUTF8(GetErrors()[3]);
};
-// Test that partially deleted extensions are cleaned up during startup
-// Test loading bad extensions from the profile directory.
-TEST_F(ExtensionServiceTest, CleanupOnStartup) {
- InitPluginService();
- InitializeGoodInstalledExtensionService();
-
- // Simulate that one of them got partially deleted by clearing its pref.
- {
- DictionaryPrefUpdate update(profile_->GetPrefs(), "extensions.settings");
- base::DictionaryValue* dict = update.Get();
- ASSERT_TRUE(dict != NULL);
- dict->Remove("behllobkkfkfnphdnhnkndlbkcpglgmj", NULL);
- }
-
- service_->Init();
- // A delayed task to call GarbageCollectExtensions is posted by
- // ExtensionService::Init. As the test won't wait for the delayed task to
- // be called, call it manually instead.
- service_->GarbageCollectExtensions();
- // Wait for GarbageCollectExtensions task to complete.
- base::RunLoop().RunUntilIdle();
-
- base::FileEnumerator dirs(extensions_install_dir_, false,
- base::FileEnumerator::DIRECTORIES);
- size_t count = 0;
- while (!dirs.Next().empty())
- count++;
-
- // We should have only gotten two extensions now.
- EXPECT_EQ(2u, count);
-
- // And extension1 dir should now be toast.
- base::FilePath extension_dir = extensions_install_dir_
- .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj");
- ASSERT_FALSE(base::PathExists(extension_dir));
-}
-
-// Test that GarbageCollectExtensions deletes the right versions of an
-// extension.
-TEST_F(ExtensionServiceTest, GarbageCollectWithPendingUpdates) {
- InitPluginService();
-
- base::FilePath source_install_dir = data_dir_
- .AppendASCII("pending_updates")
- .AppendASCII("Extensions");
- base::FilePath pref_path =
- source_install_dir.DirName().Append(chrome::kPreferencesFilename);
-
- InitializeInstalledExtensionService(pref_path, source_install_dir);
-
- // This is the directory that is going to be deleted, so make sure it actually
- // is there before the garbage collection.
- ASSERT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "hpiknbiabeeppbpihjehijgoemciehgk/3")));
-
- service_->GarbageCollectExtensions();
- // Wait for GarbageCollectExtensions task to complete.
- base::RunLoop().RunUntilIdle();
-
- // Verify that the pending update for the first extension didn't get
- // deleted.
- EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0")));
- EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0")));
- EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "hpiknbiabeeppbpihjehijgoemciehgk/2")));
- EXPECT_FALSE(base::PathExists(extensions_install_dir_.AppendASCII(
- "hpiknbiabeeppbpihjehijgoemciehgk/3")));
-}
-
-// Test that pending updates are properly handled on startup.
-TEST_F(ExtensionServiceTest, UpdateOnStartup) {
- InitPluginService();
-
- base::FilePath source_install_dir = data_dir_
- .AppendASCII("pending_updates")
- .AppendASCII("Extensions");
- base::FilePath pref_path =
- source_install_dir.DirName().Append(chrome::kPreferencesFilename);
-
- InitializeInstalledExtensionService(pref_path, source_install_dir);
-
- // This is the directory that is going to be deleted, so make sure it actually
- // is there before the garbage collection.
- ASSERT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "hpiknbiabeeppbpihjehijgoemciehgk/3")));
-
- service_->Init();
- // A delayed task to call GarbageCollectExtensions is posted by
- // ExtensionService::Init. As the test won't wait for the delayed task to
- // be called, call it manually instead.
- service_->GarbageCollectExtensions();
- // Wait for GarbageCollectExtensions task to complete.
- base::RunLoop().RunUntilIdle();
-
- // Verify that the pending update for the first extension got installed.
- EXPECT_FALSE(base::PathExists(extensions_install_dir_.AppendASCII(
- "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0")));
- EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "bjafgdebaacbbbecmhlhpofkepfkgcpa/2.0")));
- EXPECT_TRUE(base::PathExists(extensions_install_dir_.AppendASCII(
- "hpiknbiabeeppbpihjehijgoemciehgk/2")));
- EXPECT_FALSE(base::PathExists(extensions_install_dir_.AppendASCII(
- "hpiknbiabeeppbpihjehijgoemciehgk/3")));
-
- // Make sure update information got deleted.
- ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_.get());
- EXPECT_FALSE(
- prefs->GetDelayedInstallInfo("bjafgdebaacbbbecmhlhpofkepfkgcpa"));
-}
-
// Test various cases for delayed install because of missing imports.
TEST_F(ExtensionServiceTest, PendingImports) {
InitPluginService();