Add garbage collection for shared extensions on Chrome OS

Chrome OS specific garbage collection in addition to profile specific
clean up also cleans up shared location but only once regardless number
of logged in users.

BUG=235263
TEST=unittest

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=273763

R=asargent@chromium.org

Review URL: https://codereview.chromium.org/303693011

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@273916 0039d316-1c4b-4281-b951-d872f2087c98
parent c9cace0f
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
#include "chrome/browser/chromeos/policy/app_pack_updater.h" #include "chrome/browser/chromeos/policy/app_pack_updater.h"
#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/extensions/extension_garbage_collector.h" #include "chrome/browser/extensions/extension_garbage_collector_chromeos.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/sandboxed_unpacker.h" #include "chrome/browser/extensions/sandboxed_unpacker.h"
#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" #include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h"
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
#include "ui/wm/core/user_activity_detector.h" #include "ui/wm/core/user_activity_detector.h"
using extensions::Extension; using extensions::Extension;
using extensions::ExtensionGarbageCollector; using extensions::ExtensionGarbageCollectorChromeOS;
using extensions::SandboxedUnpacker; using extensions::SandboxedUnpacker;
namespace chromeos { namespace chromeos {
...@@ -47,11 +47,11 @@ ExtensionService* GetDefaultExtensionService() { ...@@ -47,11 +47,11 @@ ExtensionService* GetDefaultExtensionService() {
default_profile)->extension_service(); default_profile)->extension_service();
} }
ExtensionGarbageCollector* GetDefaultExtensionGarbageCollector() { ExtensionGarbageCollectorChromeOS* GetDefaultExtensionGarbageCollector() {
Profile* default_profile = ProfileHelper::GetSigninProfile(); Profile* default_profile = ProfileHelper::GetSigninProfile();
if (!default_profile) if (!default_profile)
return NULL; return NULL;
return ExtensionGarbageCollector::Get(default_profile); return ExtensionGarbageCollectorChromeOS::Get(default_profile);
} }
typedef base::Callback<void( typedef base::Callback<void(
...@@ -116,7 +116,7 @@ void ScreensaverUnpackerClient::LoadScreensaverExtension( ...@@ -116,7 +116,7 @@ void ScreensaverUnpackerClient::LoadScreensaverExtension(
// TODO(rkc): This is a HACK, please remove this method from extension // TODO(rkc): This is a HACK, please remove this method from extension
// service once this code is deprecated. See crbug.com/280363 // service once this code is deprecated. See crbug.com/280363
ExtensionGarbageCollector* gc = GetDefaultExtensionGarbageCollector(); ExtensionGarbageCollectorChromeOS* gc = GetDefaultExtensionGarbageCollector();
if (gc) if (gc)
gc->disable_garbage_collection(); gc->disable_garbage_collection();
...@@ -184,7 +184,8 @@ KioskModeScreensaver::~KioskModeScreensaver() { ...@@ -184,7 +184,8 @@ KioskModeScreensaver::~KioskModeScreensaver() {
if (!extension_base_path_.empty()) { if (!extension_base_path_.empty()) {
// TODO(rkc): This is a HACK, please remove this method from extension // TODO(rkc): This is a HACK, please remove this method from extension
// service once this code is deprecated. See crbug.com/280363 // service once this code is deprecated. See crbug.com/280363
ExtensionGarbageCollector* gc = GetDefaultExtensionGarbageCollector(); ExtensionGarbageCollectorChromeOS* gc =
GetDefaultExtensionGarbageCollector();
if (gc) if (gc)
gc->enable_garbage_collection(); gc->enable_garbage_collection();
......
...@@ -153,6 +153,11 @@ bool FakeUserManager::IsKnownUser(const std::string& email) const { ...@@ -153,6 +153,11 @@ bool FakeUserManager::IsKnownUser(const std::string& email) const {
} }
const User* FakeUserManager::FindUser(const std::string& email) const { const User* FakeUserManager::FindUser(const std::string& email) const {
const UserList& users = GetUsers();
for (UserList::const_iterator it = users.begin(); it != users.end(); ++it) {
if ((*it)->email() == email)
return *it;
}
return NULL; return NULL;
} }
......
...@@ -16,10 +16,12 @@ ...@@ -16,10 +16,12 @@
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "base/sys_info.h" #include "base/sys_info.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/login/users/user_manager.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chromeos/chromeos_switches.h" #include "chromeos/chromeos_switches.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h" #include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/file_util.h" #include "extensions/common/file_util.h"
...@@ -30,14 +32,8 @@ using content::BrowserThread; ...@@ -30,14 +32,8 @@ using content::BrowserThread;
namespace extensions { namespace extensions {
namespace { namespace {
// A dictionary that maps shared extension IDs to version/paths/users. // Path to shared extensions install dir.
const char kSharedExtensions[] = "SharedExtensions"; const char kSharedExtensionsDir[] = "/var/cache/shared_extensions";
// Name of path attribute in shared extensions map.
const char kSharedExtensionPath[] = "path";
// Name of users attribute (list of user emails) in shared extensions map.
const char kSharedExtensionUsers[] = "users";
// Shared install dir overrider for tests only. // Shared install dir overrider for tests only.
static const base::FilePath* g_shared_install_dir_override = NULL; static const base::FilePath* g_shared_install_dir_override = NULL;
...@@ -113,9 +109,12 @@ class ExtensionAssetsManagerHelper { ...@@ -113,9 +109,12 @@ class ExtensionAssetsManagerHelper {
} // namespace } // namespace
// Path to shared extensions install dir. const char ExtensionAssetsManagerChromeOS::kSharedExtensions[] =
const char ExtensionAssetsManagerChromeOS::kSharedExtensionsDir[] = "SharedExtensions";
"/var/cache/shared_extensions";
const char ExtensionAssetsManagerChromeOS::kSharedExtensionPath[] = "path";
const char ExtensionAssetsManagerChromeOS::kSharedExtensionUsers[] = "users";
ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { } ExtensionAssetsManagerChromeOS::ExtensionAssetsManagerChromeOS() { }
...@@ -182,6 +181,49 @@ void ExtensionAssetsManagerChromeOS::UninstallExtension( ...@@ -182,6 +181,49 @@ void ExtensionAssetsManagerChromeOS::UninstallExtension(
} }
} }
// static
base::FilePath ExtensionAssetsManagerChromeOS::GetSharedInstallDir() {
if (g_shared_install_dir_override)
return *g_shared_install_dir_override;
else
return base::FilePath(kSharedExtensionsDir);
}
// static
bool ExtensionAssetsManagerChromeOS::CleanUpSharedExtensions(
std::multimap<std::string, base::FilePath>* live_extension_paths) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PrefService* local_state = g_browser_process->local_state();
// It happens in many unit tests.
if (!local_state)
return false;
DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
std::vector<std::string> extensions;
extensions.reserve(shared_extensions->size());
for (base::DictionaryValue::Iterator it(*shared_extensions);
!it.IsAtEnd(); it.Advance()) {
extensions.push_back(it.key());
}
for (std::vector<std::string>::iterator it = extensions.begin();
it != extensions.end(); it++) {
base::DictionaryValue* extension_info = NULL;
if (!shared_extensions->GetDictionary(*it, &extension_info)) {
NOTREACHED();
return false;
}
if (!CleanUpExtension(*it, extension_info, live_extension_paths)) {
return false;
}
if (!extension_info->size())
shared_extensions->RemoveWithoutPathExpansion(*it, NULL);
}
return true;
}
// static // static
void ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting( void ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting(
const base::FilePath& install_dir) { const base::FilePath& install_dir) {
...@@ -198,14 +240,6 @@ base::SequencedTaskRunner* ExtensionAssetsManagerChromeOS::GetFileTaskRunner( ...@@ -198,14 +240,6 @@ base::SequencedTaskRunner* ExtensionAssetsManagerChromeOS::GetFileTaskRunner(
return extension_service->GetFileTaskRunner(); return extension_service->GetFileTaskRunner();
} }
// static
base::FilePath ExtensionAssetsManagerChromeOS::GetSharedInstallDir() {
if (g_shared_install_dir_override)
return *g_shared_install_dir_override;
else
return base::FilePath(kSharedExtensionsDir);
}
// static // static
bool ExtensionAssetsManagerChromeOS::CanShareAssets( bool ExtensionAssetsManagerChromeOS::CanShareAssets(
const Extension* extension) { const Extension* extension) {
...@@ -230,6 +264,28 @@ void ExtensionAssetsManagerChromeOS::CheckSharedExtension( ...@@ -230,6 +264,28 @@ void ExtensionAssetsManagerChromeOS::CheckSharedExtension(
InstallExtensionCallback callback) { InstallExtensionCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
const std::string& user_id = profile->GetProfileName();
chromeos::UserManager* user_manager = chromeos::UserManager::Get();
if (!user_manager) {
NOTREACHED();
return;
}
if (user_manager->IsUserNonCryptohomeDataEphemeral(user_id) ||
!user_manager->IsLoggedInAsRegularUser()) {
// Don't cache anything in shared location for ephemeral user or special
// user types.
ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
FROM_HERE,
base::Bind(&ExtensionAssetsManagerChromeOS::InstallLocalExtension,
id,
version,
unpacked_extension_root,
local_install_dir,
callback));
return;
}
PrefService* local_state = g_browser_process->local_state(); PrefService* local_state = g_browser_process->local_state();
DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions); DictionaryPrefUpdate shared_extensions(local_state, kSharedExtensions);
base::DictionaryValue* extension_info = NULL; base::DictionaryValue* extension_info = NULL;
...@@ -242,19 +298,18 @@ void ExtensionAssetsManagerChromeOS::CheckSharedExtension( ...@@ -242,19 +298,18 @@ void ExtensionAssetsManagerChromeOS::CheckSharedExtension(
version_info->GetString(kSharedExtensionPath, &shared_path) && version_info->GetString(kSharedExtensionPath, &shared_path) &&
version_info->GetList(kSharedExtensionUsers, &users)) { version_info->GetList(kSharedExtensionUsers, &users)) {
// This extension version already in shared location. // This extension version already in shared location.
const std::string& user_name = profile->GetProfileName();
size_t users_size = users->GetSize(); size_t users_size = users->GetSize();
bool user_found = false; bool user_found = false;
for (size_t i = 0; i < users_size; i++) { for (size_t i = 0; i < users_size; i++) {
std::string temp; std::string temp;
if (users->GetString(i, &temp) && temp == user_name) { if (users->GetString(i, &temp) && temp == user_id) {
// Re-installation for the same user. // Re-installation for the same user.
user_found = true; user_found = true;
break; break;
} }
} }
if (!user_found) if (!user_found)
users->AppendString(user_name); users->AppendString(user_id);
// unpacked_extension_root will be deleted by CrxInstaller. // unpacked_extension_root will be deleted by CrxInstaller.
ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask( ExtensionAssetsManagerChromeOS::GetFileTaskRunner(profile)->PostTask(
...@@ -406,9 +461,12 @@ void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused( ...@@ -406,9 +461,12 @@ void ExtensionAssetsManagerChromeOS::MarkSharedExtensionUnused(
extension_info->RemoveWithoutPathExpansion(*it, NULL); extension_info->RemoveWithoutPathExpansion(*it, NULL);
} }
} }
// Don't remove extension dir in shared location. It will be removed by GC if (!extension_info->size()) {
// when it is safe to do so, and this avoids a race condition between shared_extensions->RemoveWithoutPathExpansion(id, NULL);
// concurrent uninstall by one user and install by another. // Don't remove extension dir in shared location. It will be removed by GC
// when it is safe to do so, and this avoids a race condition between
// concurrent uninstall by one user and install by another.
}
} }
// static // static
...@@ -418,4 +476,83 @@ void ExtensionAssetsManagerChromeOS::DeleteSharedVersion( ...@@ -418,4 +476,83 @@ void ExtensionAssetsManagerChromeOS::DeleteSharedVersion(
base::DeleteFile(shared_version_dir, true); // recursive. base::DeleteFile(shared_version_dir, true); // recursive.
} }
// static
bool ExtensionAssetsManagerChromeOS::CleanUpExtension(
const std::string& id,
base::DictionaryValue* extension_info,
std::multimap<std::string, base::FilePath>* live_extension_paths) {
chromeos::UserManager* user_manager = chromeos::UserManager::Get();
if (!user_manager) {
NOTREACHED();
return false;
}
std::vector<std::string> versions;
versions.reserve(extension_info->size());
for (base::DictionaryValue::Iterator it(*extension_info);
!it.IsAtEnd(); it.Advance()) {
versions.push_back(it.key());
}
for (std::vector<std::string>::const_iterator it = versions.begin();
it != versions.end(); it++) {
base::DictionaryValue* version_info = NULL;
base::ListValue* users = NULL;
std::string shared_path;
if (!extension_info->GetDictionaryWithoutPathExpansion(*it,
&version_info) ||
!version_info->GetList(kSharedExtensionUsers, &users) ||
!version_info->GetString(kSharedExtensionPath, &shared_path)) {
NOTREACHED();
return false;
}
size_t num_users = users->GetSize();
for (size_t i = 0; i < num_users; i++) {
std::string user_id;
if (!users->GetString(i, &user_id)) {
NOTREACHED();
return false;
}
const chromeos::User* user = user_manager->FindUser(user_id);
bool not_used = false;
if (!user) {
not_used = true;
} else if (user->is_logged_in()) {
// For logged in user also check that this path is actually used as
// installed extension or as delayed install.
Profile* profile = user_manager->GetProfileByUser(user);
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile);
if (!extension_prefs || extension_prefs->pref_service()->ReadOnly())
return false;
scoped_ptr<ExtensionInfo> info =
extension_prefs->GetInstalledExtensionInfo(id);
if (!info || info->extension_path != base::FilePath(shared_path)) {
info = extension_prefs->GetDelayedInstallInfo(id);
if (!info || info->extension_path != base::FilePath(shared_path)) {
not_used = true;
}
}
}
if (not_used) {
users->Remove(i, NULL);
i--;
num_users--;
}
}
if (num_users) {
live_extension_paths->insert(
std::make_pair(id, base::FilePath(shared_path)));
} else {
extension_info->RemoveWithoutPathExpansion(*it, NULL);
}
}
return true;
}
} // namespace extensions } // namespace extensions
...@@ -5,12 +5,15 @@ ...@@ -5,12 +5,15 @@
#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_ASSETS_MANAGER_CHROMEOS_H_ #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_ASSETS_MANAGER_CHROMEOS_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_ASSETS_MANAGER_CHROMEOS_H_ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_ASSETS_MANAGER_CHROMEOS_H_
#include <map>
#include "chrome/browser/extensions/extension_assets_manager.h" #include "chrome/browser/extensions/extension_assets_manager.h"
template <typename T> struct DefaultSingletonTraits; template <typename T> struct DefaultSingletonTraits;
class PrefRegistrySimple; class PrefRegistrySimple;
namespace base { namespace base {
class DictionaryValue;
class SequencedTaskRunner; class SequencedTaskRunner;
} }
...@@ -22,8 +25,14 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager { ...@@ -22,8 +25,14 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager {
public: public:
static ExtensionAssetsManagerChromeOS* GetInstance(); static ExtensionAssetsManagerChromeOS* GetInstance();
// Path to shared extensions install dir. // A dictionary that maps shared extension IDs to version/paths/users.
static const char kSharedExtensionsDir[]; static const char kSharedExtensions[];
// Name of path attribute in shared extensions map.
static const char kSharedExtensionPath[];
// Name of users attribute (list of user emails) in shared extensions map.
static const char kSharedExtensionUsers[];
// Register shared assets related preferences. // Register shared assets related preferences.
static void RegisterPrefs(PrefRegistrySimple* registry); static void RegisterPrefs(PrefRegistrySimple* registry);
...@@ -40,6 +49,16 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager { ...@@ -40,6 +49,16 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager {
const base::FilePath& local_install_dir, const base::FilePath& local_install_dir,
const base::FilePath& extension_root) OVERRIDE; const base::FilePath& extension_root) OVERRIDE;
// Return shared install dir.
static base::FilePath GetSharedInstallDir();
// Cleans up shared extensions list in preferences and returns list of
// extension IDs and version paths that are in use in |live_extension_paths|.
// Files on disk are not removed. Must be called on UI thread.
// Returns |false| in case of errors.
static bool CleanUpSharedExtensions(
std::multimap<std::string, base::FilePath>* live_extension_paths);
static void SetSharedInstallDirForTesting(const base::FilePath& install_dir); static void SetSharedInstallDirForTesting(const base::FilePath& install_dir);
private: private:
...@@ -52,9 +71,6 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager { ...@@ -52,9 +71,6 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager {
// the profile. // the profile.
static base::SequencedTaskRunner* GetFileTaskRunner(Profile* profile); static base::SequencedTaskRunner* GetFileTaskRunner(Profile* profile);
// Return shared install dir.
static base::FilePath GetSharedInstallDir();
// Return |true| if |extension| can be installed in a shared place for all // Return |true| if |extension| can be installed in a shared place for all
// users on the device. // users on the device.
static bool CanShareAssets(const Extension* extension); static bool CanShareAssets(const Extension* extension);
...@@ -97,6 +113,12 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager { ...@@ -97,6 +113,12 @@ class ExtensionAssetsManagerChromeOS : public ExtensionAssetsManager {
// Called on task runner thread to remove shared version. // Called on task runner thread to remove shared version.
static void DeleteSharedVersion(const base::FilePath& shared_version_dir); static void DeleteSharedVersion(const base::FilePath& shared_version_dir);
// Clean shared extension with given |id|.
static bool CleanUpExtension(
const std::string& id,
base::DictionaryValue* extension_info,
std::multimap<std::string, base::FilePath>* live_extension_paths);
DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerChromeOS); DISALLOW_COPY_AND_ASSIGN(ExtensionAssetsManagerChromeOS);
}; };
......
...@@ -96,34 +96,11 @@ void CheckExtensionDirectory(const base::FilePath& path, ...@@ -96,34 +96,11 @@ void CheckExtensionDirectory(const base::FilePath& path,
} }
} }
void GarbageCollectExtensionsOnFileThread(
const base::FilePath& install_directory,
const ExtensionPathsMultimap& extension_paths) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// 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);
}
}
} // namespace } // namespace
ExtensionGarbageCollector::ExtensionGarbageCollector( ExtensionGarbageCollector::ExtensionGarbageCollector(
content::BrowserContext* context) content::BrowserContext* context)
: context_(context), crx_installs_in_progress_(0), weak_factory_(this) { : context_(context), crx_installs_in_progress_(0), weak_factory_(this) {
#if defined(OS_CHROMEOS)
disable_garbage_collection_ = false;
#endif
ExtensionSystem* extension_system = ExtensionSystem::Get(context_); ExtensionSystem* extension_system = ExtensionSystem::Get(context_);
DCHECK(extension_system); DCHECK(extension_system);
...@@ -159,13 +136,29 @@ void ExtensionGarbageCollector::GarbageCollectExtensionsForTest() { ...@@ -159,13 +136,29 @@ void ExtensionGarbageCollector::GarbageCollectExtensionsForTest() {
GarbageCollectExtensions(); GarbageCollectExtensions();
} }
void ExtensionGarbageCollector::GarbageCollectExtensions() { // static
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); void ExtensionGarbageCollector::GarbageCollectExtensionsOnFileThread(
const base::FilePath& install_directory,
const ExtensionPathsMultimap& extension_paths) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
#if defined(OS_CHROMEOS) // Nothing to clean up if it doesn't exist.
if (disable_garbage_collection_) if (!base::DirectoryExists(install_directory))
return; return;
#endif
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);
}
}
void ExtensionGarbageCollector::GarbageCollectExtensions() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_); ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(context_);
DCHECK(extension_prefs); DCHECK(extension_prefs);
......
...@@ -31,12 +31,6 @@ class ExtensionGarbageCollector : public KeyedService, public InstallObserver { ...@@ -31,12 +31,6 @@ class ExtensionGarbageCollector : public KeyedService, public InstallObserver {
static ExtensionGarbageCollector* Get(content::BrowserContext* context); static ExtensionGarbageCollector* Get(content::BrowserContext* context);
#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. // Manually trigger GarbageCollectExtensions() for testing.
void GarbageCollectExtensionsForTest(); void GarbageCollectExtensionsForTest();
...@@ -48,7 +42,7 @@ class ExtensionGarbageCollector : public KeyedService, public InstallObserver { ...@@ -48,7 +42,7 @@ class ExtensionGarbageCollector : public KeyedService, public InstallObserver {
virtual void OnFinishCrxInstall(const std::string& extension_id, virtual void OnFinishCrxInstall(const std::string& extension_id,
bool success) OVERRIDE; bool success) OVERRIDE;
private: protected:
// Cleans up the extension install directory. It can end up with garbage in it // 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 // if extensions can't initially be removed when they are uninstalled (eg if a
// file is in use). // file is in use).
...@@ -56,24 +50,20 @@ class ExtensionGarbageCollector : public KeyedService, public InstallObserver { ...@@ -56,24 +50,20 @@ class ExtensionGarbageCollector : public KeyedService, public InstallObserver {
// found in the ExtensionPrefs. // found in the ExtensionPrefs.
// The "Temp" directory that is used during extension installation will get // The "Temp" directory that is used during extension installation will get
// removed iff there are no pending installations. // removed iff there are no pending installations.
void GarbageCollectExtensions(); virtual void GarbageCollectExtensions();
// Garbage collects apps/extensions isolated storage if it is uninstalled. // Garbage collects apps/extensions isolated storage if it is uninstalled.
// There is an exception for ephemeral apps because they can outlive their // There is an exception for ephemeral apps because they can outlive their
// cache lifetimes. // cache lifetimes.
void GarbageCollectIsolatedStorageIfNeeded(); void GarbageCollectIsolatedStorageIfNeeded();
static void GarbageCollectExtensionsOnFileThread(
const base::FilePath& install_directory,
const std::multimap<std::string, base::FilePath>& extension_paths);
// The BrowserContext associated with the GarbageCollector. // The BrowserContext associated with the GarbageCollector.
content::BrowserContext* context_; content::BrowserContext* context_;
#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
// The number of currently ongoing CRX installations. This is used to prevent // The number of currently ongoing CRX installations. This is used to prevent
// garbage collection from running while a CRX is being installed. // garbage collection from running while a CRX is being installed.
int crx_installs_in_progress_; int crx_installs_in_progress_;
......
// 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_chromeos.h"
#include "chrome/browser/chromeos/login/users/user_manager.h"
#include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
#include "chrome/browser/extensions/extension_service.h"
#include "extensions/browser/extension_system.h"
namespace extensions {
bool ExtensionGarbageCollectorChromeOS::shared_extensions_garbage_collected_ =
false;
ExtensionGarbageCollectorChromeOS::ExtensionGarbageCollectorChromeOS(
content::BrowserContext* context)
: ExtensionGarbageCollector(context),
disable_garbage_collection_(false) {
}
ExtensionGarbageCollectorChromeOS::~ExtensionGarbageCollectorChromeOS() {}
// static
ExtensionGarbageCollectorChromeOS* ExtensionGarbageCollectorChromeOS::Get(
content::BrowserContext* context) {
return static_cast<ExtensionGarbageCollectorChromeOS*>(
ExtensionGarbageCollector::Get(context));
}
// static
void ExtensionGarbageCollectorChromeOS::ClearGarbageCollectedForTesting() {
shared_extensions_garbage_collected_ = false;
}
void ExtensionGarbageCollectorChromeOS::GarbageCollectExtensions() {
if (disable_garbage_collection_)
return;
// Process per-profile extensions dir.
ExtensionGarbageCollector::GarbageCollectExtensions();
if (!shared_extensions_garbage_collected_ &&
CanGarbageCollectSharedExtensions()) {
GarbageCollectSharedExtensions();
shared_extensions_garbage_collected_ = true;
}
}
bool ExtensionGarbageCollectorChromeOS::CanGarbageCollectSharedExtensions() {
chromeos::UserManager* user_manager = chromeos::UserManager::Get();
if (!user_manager) {
NOTREACHED();
return false;
}
const chromeos::UserList& active_users = user_manager->GetLoggedInUsers();
for (size_t i = 0; i < active_users.size(); i++) {
Profile* profile = user_manager->GetProfileByUser(active_users[i]);
ExtensionGarbageCollectorChromeOS* gc =
ExtensionGarbageCollectorChromeOS::Get(profile);
if (gc->crx_installs_in_progress_ > 0)
return false;
}
return true;
}
void ExtensionGarbageCollectorChromeOS::GarbageCollectSharedExtensions() {
std::multimap<std::string, base::FilePath> paths;
if (ExtensionAssetsManagerChromeOS::CleanUpSharedExtensions(&paths)) {
ExtensionService* service =
ExtensionSystem::Get(context_)->extension_service();
if (!service->GetFileTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&GarbageCollectExtensionsOnFileThread,
ExtensionAssetsManagerChromeOS::GetSharedInstallDir(),
paths))) {
NOTREACHED();
}
}
}
} // namespace extensions
// 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_CHROMEOS_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_GARBAGE_COLLECTOR_CHROMEOS_H_
#include "chrome/browser/extensions/extension_garbage_collector.h"
namespace extensions {
// Chrome OS specific extensions garbage collector. In addition to base class
// it also cleans up extensions install directory in shared location, see
// ExtensionAssetsManagerChromeOS.
class ExtensionGarbageCollectorChromeOS : public ExtensionGarbageCollector {
public:
explicit ExtensionGarbageCollectorChromeOS(content::BrowserContext* context);
virtual ~ExtensionGarbageCollectorChromeOS();
static ExtensionGarbageCollectorChromeOS* Get(
content::BrowserContext* context);
// 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; }
// Clear shared_extensions_garbage_collected_ to initiate more than one
// GC in the same process for testing.
static void ClearGarbageCollectedForTesting();
private:
// Overriddes for ExtensionGarbageCollector:
virtual void GarbageCollectExtensions() OVERRIDE;
// Return true if there is no extension installation for all active profiles.
bool CanGarbageCollectSharedExtensions();
// Do GC for shared extensions dir.
void GarbageCollectSharedExtensions();
// 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_;
// Shared extensions need to be processed only once but instances of this
// class are created per-profile so this static variable prevents multiple
// processing.
static bool shared_extensions_garbage_collected_;
DISALLOW_COPY_AND_ASSIGN(ExtensionGarbageCollectorChromeOS);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_GARBAGE_COLLECTOR_CHROMEOS_H_
// 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/prefs/scoped_user_pref_update.h"
#include "base/prefs/testing_pref_service.h"
#include "base/strings/string_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/values.h"
#include "chrome/browser/chromeos/login/users/fake_user_manager.h"
#include "chrome/browser/chromeos/login/users/user_manager.h"
#include "chrome/browser/extensions/extension_assets_manager_chromeos.h"
#include "chrome/browser/extensions/extension_garbage_collector_chromeos.h"
#include "chrome/browser/extensions/extension_service_unittest.h"
#include "chrome/browser/prefs/browser_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/plugin_service.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/manifest_constants.h"
namespace {
const char kExtensionId1[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const char kExtensionId2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
} // namespace
namespace extensions {
class ExtensionGarbageCollectorChromeOSUnitTest
: public ExtensionServiceTestBase {
protected:
PrefService& local_state() { return local_state_; }
const base::FilePath& cache_dir() { return cache_dir_.path(); }
virtual void SetUp() OVERRIDE {
TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);
chrome::RegisterLocalState(local_state_.registry());
#if defined(ENABLE_PLUGINS)
content::PluginService::GetInstance()->Init();
#endif
InitializeGoodInstalledExtensionService();
// Need real IO thread.
service_->SetFileTaskRunnerForTesting(
content::BrowserThread::GetBlockingPool()
->GetSequencedTaskRunnerWithShutdownBehavior(
content::BrowserThread::GetBlockingPool()
->GetNamedSequenceToken("ext_install-"),
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN));
CHECK(cache_dir_.CreateUniqueTempDir());
ExtensionAssetsManagerChromeOS::SetSharedInstallDirForTesting(cache_dir());
ExtensionGarbageCollectorChromeOS::ClearGarbageCollectedForTesting();
// Initialize the UserManager singleton to a fresh FakeUserManager instance.
user_manager_enabler_.reset(
new chromeos::ScopedUserManagerEnabler(new chromeos::FakeUserManager));
GetFakeUserManager()->AddUser(chromeos::UserManager::kStubUser);
GetFakeUserManager()->LoginUser(chromeos::UserManager::kStubUser);
GetFakeUserManager()->SetProfileForUser(
GetFakeUserManager()->GetActiveUser(), profile_.get());
}
virtual void TearDown() OVERRIDE {
TestingBrowserProcess::GetGlobal()->SetLocalState(NULL);
}
void GarbageCollectExtensions() {
ExtensionGarbageCollector::Get(profile_.get())
->GarbageCollectExtensionsForTest();
// Wait for GarbageCollectExtensions task to complete.
content::BrowserThread::GetBlockingPool()->FlushForTesting();
}
base::FilePath CreateSharedExtensionDir(const std::string& id,
const std::string& version,
const base::FilePath& shared_dir) {
base::FilePath path = shared_dir.AppendASCII(id).AppendASCII(version);
CreateDirectory(path);
return path;
}
void CreateSharedExtensionPrefs(const std::string& id,
const std::string& version,
const std::string& users_string,
const base::FilePath& path) {
DictionaryPrefUpdate shared_extensions(&local_state_,
ExtensionAssetsManagerChromeOS::kSharedExtensions);
base::DictionaryValue* extension_info = NULL;
if (!shared_extensions->GetDictionary(id, &extension_info)) {
extension_info = new base::DictionaryValue;
shared_extensions->Set(id, extension_info);
}
base::DictionaryValue* version_info = new base::DictionaryValue;
extension_info->SetWithoutPathExpansion(version, version_info);
version_info->SetString(
ExtensionAssetsManagerChromeOS::kSharedExtensionPath, path.value());
base::ListValue* users = new base::ListValue;
version_info->Set(ExtensionAssetsManagerChromeOS::kSharedExtensionUsers,
users);
std::vector<std::string> users_list;
if (Tokenize(users_string, ",", &users_list)) {
for (size_t i = 0; i < users_list.size(); i++) {
users->AppendString(users_list[i]);
}
}
}
scoped_refptr<Extension> CreateExtension(const std::string& id,
const std::string& version,
const base::FilePath& path) {
base::DictionaryValue manifest;
manifest.SetString(manifest_keys::kName, "test");
manifest.SetString(manifest_keys::kVersion, version);
std::string error;
scoped_refptr<Extension> extension = Extension::Create(
path, Manifest::INTERNAL, manifest, Extension::NO_FLAGS, id, &error);
CHECK(extension.get()) << error;
CHECK_EQ(id, extension->id());
return extension;
}
ExtensionPrefs* GetExtensionPrefs() {
return ExtensionPrefs::Get(profile_.get());
}
chromeos::FakeUserManager* GetFakeUserManager() {
return static_cast<chromeos::FakeUserManager*>(
chromeos::UserManager::Get());
}
private:
scoped_ptr<chromeos::ScopedUserManagerEnabler> user_manager_enabler_;
TestingPrefServiceSimple local_state_;
base::ScopedTempDir cache_dir_;
};
// Test shared extensions clean up.
TEST_F(ExtensionGarbageCollectorChromeOSUnitTest, SharedExtensions) {
// Version for non-existing user.
base::FilePath path_id1_1 = CreateSharedExtensionDir(
kExtensionId1, "1.0", cache_dir());
CreateSharedExtensionPrefs(kExtensionId1, "1.0", "test@test.com", path_id1_1);
EXPECT_TRUE(base::PathExists(path_id1_1));
// Version for current user but the extension is not installed.
base::FilePath path_id1_2 = CreateSharedExtensionDir(
kExtensionId1, "2.0", cache_dir());
CreateSharedExtensionPrefs(
kExtensionId1, "2.0", chromeos::UserManager::kStubUser, path_id1_2);
EXPECT_TRUE(base::PathExists(path_id1_2));
// Version for current user that delayed install.
base::FilePath path_id2_1 = CreateSharedExtensionDir(
kExtensionId2, "1.0", cache_dir());
CreateSharedExtensionPrefs(
kExtensionId2, "1.0", chromeos::UserManager::kStubUser, path_id2_1);
scoped_refptr<Extension> extension2 = CreateExtension(kExtensionId2, "1.0",
path_id2_1);
GetExtensionPrefs()->SetDelayedInstallInfo(
extension2.get(),
Extension::ENABLED,
false,
false,
ExtensionPrefs::DELAY_REASON_WAIT_FOR_IDLE,
syncer::StringOrdinal(),
std::string());
EXPECT_TRUE(base::PathExists(path_id2_1));
GarbageCollectExtensions();
EXPECT_FALSE(base::PathExists(path_id1_1));
EXPECT_FALSE(base::PathExists(path_id1_2));
EXPECT_FALSE(base::PathExists(cache_dir().AppendASCII(kExtensionId1)));
EXPECT_TRUE(base::PathExists(path_id2_1));
const base::DictionaryValue* shared_extensions = local_state().GetDictionary(
ExtensionAssetsManagerChromeOS::kSharedExtensions);
ASSERT_TRUE(shared_extensions);
EXPECT_FALSE(shared_extensions->HasKey(kExtensionId1));
EXPECT_TRUE(shared_extensions->HasKey(kExtensionId2));
}
} // namespace extensions
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/extensions_browser_client.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/extensions/extension_garbage_collector_chromeos.h"
#endif
namespace extensions { namespace extensions {
// static // static
...@@ -40,7 +44,11 @@ ExtensionGarbageCollectorFactory::~ExtensionGarbageCollectorFactory() {} ...@@ -40,7 +44,11 @@ ExtensionGarbageCollectorFactory::~ExtensionGarbageCollectorFactory() {}
// static // static
KeyedService* ExtensionGarbageCollectorFactory::BuildInstanceFor( KeyedService* ExtensionGarbageCollectorFactory::BuildInstanceFor(
content::BrowserContext* context) { content::BrowserContext* context) {
#if defined(OS_CHROMEOS)
return new ExtensionGarbageCollectorChromeOS(context);
#else
return new ExtensionGarbageCollector(context); return new ExtensionGarbageCollector(context);
#endif
} }
KeyedService* ExtensionGarbageCollectorFactory::BuildServiceInstanceFor( KeyedService* ExtensionGarbageCollectorFactory::BuildServiceInstanceFor(
......
...@@ -718,6 +718,8 @@ ...@@ -718,6 +718,8 @@
'browser/extensions/extension_error_ui_default.h', 'browser/extensions/extension_error_ui_default.h',
'browser/extensions/extension_garbage_collector.cc', 'browser/extensions/extension_garbage_collector.cc',
'browser/extensions/extension_garbage_collector.h', 'browser/extensions/extension_garbage_collector.h',
'browser/extensions/extension_garbage_collector_chromeos.cc',
'browser/extensions/extension_garbage_collector_chromeos.h',
'browser/extensions/extension_garbage_collector_factory.cc', 'browser/extensions/extension_garbage_collector_factory.cc',
'browser/extensions/extension_garbage_collector_factory.h', 'browser/extensions/extension_garbage_collector_factory.h',
'browser/extensions/extension_gcm_app_handler.cc', 'browser/extensions/extension_gcm_app_handler.cc',
......
...@@ -948,6 +948,7 @@ ...@@ -948,6 +948,7 @@
'browser/extensions/extension_function_test_utils.cc', 'browser/extensions/extension_function_test_utils.cc',
'browser/extensions/extension_function_test_utils.h', 'browser/extensions/extension_function_test_utils.h',
'browser/extensions/extension_garbage_collector_unittest.cc', 'browser/extensions/extension_garbage_collector_unittest.cc',
'browser/extensions/extension_garbage_collector_chromeos_unittest.cc',
'browser/extensions/extension_gcm_app_handler_unittest.cc', 'browser/extensions/extension_gcm_app_handler_unittest.cc',
'browser/extensions/extension_icon_image_unittest.cc', 'browser/extensions/extension_icon_image_unittest.cc',
'browser/extensions/extension_icon_manager_unittest.cc', 'browser/extensions/extension_icon_manager_unittest.cc',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment