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
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 87c14a2..2d8b0391 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 @@
 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 @@
       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 @@
   // 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 @@
 
   // 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::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::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::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 @@
   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 @@
 }
 
 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 @@
     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 @@
     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 @@
   }
 
   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 @@
     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 @@
 
   // 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 @@
   // 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 @@
     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 @@
       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 @@
   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 @@
   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 @@
       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 @@
     }
     ++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 @@
 
     // 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;
 }