summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorfinnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-05 21:53:19 +0000
committerfinnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-05 21:53:19 +0000
commit25b3433897bab5e94dac9ab8a2f09b356792ab7d (patch)
tree50addd81b87cf947d52b489af4edeae4614013bc /chrome
parentc333ede219e0e33b5df85f02bb9d261d7033c9c1 (diff)
downloadchromium_src-25b3433897bab5e94dac9ab8a2f09b356792ab7d.zip
chromium_src-25b3433897bab5e94dac9ab8a2f09b356792ab7d.tar.gz
chromium_src-25b3433897bab5e94dac9ab8a2f09b356792ab7d.tar.bz2
Add this ability to install Extensions using preferences. Also known as: port the installation mechanism to other platforms.
We already have the ability to install extensions using a registry key. That works only on Windows so this new change adds the same but using preferences instead of the Registry. This will eventually allow us to pre-install certain extensions when we install Chrome. BUG=12060 TEST=Covered by unit tests, but to test manually: close Chrome, open your Preferences file (in your profile) and add this (after substituting all <values> in elbow brackets): "extensions": { "settings": { "<your_extension_id_lowercased>": { "external_crx": "<path_to_crx>", "external_version": "<crx version>" } }, }, ... then start Chrome. Your extension should get installed. Review URL: http://codereview.chromium.org/119195 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17777 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/extensions_service.cc308
-rw-r--r--chrome/browser/extensions/extensions_service.h45
-rw-r--r--chrome/browser/extensions/extensions_service_unittest.cc289
-rw-r--r--chrome/common/extensions/extension.cc46
-rw-r--r--chrome/common/extensions/extension.h34
-rw-r--r--chrome/common/extensions/extension_unpacker.cc13
6 files changed, 626 insertions, 109 deletions
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 87c14a2..2d8b039 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -30,6 +30,7 @@
#include "chrome/common/extensions/extension_unpacker.h"
#include "chrome/common/json_value_serializer.h"
#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/common/zip.h"
#include "chrome/common/url_constants.h"
@@ -43,7 +44,7 @@
#include "base/win_util.h"
#endif
-// ExtensionsService
+// ExtensionsService.
const char* ExtensionsService::kInstallDirectoryName = "Extensions";
const char* ExtensionsService::kCurrentVersionFileName = "Current Version";
@@ -63,10 +64,13 @@ struct ExtensionHeader {
const size_t kZipHashBytes = 32; // SHA-256
const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size.
-// A preference that keeps track of external extensions the user has
-// uninstalled.
-const wchar_t kUninstalledExternalPref[] =
- L"extensions.uninstalled_external_ids";
+// A preference that keeps track of extension settings. This is a dictionary
+// object read from the Preferences file, keyed off of extension id's.
+const wchar_t kExternalExtensionsPref[] = L"extensions.settings";
+
+// A preference keeping track of how the extension was installed.
+const wchar_t kLocation[] = L"location";
+const wchar_t kState[] = L"state";
// Registry key where registry defined extension installers live.
// TODO(port): Assuming this becomes a similar key into the appropriate
@@ -226,7 +230,7 @@ ExtensionsService::ExtensionsService(Profile* profile,
backend_(new ExtensionsServiceBackend(
install_directory_, g_browser_process->resource_dispatcher_host(),
frontend_loop, registry_path)) {
- prefs_->RegisterListPref(kUninstalledExternalPref);
+ prefs_->RegisterDictionaryPref(kExternalExtensionsPref);
}
ExtensionsService::~ExtensionsService() {
@@ -240,34 +244,23 @@ bool ExtensionsService::Init() {
// Start up the extension event routers.
ExtensionBrowserEventRouter::GetInstance()->Init();
-#if defined(OS_WIN)
+ scoped_ptr<DictionaryValue> external_extensions(new DictionaryValue);
+ GetExternalExtensions(external_extensions.get(), NULL);
- std::set<std::string> uninstalled_external_ids;
- const ListValue* list = prefs_->GetList(kUninstalledExternalPref);
- if (list) {
- for (size_t i = 0; i < list->GetSize(); ++i) {
- std::string val;
- bool ok = list->GetString(i, &val);
- DCHECK(ok);
- DCHECK(uninstalled_external_ids.find(val) ==
- uninstalled_external_ids.end());
- uninstalled_external_ids.insert(val);
- }
- }
+ scoped_ptr< std::set<std::string> >
+ killed_extensions(new std::set<std::string>);
+ GetExternalExtensions(NULL, killed_extensions.get());
- // TODO(erikkay): Should we monitor the registry during run as well?
backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(),
&ExtensionsServiceBackend::CheckForExternalUpdates,
- uninstalled_external_ids, scoped_refptr<ExtensionsService>(this)));
-#else
-
- // TODO(port)
-
-#endif
+ *killed_extensions.get(),
+ external_extensions.get(),
+ scoped_refptr<ExtensionsService>(this)));
backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(),
&ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory,
- scoped_refptr<ExtensionsService>(this)));
+ scoped_refptr<ExtensionsService>(this),
+ external_extensions.release()));
return true;
}
@@ -303,15 +296,17 @@ void ExtensionsService::UninstallExtension(const std::string& extension_id) {
// For external extensions, we save a preference reminding ourself not to try
// and install the extension anymore.
- if (extension->location() == Extension::EXTERNAL) {
- ListValue* list = prefs_->GetMutableList(kUninstalledExternalPref);
- list->Append(Value::CreateStringValue(extension->id()));
- prefs_->ScheduleSavePersistentPrefs();
+ if (Extension::IsExternalLocation(extension->location())) {
+ UpdateExtensionPref(ASCIIToWide(extension->id()), kState,
+ Value::CreateIntegerValue(Extension::KILLBIT), true);
+ } else {
+ UpdateExtensionPref(ASCIIToWide(extension->id()), kState,
+ Value::CreateIntegerValue(Extension::DISABLED), true);
}
// Tell the backend to start deleting installed extensions on the file thread.
if (extension->location() == Extension::INTERNAL ||
- extension->location() == Extension::EXTERNAL) {
+ Extension::IsExternalLocation(extension->location())) {
backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(),
&ExtensionsServiceBackend::UninstallExtension, extension_id));
}
@@ -326,6 +321,32 @@ void ExtensionsService::LoadExtension(const FilePath& extension_path) {
}
void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) {
+ // Sync with manually loaded extensions. Otherwise we won't know about them
+ // since they aren't installed in the normal way. Eventually, we want to not
+ // load extensions at all from directory, but use the Extension preferences
+ // as the truth for what is installed.
+ DictionaryValue* pref = NULL;
+ for (ExtensionList::const_iterator iter = new_extensions->begin();
+ iter != new_extensions->end(); ++iter) {
+ std::wstring extension_id = ASCIIToWide((*iter)->id());
+ pref = GetOrCreateExtensionPref(extension_id);
+ Extension::Location location;
+ Extension::State state;
+ if (!pref->GetInteger(kLocation, reinterpret_cast<int*>(&location)) ||
+ !pref->GetInteger(kState, reinterpret_cast<int*>(&state))) {
+ UpdateExtensionPref(extension_id,
+ kLocation, Value::CreateIntegerValue(Extension::INTERNAL), false);
+ UpdateExtensionPref(extension_id,
+ kState, Value::CreateIntegerValue(Extension::ENABLED), false);
+ } else {
+ // The kill-bit only applies to External extensions so this check fails
+ // for internal locations that have the kill-bit set. In other words,
+ // the kill-bit cannot be set unless the extension is external.
+ DCHECK(state != Extension::KILLBIT ||
+ Extension::IsExternalLocation(location));
+ }
+ }
+
// If extensions aren't enabled, we still want to add themes. However, themes
// should not trigger EXTENSIONS_LOADED.
// TODO(aa): This can be re-enabled when BUG 13128 is fixed.
@@ -350,6 +371,11 @@ void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) {
void ExtensionsService::OnExtensionInstalled(Extension* extension,
bool update) {
+ UpdateExtensionPref(ASCIIToWide(extension->id()), kState,
+ Value::CreateIntegerValue(Extension::ENABLED), false);
+ UpdateExtensionPref(ASCIIToWide(extension->id()), kLocation,
+ Value::CreateIntegerValue(Extension::INTERNAL), true);
+
// If the extension is a theme, tell the profile (and therefore ThemeProvider)
// to apply it.
if (extension->IsTheme()) {
@@ -365,6 +391,15 @@ void ExtensionsService::OnExtensionInstalled(Extension* extension,
}
}
+void ExtensionsService::OnExternalExtensionInstalled(
+ const std::string& id, Extension::Location location) {
+ DCHECK(Extension::IsExternalLocation(location));
+ UpdateExtensionPref(ASCIIToWide(id), kState,
+ Value::CreateIntegerValue(Extension::ENABLED), false);
+ UpdateExtensionPref(ASCIIToWide(id), kLocation,
+ Value::CreateIntegerValue(location), true);
+}
+
void ExtensionsService::OnExtensionVersionReinstalled(const std::string& id) {
Extension* extension = GetExtensionByID(id);
if (extension && extension->IsTheme()) {
@@ -384,6 +419,70 @@ Extension* ExtensionsService::GetExtensionByID(std::string id) {
return NULL;
}
+void ExtensionsService::GetExternalExtensions(
+ DictionaryValue* external_extensions,
+ std::set<std::string>* killed_extensions) {
+ const DictionaryValue* dict = prefs_->GetDictionary(kExternalExtensionsPref);
+ if (!dict || dict->GetSize() == 0)
+ return;
+
+ for (DictionaryValue::key_iterator i = dict->begin_keys();
+ i != dict->end_keys(); ++i) {
+ std::wstring key_name = *i;
+ DCHECK(Extension::IdIsValid(WideToASCII(key_name)));
+ DictionaryValue* extension = NULL;
+ if (!dict->GetDictionary(key_name, &extension)) {
+ NOTREACHED();
+ continue;
+ }
+
+ // Check to see if the extension has been killed.
+ Extension::State state;
+ if (extension->GetInteger(kState, reinterpret_cast<int*>(&state)) &&
+ state == static_cast<int>(Extension::KILLBIT)) {
+ if (killed_extensions) {
+ StringToLowerASCII(&key_name);
+ killed_extensions->insert(WideToASCII(key_name));
+ }
+ }
+ // Return all extensions found.
+ if (external_extensions) {
+ DictionaryValue* result =
+ static_cast<DictionaryValue*>(extension->DeepCopy());
+ StringToLowerASCII(&key_name);
+ external_extensions->Set(key_name, result);
+ }
+ }
+}
+
+DictionaryValue* ExtensionsService::GetOrCreateExtensionPref(
+ const std::wstring& extension_id) {
+ DictionaryValue* dict = prefs_->GetMutableDictionary(kExternalExtensionsPref);
+ DictionaryValue* extension = NULL;
+ if (!dict->GetDictionary(extension_id, &extension)) {
+ // Extension pref does not exist, create it.
+ extension = new DictionaryValue();
+ dict->Set(extension_id, extension);
+ }
+
+ return extension;
+}
+
+bool ExtensionsService::UpdateExtensionPref(const std::wstring& extension_id,
+ const std::wstring& key,
+ Value* data_value,
+ bool schedule_save) {
+ DictionaryValue* extension = GetOrCreateExtensionPref(extension_id);
+ if (!extension->Set(key, data_value)) {
+ NOTREACHED() << L"Cannot modify key: '" << key.c_str()
+ << "' for extension: '" << extension_id.c_str() << "'";
+ return false;
+ }
+
+ if (schedule_save)
+ prefs_->ScheduleSavePersistentPrefs();
+ return true;
+}
// ExtensionsServicesBackend
@@ -403,9 +502,11 @@ ExtensionsServiceBackend::ExtensionsServiceBackend(
}
void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory(
- scoped_refptr<ExtensionsService> frontend) {
+ scoped_refptr<ExtensionsService> frontend,
+ DictionaryValue* extension_prefs) {
frontend_ = frontend;
alert_on_error_ = false;
+ scoped_ptr<DictionaryValue> external_extensions(extension_prefs);
#if defined(OS_WIN)
// On POSIX, AbsolutePath() calls realpath() which returns NULL if
@@ -457,7 +558,7 @@ void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory(
if (!file_util::PathExists(current_version_path)) {
LOG(INFO) << "Deleting incomplete install for directory "
<< WideToASCII(extension_path.ToWStringHack()) << ".";
- file_util::Delete(extension_path, true); // recursive;
+ file_util::Delete(extension_path, true); // Recursive.
continue;
}
@@ -465,8 +566,17 @@ void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory(
if (!ReadCurrentVersion(extension_path, &current_version))
continue;
+ Extension::Location location;
+ DictionaryValue* pref = NULL;
+ external_extensions->GetDictionary(ASCIIToWide(extension_id), &pref);
+ if (!pref ||
+ !pref->GetInteger(kLocation, reinterpret_cast<int*>(&location))) {
+ location = Extension::INTERNAL;
+ }
FilePath version_path = extension_path.AppendASCII(current_version);
- if (CheckExternalUninstall(version_path, extension_id)) {
+ if (Extension::IsExternalLocation(location) &&
+ CheckExternalUninstall(external_extensions.get(),
+ version_path, extension_id)) {
// TODO(erikkay): Possibly defer this operation to avoid slowing initial
// load of extensions.
UninstallExtension(extension_id);
@@ -539,10 +649,12 @@ Extension* ExtensionsServiceBackend::LoadExtension(
}
FilePath external_marker = extension_path.AppendASCII(kExternalInstallFile);
- if (file_util::PathExists(external_marker))
- extension->set_location(Extension::EXTERNAL);
- else
+ if (file_util::PathExists(external_marker)) {
+ extension->set_location(
+ extension->ExternalExtensionInstallType(registry_path_));
+ } else {
extension->set_location(Extension::INTERNAL);
+ }
// Theme resource validation.
if (extension->IsTheme()) {
@@ -550,7 +662,7 @@ Extension* ExtensionsServiceBackend::LoadExtension(
DictionaryValue::key_iterator iter = images_value->begin_keys();
while (iter != images_value->end_keys()) {
std::string val;
- if (images_value->GetString(*iter, &val)) {
+ if (images_value->GetString(*iter , &val)) {
FilePath image_path = extension->path().AppendASCII(val);
if (!file_util::PathExists(image_path)) {
ReportExtensionLoadError(extension_path,
@@ -810,8 +922,12 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
// If an expected id was provided, make sure it matches.
if (!expected_id.empty() && expected_id != extension.id()) {
- ReportExtensionInstallError(extension_path,
- "ID in new extension manifest does not match expected ID.");
+ std::string error_msg = "ID in new extension manifest (";
+ error_msg += extension.id();
+ error_msg += ") does not match expected ID (";
+ error_msg += expected_id;
+ error_msg += ")";
+ ReportExtensionInstallError(extension_path, error_msg);
return;
}
@@ -894,8 +1010,9 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
// Load the extension immediately and then report installation success. We
// don't load extensions for external installs because external installation
// occurs before the normal startup so we just let startup pick them up. We
- // don't notify installation because there is no UI for external install so
- // there is nobody to notify.
+ // notify on installation of external extensions because we need to update
+ // the preferences for these extensions to reflect that they've just been
+ // installed.
if (!from_external) {
Extension* extension = LoadExtension(version_dir, true); // require id
CHECK(extension);
@@ -911,6 +1028,11 @@ void ExtensionsServiceBackend::OnExtensionUnpacked(
LOG(INFO) << "Done.";
// Hand off ownership of the loaded extensions to the frontend.
ReportExtensionsLoaded(extensions.release());
+ } else {
+ frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ frontend_, &ExtensionsService::OnExternalExtensionInstalled,
+ extension.id(),
+ extension.ExternalExtensionInstallType(registry_path_)));
}
scoped_version_dir.Take();
@@ -933,16 +1055,33 @@ void ExtensionsServiceBackend::ReportExtensionVersionReinstalled(
frontend_, &ExtensionsService::OnExtensionVersionReinstalled, id));
}
+bool ExtensionsServiceBackend::ShouldSkipInstallingExtension(
+ const std::set<std::string>& ids_to_ignore,
+ const std::string& id) {
+ if (ids_to_ignore.find(id) != ids_to_ignore.end()) {
+ LOG(INFO) << "Skipping uninstalled external extension " << id;
+ return true;
+ }
+ return false;
+}
+
+void ExtensionsServiceBackend::CheckVersionAndInstallExtension(
+ const std::string& id, const std::string& extension_version,
+ const FilePath& extension_path, bool from_external) {
+ if (ShouldInstall(id, extension_version))
+ InstallOrUpdateExtension(FilePath(extension_path), id, from_external);
+}
+
// Some extensions will autoupdate themselves externally from Chrome. These
// are typically part of some larger client application package. To support
-// these, the extension will register its location in the registry on Windows
-// (TODO(port): what about on other platforms?) and this code will periodically
+// these, the extension will register its location in the the preferences file
+// (and also, on Windows, in the registry) and this code will periodically
// check that location for a .crx file, which it will then install locally if
// a new version is available.
void ExtensionsServiceBackend::CheckForExternalUpdates(
std::set<std::string> ids_to_ignore,
+ DictionaryValue* extension_prefs,
scoped_refptr<ExtensionsService> frontend) {
-
// Note that this installation is intentionally silent (since it didn't
// go through the front-end). Extensions that are registered in this
// way are effectively considered 'pre-bundled', and so implicitly
@@ -951,6 +1090,44 @@ void ExtensionsServiceBackend::CheckForExternalUpdates(
alert_on_error_ = false;
frontend_ = frontend;
+ for (DictionaryValue::key_iterator i = extension_prefs->begin_keys();
+ i != extension_prefs->end_keys(); ++i) {
+ const std::wstring& extension_id = *i;
+ if (ShouldSkipInstallingExtension(ids_to_ignore, WideToASCII(extension_id)))
+ continue;
+
+ DictionaryValue* extension = NULL;
+ if (!extension_prefs->GetDictionary(extension_id, &extension)) {
+ NOTREACHED() << "Cannot read extension " << extension_id.c_str()
+ << " from dictionary.";
+ continue;
+ }
+
+ Extension::Location location;
+ if (extension->GetInteger(kLocation, reinterpret_cast<int*>(&location)) &&
+ location != Extension::EXTERNAL_PREF) {
+ continue;
+ }
+ Extension::State state;
+ if (extension->GetInteger(kState, reinterpret_cast<int*>(&state)) &&
+ state == Extension::KILLBIT) {
+ continue;
+ }
+
+ FilePath::StringType external_crx;
+ std::string external_version;
+ if (!extension->GetString(L"external_crx", &external_crx) ||
+ !extension->GetString(L"external_version", &external_version)) {
+ LOG(WARNING) << "Malformed extension dictionary for extension: "
+ << extension_id.c_str();
+ continue;
+ }
+
+ bool from_external = true;
+ CheckVersionAndInstallExtension(WideToASCII(extension_id), external_version,
+ FilePath(external_crx), from_external);
+ }
+
#if defined(OS_WIN)
// TODO(port): Pull this out into an interface. That will also allow us to
// test the behavior of external extensions.
@@ -959,8 +1136,7 @@ void ExtensionsServiceBackend::CheckForExternalUpdates(
while (iterator.Valid()) {
// Fold
std::string id = StringToLowerASCII(WideToASCII(iterator.Name()));
- if (ids_to_ignore.find(id) != ids_to_ignore.end()) {
- LOG(INFO) << "Skipping uninstalled external extension " << id;
+ if (ShouldSkipInstallingExtension(ids_to_ignore, id)) {
++iterator;
continue;
}
@@ -974,11 +1150,10 @@ void ExtensionsServiceBackend::CheckForExternalUpdates(
if (key.ReadValue(kRegistryExtensionPath, &extension_path)) {
std::wstring extension_version;
if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) {
- if (ShouldInstall(id, WideToASCII(extension_version))) {
- bool from_external = true;
- InstallOrUpdateExtension(FilePath(extension_path), id,
- from_external);
- }
+ bool from_external = true;
+ CheckVersionAndInstallExtension(
+ id, WideToASCII(extension_version), FilePath(extension_path),
+ from_external);
} else {
// TODO(erikkay): find a way to get this into about:extensions
LOG(WARNING) << "Missing value " << kRegistryExtensionVersion <<
@@ -992,16 +1167,26 @@ void ExtensionsServiceBackend::CheckForExternalUpdates(
}
++iterator;
}
-#else
- NOTREACHED();
#endif
}
-bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& version_path,
- const std::string& id) {
- FilePath external_file = version_path.AppendASCII(kExternalInstallFile);
- if (file_util::PathExists(external_file)) {
+bool ExtensionsServiceBackend::CheckExternalUninstall(
+ DictionaryValue* extension_prefs, const FilePath& version_path,
+ const std::string& id) {
+ // First check the preferences for the kill-bit.
+ Extension::Location location = Extension::INVALID;
+ DictionaryValue* extension = NULL;
+ if (extension_prefs->GetDictionary(ASCIIToWide(id), &extension)) {
+ Extension::State state;
+ if (extension->GetInteger(kLocation, reinterpret_cast<int*>(&location)) &&
+ location == Extension::EXTERNAL_PREF) {
+ return extension->GetInteger(kState, reinterpret_cast<int*>(&state)) &&
+ state == Extension::KILLBIT;
+ }
+ }
+
#if defined(OS_WIN)
+ if (location == Extension::EXTERNAL_REGISTRY) {
HKEY reg_root = HKEY_LOCAL_MACHINE;
RegKey key;
std::wstring key_path = ASCIIToWide(registry_path_);
@@ -1010,10 +1195,9 @@ bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& version_pa
// If the key doesn't exist, then we should uninstall.
return !key.Open(reg_root, key_path.c_str());
-#else
- // TODO(port)
-#endif
}
+#endif
+
return false;
}
diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h
index 09c9623..2d149f1 100644
--- a/chrome/browser/extensions/extensions_service.h
+++ b/chrome/browser/extensions/extensions_service.h
@@ -16,8 +16,10 @@
#include "base/task.h"
#include "base/values.h"
#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
class Browser;
+class DictionaryValue;
class Extension;
class ExtensionsServiceBackend;
class GURL;
@@ -63,6 +65,26 @@ class ExtensionsService
// Lookup an extension by |id|.
Extension* GetExtensionByID(std::string id);
+ // Gets a list of external extensions. If |external_extensions| is non-null,
+ // a dictionary with all external extensions (including extensions installed
+ // through the registry on Windows builds) and their preferences are
+ // returned. If |killed_extensions| is non-null, a set of string IDs
+ // containing all external extension IDs with the killbit set are returned.
+ void GetExternalExtensions(DictionaryValue* external_extensions,
+ std::set<std::string>* killed_extensions);
+
+ // Gets the settings for an extension from preferences. If the key doesn't
+ // exist, this function creates it (don't need to check return for NULL).
+ DictionaryValue* GetOrCreateExtensionPref(const std::wstring& extension_id);
+
+ // Writes a preference value for a particular extension |extension_id| under
+ // the |key| specified. If |schedule_save| is true, it will also ask the
+ // preference system to schedule a save to disk.
+ bool UpdateExtensionPref(const std::wstring& extension_id,
+ const std::wstring& key,
+ Value* data_value,
+ bool schedule_save);
+
// The name of the file that the current active version number is stored in.
static const char* kCurrentVersionFileName;
@@ -87,6 +109,10 @@ class ExtensionsService
// Called by the backend when an extensoin hsa been installed.
void OnExtensionInstalled(Extension* extension, bool is_update);
+ // Called by the backend when an external extension has been installed.
+ void OnExternalExtensionInstalled(
+ const std::string& id, Extension::Location location);
+
// Called by the backend when an extension has been reinstalled.
void OnExtensionVersionReinstalled(const std::string& id);
@@ -136,7 +162,8 @@ class ExtensionsServiceBackend
// Errors are reported through ExtensionErrorReporter. On completion,
// OnExtensionsLoaded() is called with any successfully loaded extensions.
void LoadExtensionsFromInstallDirectory(
- scoped_refptr<ExtensionsService> frontend);
+ scoped_refptr<ExtensionsService> frontend,
+ DictionaryValue* extension_prefs);
// Loads a single extension from |path| where |path| is the top directory of
// a specific extension where its manifest file lives.
@@ -158,6 +185,7 @@ class ExtensionsServiceBackend
// Errors are reported through ExtensionErrorReporter. Succcess is not
// reported.
void CheckForExternalUpdates(std::set<std::string> ids_to_ignore,
+ DictionaryValue* extension_prefs,
scoped_refptr<ExtensionsService> frontend);
// Deletes all versions of the extension from the filesystem. Note that only
@@ -214,6 +242,18 @@ class ExtensionsServiceBackend
// Notify the frontend that the extension had already been installed.
void ReportExtensionVersionReinstalled(const std::string& id);
+ // Checks a set of strings (containing id's to ignore) in order to determine
+ // if the extension should be installed.
+ bool ShouldSkipInstallingExtension(const std::set<std::string>& ids_to_ignore,
+ const std::string& id);
+
+ // Installs the extension if the extension is a newer version or if the
+ // extension hasn't been installed before.
+ void CheckVersionAndInstallExtension(const std::string& id,
+ const std::string& extension_version,
+ const FilePath& extension_path,
+ bool from_external);
+
// Read the manifest from the front of the extension file.
// Caller takes ownership of return value.
DictionaryValue* ReadManifest(const FilePath& extension_path);
@@ -238,7 +278,8 @@ class ExtensionsServiceBackend
// For the extension in |version_path| with |id|, check to see if it's an
// externally managed extension. If so return true if it should be
// uninstalled.
- bool CheckExternalUninstall(const FilePath& version_path,
+ bool CheckExternalUninstall(DictionaryValue* extension_prefs,
+ const FilePath& version_path,
const std::string& id);
// Should an extension of |id| and |version| be installed?
diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc
index 371dd8a..e7cdd6b 100644
--- a/chrome/browser/extensions/extensions_service_unittest.cc
+++ b/chrome/browser/extensions/extensions_service_unittest.cc
@@ -30,6 +30,17 @@
#include "base/registry.h"
#endif
+// Extension ids used during testing.
+static const char* all_zero = "0000000000000000000000000000000000000000";
+static const char* zero_n_one = "0000000000000000000000000000000000000001";
+static const char* good0 = "00123456789abcdef0123456789abcdef0123456";
+static const char* good1 = "10123456789abcdef0123456789abcdef0123456";
+static const char* good2 = "20123456789abcdef0123456789abcdef0123456";
+static const char* good_crx = "00123456789abcdef0123456789abcdef0123456";
+static const char* page_action = "8a5e4cb023c61b431e9b603a97c293429ce057c8";
+static const char* theme_crx = "f0123456789abcdef0123456789abcdef0126456";
+static const char* theme2_crx = "f0123456789adddef0123456789abcdef0126456";
+
namespace {
struct ExtensionsOrder {
@@ -177,6 +188,35 @@ class ExtensionsServiceTest
ExtensionErrorReporter::GetInstance()->ClearErrors();
}
+ void ValidatePrefKeyCount(size_t count) {
+ DictionaryValue* dict =
+ profile_->GetPrefs()->GetMutableDictionary(L"extensions.settings");
+ ASSERT_TRUE(dict != NULL);
+ EXPECT_EQ(count, dict->GetSize());
+ }
+
+ void ValidatePref(std::string extension_id,
+ std::wstring pref_path,
+ int must_equal) {
+ std::wstring msg = L" while checking: ";
+ msg += ASCIIToWide(extension_id);
+ msg += L" ";
+ msg += pref_path;
+ msg += L" == ";
+ msg += IntToWString(must_equal);
+
+ const DictionaryValue* dict =
+ profile_->GetPrefs()->GetDictionary(L"extensions.settings");
+ ASSERT_TRUE(dict != NULL) << msg;
+ DictionaryValue* pref = NULL;
+ ASSERT_TRUE(dict->GetDictionary(ASCIIToWide(extension_id), &pref)) << msg;
+ EXPECT_TRUE(pref != NULL) << msg;
+ int val;
+ pref->GetInteger(pref_path, &val);
+ EXPECT_EQ(must_equal, val) << msg;
+ }
+
+ protected:
scoped_ptr<TestingProfile> profile_;
scoped_refptr<ExtensionsService> service_;
size_t total_successes_;
@@ -199,7 +239,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) {
source_path = source_path.AppendASCII("good");
FilePath dest_path = profile_->GetPath().AppendASCII("Extensions");
- file_util::CopyDirectory(source_path, dest_path, true); // recursive
+ file_util::CopyDirectory(source_path, dest_path, true); // Recursive.
ASSERT_TRUE(service_->Init());
loop_.RunAllPending();
@@ -211,8 +251,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) {
}
ASSERT_EQ(3u, loaded_.size());
- EXPECT_EQ(std::string("00123456789abcdef0123456789abcdef0123456"),
- loaded_[0]->id());
+ EXPECT_EQ(std::string(good_crx), loaded_[0]->id());
EXPECT_EQ(std::string("My extension 1"),
loaded_[0]->name());
EXPECT_EQ(std::string("The first extension that I made."),
@@ -221,6 +260,14 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) {
EXPECT_TRUE(service_->GetExtensionByID(loaded_[0]->id()));
EXPECT_EQ(3u, service_->extensions()->size());
+ ValidatePrefKeyCount(3);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
+ ValidatePref(good1, L"state", Extension::ENABLED);
+ ValidatePref(good1, L"location", Extension::INTERNAL);
+ ValidatePref(good2, L"state", Extension::ENABLED);
+ ValidatePref(good2, L"location", Extension::INTERNAL);
+
Extension* extension = loaded_[0];
const UserScriptList& scripts = extension->content_scripts();
const std::vector<std::string>& toolstrips = extension->toolstrips();
@@ -248,8 +295,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) {
EXPECT_EQ("toolstrip1.html", toolstrips[0]);
EXPECT_EQ("toolstrip2.html", toolstrips[1]);
- EXPECT_EQ(std::string("10123456789abcdef0123456789abcdef0123456"),
- loaded_[1]->id());
+ EXPECT_EQ(std::string(good1), loaded_[1]->id());
EXPECT_EQ(std::string("My extension 2"), loaded_[1]->name());
EXPECT_EQ(std::string(""), loaded_[1]->description());
EXPECT_EQ(loaded_[1]->GetResourceURL("background.html"),
@@ -264,8 +310,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) {
EXPECT_FALSE(loaded_[1]->plugins()[1].is_public);
EXPECT_EQ(Extension::INTERNAL, loaded_[1]->location());
- EXPECT_EQ(std::string("20123456789abcdef0123456789abcdef0123456"),
- loaded_[2]->id());
+ EXPECT_EQ(std::string(good2), loaded_[2]->id());
EXPECT_EQ(std::string("My extension 3"), loaded_[2]->name());
EXPECT_EQ(std::string(""), loaded_[2]->description());
EXPECT_EQ(0u, loaded_[2]->content_scripts().size());
@@ -280,7 +325,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectoryFail) {
source_path = source_path.AppendASCII("bad");
FilePath dest_path = profile_->GetPath().AppendASCII("Extensions");
- file_util::CopyDirectory(source_path, dest_path, true); // recursive
+ file_util::CopyDirectory(source_path, dest_path, true); // Recursive.
ASSERT_TRUE(service_->Init());
loop_.RunAllPending();
@@ -288,6 +333,9 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectoryFail) {
EXPECT_EQ(3u, GetErrors().size());
EXPECT_EQ(0u, loaded_.size());
+ // Make sure the dictionary is empty.
+ ValidatePrefKeyCount(0);
+
EXPECT_TRUE(MatchPattern(GetErrors()[0],
std::string("Could not load extension from '*'. * ") +
JSONReader::kBadRootElementType)) << GetErrors()[0];
@@ -310,7 +358,7 @@ TEST_F(ExtensionsServiceTest, CleanupOnStartup) {
source_path = source_path.AppendASCII("good");
FilePath dest_path = profile_->GetPath().AppendASCII("Extensions");
- file_util::CopyDirectory(source_path, dest_path, true); // recursive
+ file_util::CopyDirectory(source_path, dest_path, true); // Recursive.
// Simulate that one of them got partially deleted by deling the
// Current Version file.
@@ -327,6 +375,12 @@ TEST_F(ExtensionsServiceTest, CleanupOnStartup) {
// And extension1 dir should now be toast.
dest_path = dest_path.DirName();
ASSERT_FALSE(file_util::PathExists(dest_path));
+
+ ValidatePrefKeyCount(2);
+ ValidatePref(good1, L"state", Extension::ENABLED);
+ ValidatePref(good1, L"location", Extension::INTERNAL);
+ ValidatePref(good2, L"state", Extension::ENABLED);
+ ValidatePref(good2, L"location", Extension::INTERNAL);
}
// Test installing extensions.
@@ -341,30 +395,44 @@ TEST_F(ExtensionsServiceTest, InstallExtension) {
TestInstallExtension(path, false);
SetExtensionsEnabled(true);
+ ValidatePrefKeyCount(0);
+
// A simple extension that should install without error.
path = extensions_path.AppendASCII("good.crx");
TestInstallExtension(path, true);
// TODO(erikkay): verify the contents of the installed extension.
+ int pref_count = 0;
+ ValidatePrefKeyCount(++pref_count);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
+
// An extension with page actions.
path = extensions_path.AppendASCII("page_action.crx");
TestInstallExtension(path, true);
+ ValidatePrefKeyCount(++pref_count);
+ ValidatePref(page_action, L"state", Extension::ENABLED);
+ ValidatePref(page_action, L"location", Extension::INTERNAL);
// 0-length extension file.
path = extensions_path.AppendASCII("not_an_extension.crx");
TestInstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
// Bad magic number.
path = extensions_path.AppendASCII("bad_magic.crx");
TestInstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
// Poorly formed JSON.
path = extensions_path.AppendASCII("bad_json.crx");
TestInstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
// Incorrect zip hash.
path = extensions_path.AppendASCII("bad_hash.crx");
TestInstallExtension(path, false);
+ ValidatePrefKeyCount(pref_count);
// TODO(erikkay): add more tests for many of the failure cases.
// TODO(erikkay): add tests for upgrade cases.
@@ -378,20 +446,31 @@ TEST_F(ExtensionsServiceTest, InstallTheme) {
// A theme.
FilePath path = extensions_path.AppendASCII("theme.crx");
TestInstallTheme(path, true);
+ int pref_count = 0;
+ ValidatePrefKeyCount(++pref_count);
+ ValidatePref(theme_crx, L"state", Extension::ENABLED);
+ ValidatePref(theme_crx, L"location", Extension::INTERNAL);
- // A theme when extensions are disabled.
+ // A theme when extensions are disabled. Themes can be installed even though
+ // extensions are disabled.
SetExtensionsEnabled(false);
path = extensions_path.AppendASCII("theme2.crx");
TestInstallTheme(path, true);
+ ValidatePrefKeyCount(++pref_count);
+ ValidatePref(theme2_crx, L"state", Extension::ENABLED);
+ ValidatePref(theme2_crx, L"location", Extension::INTERNAL);
SetExtensionsEnabled(true);
- // A theme with extension elements.
+ // A theme with extension elements. Themes cannot have extension elements so
+ // this test should fail.
path = extensions_path.AppendASCII("theme_with_extension.crx");
TestInstallTheme(path, false);
+ ValidatePrefKeyCount(pref_count);
// A theme with image resources missing (misspelt path).
path = extensions_path.AppendASCII("theme_missing_image.crx");
TestInstallTheme(path, false);
+ ValidatePrefKeyCount(pref_count);
}
// Test that when an extension version is reinstalled, nothing happens.
@@ -408,6 +487,9 @@ TEST_F(ExtensionsServiceTest, Reinstall) {
ASSERT_TRUE(installed_);
ASSERT_EQ(1u, loaded_.size());
ASSERT_EQ(0u, GetErrors().size());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
installed_ = NULL;
loaded_.clear();
@@ -420,6 +502,9 @@ TEST_F(ExtensionsServiceTest, Reinstall) {
ASSERT_FALSE(installed_);
ASSERT_EQ(0u, loaded_.size());
ASSERT_EQ(0u, GetErrors().size());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
}
// Tests uninstalling normal extensions
@@ -434,10 +519,14 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) {
// The directory should be there now.
FilePath install_path = profile_->GetPath().AppendASCII("Extensions");
- const char* extension_id = "00123456789abcdef0123456789abcdef0123456";
+ const char* extension_id = good_crx;
FilePath extension_path = install_path.AppendASCII(extension_id);
EXPECT_TRUE(file_util::PathExists(extension_path));
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
+
// Uninstall it.
service_->UninstallExtension(extension_id);
total_successes_ = 0;
@@ -446,6 +535,10 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) {
ASSERT_TRUE(unloaded_id_.length());
EXPECT_EQ(extension_id, unloaded_id_);
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::DISABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
+
// The extension should not be in the service anymore.
ASSERT_FALSE(service_->GetExtensionByID(extension_id));
loop_.RunAllPending();
@@ -463,6 +556,10 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) {
service_->UninstallExtension(extension_id);
loop_.RunAllPending();
EXPECT_FALSE(file_util::PathExists(extension_path));
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::DISABLED);
+ ValidatePref(good_crx, L"location", Extension::INTERNAL);
}
// Tests loading single extensions (like --load-extension)
@@ -479,6 +576,10 @@ TEST_F(ExtensionsServiceTest, LoadExtension) {
ASSERT_EQ(1u, loaded_.size());
ASSERT_EQ(Extension::LOAD, loaded_[0]->location());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good0, L"state", Extension::ENABLED);
+ ValidatePref(good0, L"location", Extension::INTERNAL);
+
FilePath no_manifest = extensions_path.AppendASCII("bad")
.AppendASCII("no_manifest").AppendASCII("1");
service_->LoadExtension(no_manifest);
@@ -486,12 +587,18 @@ TEST_F(ExtensionsServiceTest, LoadExtension) {
EXPECT_EQ(1u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
- // Test uninstall
+ ValidatePrefKeyCount(1);
+
+ // Test uninstall.
std::string id = loaded_[0]->id();
EXPECT_FALSE(unloaded_id_.length());
service_->UninstallExtension(id);
loop_.RunAllPending();
EXPECT_EQ(id, unloaded_id_);
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good0, L"state", Extension::DISABLED);
+ ValidatePref(good0, L"location", Extension::INTERNAL);
}
// Tests that we generate IDs when they are not specified in the manifest for
@@ -507,23 +614,31 @@ TEST_F(ExtensionsServiceTest, GenerateID) {
EXPECT_EQ(0u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
std::string id1 = loaded_[0]->id();
- ASSERT_EQ("0000000000000000000000000000000000000000", id1);
+ ASSERT_EQ(all_zero, id1);
ASSERT_EQ("chrome-extension://0000000000000000000000000000000000000000/",
loaded_[0]->url().spec());
+ ValidatePrefKeyCount(1);
+ ValidatePref(all_zero, L"state", Extension::ENABLED);
+ ValidatePref(all_zero, L"location", Extension::INTERNAL);
+
service_->LoadExtension(no_id_ext);
loop_.RunAllPending();
std::string id2 = loaded_[1]->id();
- ASSERT_EQ("0000000000000000000000000000000000000001", id2);
+ ASSERT_EQ(zero_n_one, id2);
ASSERT_EQ("chrome-extension://0000000000000000000000000000000000000001/",
loaded_[1]->url().spec());
+
+ ValidatePrefKeyCount(2);
+ ValidatePref(zero_n_one, L"state", Extension::ENABLED);
+ ValidatePref(zero_n_one, L"location", Extension::INTERNAL);
}
// Tests the external installation feature
#if defined(OS_WIN)
-TEST_F(ExtensionsServiceTest, ExternalInstall) {
- // Register a test extension externally.
+TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) {
+ // Register a test extension externally using the registry.
FilePath source_path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path));
source_path = source_path.AppendASCII("extensions").AppendASCII("good.crx");
@@ -542,8 +657,11 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) {
ASSERT_EQ(0u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
- ASSERT_EQ(Extension::EXTERNAL, loaded_[0]->location());
+ ASSERT_EQ(Extension::EXTERNAL_REGISTRY, loaded_[0]->location());
ASSERT_EQ("1.0.0.0", loaded_[0]->version()->GetString());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY);
// Reinit the service without changing anything. The extension should be
// loaded again.
@@ -552,6 +670,9 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) {
loop_.RunAllPending();
ASSERT_EQ(0u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY);
// Now update the extension with a new version. We should get upgraded.
source_path = source_path.DirName().AppendASCII("good2.crx");
@@ -564,6 +685,9 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) {
ASSERT_EQ(0u, GetErrors().size());
ASSERT_EQ(1u, loaded_.size());
ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY);
// Uninstall the extension and reinit. Nothing should happen because the
// preference should prevent us from reinstalling.
@@ -580,17 +704,28 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) {
service_->Init();
loop_.RunAllPending();
ASSERT_EQ(0u, loaded_.size());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::KILLBIT); // It is an ex-parrot.
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY);
// Now clear the preference, reinstall, then remove the reg key. The extension
// should be uninstalled.
- profile_->GetPrefs()->GetMutableList(L"extensions.uninstalled_external_ids")
- ->Clear();
+ std::wstring pref_path = L"extensions.settings.";
+ pref_path += ASCIIToWide(good_crx);
+ profile_->GetPrefs()->RegisterDictionaryPref(pref_path.c_str());
+ DictionaryValue* extension_prefs;
+ extension_prefs =
+ profile_->GetPrefs()->GetMutableDictionary(pref_path.c_str());
+ extension_prefs->SetInteger(L"state", 0);
profile_->GetPrefs()->ScheduleSavePersistentPrefs();
loaded_.clear();
service_->Init();
loop_.RunAllPending();
ASSERT_EQ(1u, loaded_.size());
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY);
RegKey parent_key;
key.Open(HKEY_LOCAL_MACHINE, ASCIIToWide(registry_path_).c_str(), KEY_WRITE);
@@ -601,8 +736,116 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) {
ASSERT_EQ(0u, loaded_.size());
}
-#else
+#endif
+
+TEST_F(ExtensionsServiceTest, ExternalInstallPref) {
+ // Register a external extension using preinstalled preferences.
+ FilePath source_path;
+ ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path));
+ source_path = source_path.AppendASCII("extensions").AppendASCII("good.crx");
+
+ DictionaryValue* extension_prefs;
+ extension_prefs =
+ profile_->GetPrefs()->GetMutableDictionary(L"extensions.settings");
-// TODO(port)
+ DictionaryValue* extension = new DictionaryValue();
+ ASSERT_TRUE(extension->SetString(L"external_crx",
+ source_path.ToWStringHack()));
+ ASSERT_TRUE(extension->SetString(L"external_version", L"1.0"));
+ ASSERT_TRUE(extension_prefs->Set(ASCIIToWide(good_crx), extension));
-#endif
+ // Start up the service, it should find our externally registered extension
+ // and install it.
+ service_->Init();
+ loop_.RunAllPending();
+
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ(Extension::EXTERNAL_PREF, loaded_[0]->location());
+ ASSERT_EQ("1.0.0.0", loaded_[0]->version()->GetString());
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF);
+
+ // Reinit the service without changing anything. The extension should be
+ // loaded again.
+ loaded_.clear();
+ service_->Init();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF);
+
+ // Now update the extension with a new version. We should get upgraded.
+ source_path = source_path.DirName().AppendASCII("good2.crx");
+ ASSERT_TRUE(extension->SetString(L"external_crx",
+ source_path.ToWStringHack()));
+ ASSERT_TRUE(extension->SetString(L"external_version", L"1.0.0.1"));
+
+ loaded_.clear();
+ service_->Init();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, GetErrors().size());
+ ASSERT_EQ(1u, loaded_.size());
+ ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString());
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF);
+
+ // Uninstall the extension and re-init. Nothing should happen because the
+ // preference should prevent us from reinstalling.
+ std::string id = loaded_[0]->id();
+ service_->UninstallExtension(id);
+ loop_.RunAllPending();
+
+ // The extension should also be gone from the install directory.
+ FilePath install_path =
+ profile_->GetPath().AppendASCII("Extensions").AppendASCII(id);
+ ASSERT_FALSE(file_util::PathExists(install_path));
+
+ loaded_.clear();
+ service_->Init();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::KILLBIT);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF);
+
+ // Now clear the preference and reinstall.
+ extension->SetInteger(L"state", Extension::ENABLED);
+ profile_->GetPrefs()->ScheduleSavePersistentPrefs();
+
+ loaded_.clear();
+ service_->Init();
+ loop_.RunAllPending();
+ ASSERT_EQ(1u, loaded_.size());
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::ENABLED);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF);
+
+ // Now set the kill bit and watch the extension go away.
+ extension->SetInteger(L"state", Extension::KILLBIT);
+ profile_->GetPrefs()->ScheduleSavePersistentPrefs();
+
+ loaded_.clear();
+ service_->Init();
+ loop_.RunAllPending();
+ ASSERT_EQ(0u, loaded_.size());
+
+ ValidatePrefKeyCount(1);
+ ValidatePref(good_crx, L"state", Extension::KILLBIT);
+ ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF);
+
+ // The extension should also be gone from disk.
+ FilePath extension_path = install_path.DirName();
+ extension_path = extension_path.AppendASCII(good_crx);
+ EXPECT_FALSE(file_util::PathExists(extension_path)) <<
+ extension_path.ToWStringHack();
+}
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc
index f6a579b..c641afc 100644
--- a/chrome/common/extensions/extension.cc
+++ b/chrome/common/extensions/extension.cc
@@ -15,6 +15,10 @@
#include "chrome/common/extensions/user_script.h"
#include "chrome/common/url_constants.h"
+#if defined(OS_WIN)
+#include "base/registry.h"
+#endif
+
const char Extension::kManifestFilename[] = "manifest.json";
const wchar_t* Extension::kContentScriptsKey = L"content_scripts";
@@ -142,6 +146,11 @@ const char* Extension::kInvalidThemeTintsError =
const char* Extension::kThemesCannotContainExtensionsError =
"A theme cannot contain extensions code.";
+#if defined(OS_WIN)
+const char* Extension::kExtensionRegistryPath =
+ "Software\\Google\\Chrome\\Extensions";
+#endif
+
const size_t Extension::kIdSize = 20; // SHA1 (160 bits) == 20 bytes
Extension::~Extension() {
@@ -155,6 +164,24 @@ const std::string Extension::VersionString() const {
}
// static
+bool Extension::IdIsValid(const std::string& id) {
+ // Verify that the id is legal. The id is a hex string of the SHA-1 hash of
+ // the public key.
+ std::vector<uint8> id_bytes;
+ if (!HexStringToBytes(id, &id_bytes) || id_bytes.size() != kIdSize)
+ return false;
+
+ // We only support lowercase IDs, because IDs can be used as URL components
+ // (where GURL will lowercase it).
+ std::string temp = id;
+ StringToLowerASCII(temp);
+ if (temp != id)
+ return false;
+
+ return true;
+}
+
+// static
GURL Extension::GetResourceURL(const GURL& extension_url,
const std::string& relative_path) {
DCHECK(extension_url.SchemeIs(chrome::kExtensionScheme));
@@ -174,6 +201,19 @@ const PageAction* Extension::GetPageAction(std::string id) const {
return it->second;
}
+Extension::Location Extension::ExternalExtensionInstallType(
+ std::string registry_path) {
+#if defined(OS_WIN)
+ HKEY reg_root = HKEY_LOCAL_MACHINE;
+ RegKey key;
+ registry_path.append("\\");
+ registry_path.append(id_);
+ if (key.Open(reg_root, ASCIIToWide(registry_path).c_str()))
+ return Extension::EXTERNAL_REGISTRY;
+#endif
+ return Extension::EXTERNAL_PREF;
+}
+
// Helper method that loads a UserScript object from a dictionary in the
// content_script list of the manifest.
bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
@@ -449,10 +489,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id,
// (where GURL will lowercase it).
StringToLowerASCII(&id_);
- // Verify that the id is legal. The id is a hex string of the SHA-1 hash of
- // the public key.
- std::vector<uint8> id_bytes;
- if (!HexStringToBytes(id_, &id_bytes) || id_bytes.size() != kIdSize) {
+ // Verify that the id is legal.
+ if (!IdIsValid(id_)) {
*error = kInvalidIdError;
return false;
}
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index 76f4b20..9b79f30 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -24,10 +24,17 @@ class Extension {
// What an extension was loaded from.
enum Location {
INVALID,
- INTERNAL, // A crx file from the internal Extensions directory.
- EXTERNAL, // A crx file from an external directory (via eg the registry
- // on Windows).
- LOAD // --load-extension.
+ INTERNAL, // A crx file from the internal Extensions directory.
+ EXTERNAL_PREF, // A crx file from an external directory (via prefs).
+ EXTERNAL_REGISTRY, // A crx file from an external directory (via eg the
+ // registry on Windows).
+ LOAD // --load-extension.
+ };
+
+ enum State {
+ DISABLED,
+ ENABLED,
+ KILLBIT, // Don't install/upgrade (applies to external extensions only).
};
// An NPAPI plugin included in the extension.
@@ -114,6 +121,10 @@ class Extension {
static const char* kThemesCannotContainExtensionsError;
static const char* kMissingFileError;
+#if defined(OS_WIN)
+ static const char* kExtensionRegistryPath;
+#endif
+
// The number of bytes in a legal id.
static const size_t kIdSize;
@@ -121,6 +132,15 @@ class Extension {
explicit Extension(const FilePath& path);
virtual ~Extension();
+ // Checks to see if the extension has a valid ID.
+ static bool IdIsValid(const std::string& id);
+
+ // Whether the |location| is external or not.
+ static inline bool IsExternalLocation(Location location) {
+ return location == Extension::EXTERNAL_PREF ||
+ location == Extension::EXTERNAL_REGISTRY;
+ }
+
// Returns an absolute url to a resource inside of an extension. The
// |extension_url| argument should be the url() from an Extension object. The
// |relative_path| can be untrusted user input. The returned URL will either
@@ -169,7 +189,11 @@ class Extension {
// Retrieves a page action by |id|.
const PageAction* GetPageAction(std::string id) const;
- // Theme-related
+ // Returns the origin of this extension. This function takes a |registry_path|
+ // so that the registry location can be overwritten during testing.
+ Location ExternalExtensionInstallType(std::string registry_path);
+
+ // Theme-related.
DictionaryValue* GetThemeImages() const { return theme_images_.get(); }
DictionaryValue* GetThemeColors() const { return theme_colors_.get(); }
DictionaryValue* GetThemeTints() const { return theme_tints_.get(); }
diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc
index f98e875..916b048 100644
--- a/chrome/common/extensions/extension_unpacker.cc
+++ b/chrome/common/extensions/extension_unpacker.cc
@@ -41,19 +41,6 @@ struct ExtensionHeader {
const size_t kZipHashBytes = 32; // SHA-256
const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size.
-#if defined(OS_WIN)
-
-// Registry key where registry defined extension installers live.
-const wchar_t kRegistryExtensions[] = L"Software\\Google\\Chrome\\Extensions";
-
-// Registry value of of that key that defines the path to the .crx file.
-const wchar_t kRegistryExtensionPath[] = L"path";
-
-// Registry value of that key that defines the current version of the .crx file.
-const wchar_t kRegistryExtensionVersion[] = L"version";
-
-#endif
-
// A marker file to indicate that an extension was installed from an external
// source.
const char kExternalInstallFile[] = "EXTERNAL_INSTALL";