Auto install and update extensions from a registry defined location.  This allows developers to install a chrome extension and manage its update process completely indepdendently of Chrome's built-in mechanism.
Review URL: http://codereview.chromium.org/28040

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10412 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 17c5afe..1446e7b 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -19,7 +19,9 @@
 #include "chrome/common/json_value_serializer.h"
 #include "chrome/common/notification_service.h"
 #include "chrome/common/unzip.h"
+
 #if defined(OS_WIN)
+#include "base/registry.h"
 #include "chrome/common/win_util.h"
 #endif
 
@@ -28,8 +30,10 @@
 const char* ExtensionsService::kInstallDirectoryName = "Extensions";
 const char* ExtensionsService::kCurrentVersionFileName = "Current Version";
 const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL";
+
+namespace {
 // Chromium Extension magic number
-static const char kExtensionFileMagic[] = "Cr24";
+const char kExtensionFileMagic[] = "Cr24";
 
 struct ExtensionHeader {
   char magic[sizeof(kExtensionFileMagic) - 1];
@@ -41,6 +45,25 @@
 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";
+}
+
+
 ExtensionsService::ExtensionsService(const FilePath& profile_directory,
                                      UserScriptMaster* user_script_master)
     : message_loop_(MessageLoop::current()),
@@ -57,6 +80,18 @@
 }
 
 bool ExtensionsService::Init() {
+#if defined(OS_WIN)
+
+  // TODO(port): ExtensionsServiceBackend::CheckForExternalUpdates depends on
+  // the Windows registry.
+  // TODO(erikkay): Should we monitor the registry during run as well?
+  g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
+      NewRunnableMethod(backend_.get(),
+          &ExtensionsServiceBackend::CheckForExternalUpdates,
+          install_directory_,
+          scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
+#endif
+
   // TODO(aa): This message loop should probably come from a backend
   // interface, similar to how the message loop for the frontend comes
   // from the frontend interface.
@@ -65,7 +100,6 @@
           &ExtensionsServiceBackend::LoadExtensionsFromDirectory,
           install_directory_,
           scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
-  // TODO(aa): Load extensions from other registered directories.
 
   return true;
 }
@@ -83,6 +117,7 @@
           &ExtensionsServiceBackend::InstallExtension,
           extension_path,
           install_directory_,
+          true,  // alert_on_error
           scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
 }
 
@@ -135,115 +170,125 @@
   delete new_extensions;
 }
 
-void ExtensionsService::OnExtensionLoadError(const std::string& error) {
+void ExtensionsService::OnExtensionLoadError(bool alert_on_error,
+                                             const std::string& error) {
   // TODO(aa): Print the error message out somewhere better. I think we are
   // going to need some sort of 'extension inspector'.
   LOG(WARNING) << error;
+  if (alert_on_error) {
 #if defined(OS_WIN)
-  win_util::MessageBox(NULL, UTF8ToWide(error),
-      L"Extension load error", MB_OK | MB_SETFOREGROUND);
+    win_util::MessageBox(NULL, UTF8ToWide(error),
+        L"Extension load error", MB_OK | MB_SETFOREGROUND);
 #endif
+  }
 }
 
-void ExtensionsService::OnExtensionInstallError(const std::string& error) {
+void ExtensionsService::OnExtensionInstallError(bool alert_on_error,
+                                                const std::string& error) {
   // TODO(erikkay): Print the error message out somewhere better.
   LOG(WARNING) << error;
+  if (alert_on_error) {
 #if defined(OS_WIN)
-  win_util::MessageBox(NULL, UTF8ToWide(error),
-      L"Extension load error", MB_OK | MB_SETFOREGROUND);
+    win_util::MessageBox(NULL, UTF8ToWide(error),
+        L"Extension load error", MB_OK | MB_SETFOREGROUND);
 #endif
+  }
 }
 
-void ExtensionsService::OnExtensionInstalled(FilePath path) {
+void ExtensionsService::OnExtensionInstalled(FilePath path, bool update) {
   NotificationService::current()->Notify(
       NotificationType::EXTENSION_INSTALLED,
       NotificationService::AllSources(),
       Details<FilePath>(&path));
 
-  // Immediately try to load the extension.
-  g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE,
-      NewRunnableMethod(backend_.get(),
-          &ExtensionsServiceBackend::LoadSingleExtension,
-          path,
-          scoped_refptr<ExtensionsServiceFrontendInterface>(this)));
+  // TODO(erikkay): Update UI if appropriate.
 }
 
 
 // ExtensionsServicesBackend
 
-bool ExtensionsServiceBackend::LoadExtensionsFromDirectory(
+void ExtensionsServiceBackend::LoadExtensionsFromDirectory(
     const FilePath& path_in,
     scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
   FilePath path = path_in;
-
-  // Create the <Profile>/Extensions directory if it doesn't exist.
-  if (!file_util::DirectoryExists(path))
-    file_util::CreateDirectory(path);
+  frontend_ = frontend;
+  alert_on_error_ = false;
 
   if (!file_util::AbsolutePath(&path))
     NOTREACHED();
 
+  scoped_ptr<ExtensionList> extensions(new ExtensionList);
+
+  // Create the <Profile>/Extensions directory if it doesn't exist.
+  if (!file_util::DirectoryExists(path)) {
+    file_util::CreateDirectory(path);
+    LOG(INFO) << "Created Extensions directory.  No extensions to install.";
+    ReportExtensionsLoaded(extensions.release());
+    return;
+  }
+
   LOG(INFO) << "Loading installed extensions...";
 
   // Find all child directories in the install directory and load their
   // manifests. Post errors and results to the frontend.
-  scoped_ptr<ExtensionList> extensions(new ExtensionList);
   file_util::FileEnumerator enumerator(path,
                                        false,  // not recursive
                                        file_util::FileEnumerator::DIRECTORIES);
-  for (FilePath child_path = enumerator.Next(); !child_path.value().empty();
-       child_path = enumerator.Next()) {
-    std::string version_str;
-    if (!ReadCurrentVersion(child_path, &version_str)) {
-      ReportExtensionLoadError(frontend.get(), child_path, StringPrintf(
-          "Could not read '%s' file.", 
-          ExtensionsService::kCurrentVersionFileName));
-      continue;
-    }
-
-    LOG(INFO) << "  " << WideToASCII(child_path.BaseName().ToWStringHack()) <<
-       " version: " << version_str;
-
-    child_path = child_path.AppendASCII(version_str);
-    Extension* extension = LoadExtension(child_path, frontend);
+  for (extension_path_ = enumerator.Next(); !extension_path_.value().empty();
+       extension_path_ = enumerator.Next()) {
+    Extension* extension = LoadExtensionCurrentVersion();
     if (extension)
       extensions->push_back(extension);
   }
 
   LOG(INFO) << "Done.";
-
-  ReportExtensionsLoaded(frontend.get(), extensions.release());
-  return true;
+  ReportExtensionsLoaded(extensions.release());
 }
 
-bool ExtensionsServiceBackend::LoadSingleExtension(
+void ExtensionsServiceBackend::LoadSingleExtension(
     const FilePath& path_in,
     scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
-  FilePath path = path_in;
-  if (!file_util::AbsolutePath(&path))
+  frontend_ = frontend;
+
+  // Explicit UI loads are always noisy.
+  alert_on_error_ = true;
+
+  extension_path_ = path_in;
+  if (!file_util::AbsolutePath(&extension_path_))
     NOTREACHED();
 
   LOG(INFO) << "Loading single extension from " <<
-      WideToASCII(path.BaseName().ToWStringHack());
+      WideToASCII(extension_path_.BaseName().ToWStringHack());
 
-  Extension* extension = LoadExtension(path, frontend);
+  Extension* extension = LoadExtension();
   if (extension) {
     ExtensionList* extensions = new ExtensionList;
     extensions->push_back(extension);
-    ReportExtensionsLoaded(frontend.get(), extensions);
-    return true;
+    ReportExtensionsLoaded(extensions);
   }
-  return false;
 }
 
-Extension* ExtensionsServiceBackend::LoadExtension(
-    const FilePath& path,
-    scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
+Extension* ExtensionsServiceBackend::LoadExtensionCurrentVersion() {
+  std::string version_str;
+  if (!ReadCurrentVersion(extension_path_, &version_str)) {
+    ReportExtensionLoadError(StringPrintf("Could not read '%s' file.",
+        ExtensionsService::kCurrentVersionFileName));
+    return NULL;
+  }
+
+  LOG(INFO) << "  " <<
+      WideToASCII(extension_path_.BaseName().ToWStringHack()) <<
+      " version: " << version_str;
+
+  extension_path_ = extension_path_.AppendASCII(version_str);
+  return LoadExtension();
+}
+
+Extension* ExtensionsServiceBackend::LoadExtension() {
   FilePath manifest_path =
-      path.AppendASCII(Extension::kManifestFilename);
+      extension_path_.AppendASCII(Extension::kManifestFilename);
   if (!file_util::PathExists(manifest_path)) {
-    ReportExtensionLoadError(frontend.get(), path,
-                             Extension::kInvalidManifestError);
+    ReportExtensionLoadError(Extension::kInvalidManifestError);
     return NULL;
   }
 
@@ -251,21 +296,29 @@
   std::string error;
   scoped_ptr<Value> root(serializer.Deserialize(&error));
   if (!root.get()) {
-    ReportExtensionLoadError(frontend.get(), path,
-                             error);
+    ReportExtensionLoadError(error);
     return NULL;
   }
 
   if (!root->IsType(Value::TYPE_DICTIONARY)) {
-    ReportExtensionLoadError(frontend.get(), path,
-                             Extension::kInvalidManifestError);
+    ReportExtensionLoadError(Extension::kInvalidManifestError);
     return NULL;
   }
 
-  scoped_ptr<Extension> extension(new Extension(path));
+  scoped_ptr<Extension> extension(new Extension(extension_path_));
   if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()),
                                 &error)) {
-    ReportExtensionLoadError(frontend.get(), path, error);
+    ReportExtensionLoadError(error);
+    return NULL;
+  }
+
+  if (CheckExternalUninstall(extension_path_, extension->id())) {
+
+    // TODO(erikkay): Possibly defer this operation to avoid slowing initial
+    // load of extensions.
+    UninstallExtension(extension_path_);
+
+    // No error needs to be reported.  The extension effectively doesn't exist.
     return NULL;
   }
 
@@ -274,9 +327,9 @@
        extension->content_scripts().begin();
        iter != extension->content_scripts().end(); ++iter) {
     if (!file_util::PathExists(iter->path())) {
-      ReportExtensionLoadError(frontend.get(), path, StringPrintf(
-          "Could not load content script '%s'.",
-          WideToUTF8(iter->path().ToWStringHack()).c_str()));
+      ReportExtensionLoadError(
+          StringPrintf("Could not load content script '%s'.",
+                       WideToUTF8(iter->path().ToWStringHack()).c_str()));
       return NULL;
     }
   }
@@ -285,21 +338,21 @@
 }
 
 void ExtensionsServiceBackend::ReportExtensionLoadError(
-    ExtensionsServiceFrontendInterface *frontend, const FilePath& path,
     const std::string &error) {
-  // TODO(erikkay): note that this isn't guaranteed to work properly on Linux.
-  std::string path_str = WideToASCII(path.ToWStringHack());
+
+  // TODO(port): note that this isn't guaranteed to work properly on Linux.
+  std::string path_str = WideToASCII(extension_path_.ToWStringHack());
   std::string message = StringPrintf("Could not load extension from '%s'. %s",
                                      path_str.c_str(), error.c_str());
-  frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
-      frontend, &ExtensionsServiceFrontendInterface::OnExtensionLoadError,
-      message));
+  frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
+      frontend_, &ExtensionsServiceFrontendInterface::OnExtensionLoadError,
+      alert_on_error_, message));
 }
 
 void ExtensionsServiceBackend::ReportExtensionsLoaded(
-    ExtensionsServiceFrontendInterface *frontend, ExtensionList* extensions) {
-  frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
-      frontend,
+    ExtensionList* extensions) {
+  frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
+      frontend_,
       &ExtensionsServiceFrontendInterface::OnExtensionsLoadedFromDirectory,
       extensions));
 }
@@ -307,37 +360,32 @@
 // The extension file format is a header, followed by the manifest, followed
 // by the zip file.  The header is a magic number, a version, the size of the
 // header, and the size of the manifest.  These ints are 4 byte little endian.
-DictionaryValue* ExtensionsServiceBackend::ReadManifest(
-      const FilePath& extension_path,
-      scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
-  ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb"));
+DictionaryValue* ExtensionsServiceBackend::ReadManifest() {
+  ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb"));
   if (!file.get()) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "no such extension file");
+    ReportExtensionInstallError("no such extension file");
     return NULL;
   }
 
   // Read and verify the header.
   ExtensionHeader header;
   size_t len;
+
   // TODO(erikkay): Yuck.  I'm not a big fan of this kind of code, but it
   // appears that we don't have any endian/alignment aware serialization
   // code in the code base.  So for now, this assumes that we're running
   // on a little endian machine with 4 byte alignment.
   len = fread(&header, 1, sizeof(ExtensionHeader), file.get());
   if (len < sizeof(ExtensionHeader)) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "invalid extension header");
+    ReportExtensionInstallError("invalid extension header");
     return NULL;
   }
   if (strncmp(kExtensionFileMagic, header.magic, sizeof(header.magic))) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "bad magic number");
+    ReportExtensionInstallError("bad magic number");
     return NULL;
   }
   if (header.version != Extension::kExpectedFormatVersion) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "bad version number");
+    ReportExtensionInstallError("bad version number");
     return NULL;
   }
   if (header.header_size > sizeof(ExtensionHeader))
@@ -360,24 +408,44 @@
   std::string error;
   scoped_ptr<Value> val(json.Deserialize(&error));
   if (!val.get()) {
-    ReportExtensionInstallError(frontend, extension_path, error);
+    ReportExtensionInstallError(error);
     return NULL;
   }
   if (!val->IsType(Value::TYPE_DICTIONARY)) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "manifest isn't a JSON dictionary");
+    ReportExtensionInstallError("manifest isn't a JSON dictionary");
     return NULL;
   }
   DictionaryValue* manifest = static_cast<DictionaryValue*>(val.get());
+
+  // Check the version before proceeding.  Although we verify the version
+  // again later, checking it here allows us to skip some potentially expensive
+  // work.
+  std::string id;
+  if (!manifest->GetString(Extension::kIdKey, &id)) {
+    ReportExtensionInstallError("missing id key");
+    return NULL;
+  }
+  FilePath dest_dir = install_directory_.AppendASCII(id.c_str());
+  if (file_util::PathExists(dest_dir)) {
+    std::string version;
+    if (!manifest->GetString(Extension::kVersionKey, &version)) {
+      ReportExtensionInstallError("missing version key");
+      return NULL;
+    }
+    std::string current_version;
+    if (ReadCurrentVersion(dest_dir, &current_version)) {
+      if (!CheckCurrentVersion(version, current_version, dest_dir))
+        return NULL;
+    }
+  }
+
   std::string zip_hash;
   if (!manifest->GetString(Extension::kZipHashKey, &zip_hash)) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "missing zip_hash key");
+    ReportExtensionInstallError("missing zip_hash key");
     return NULL;
   }
   if (zip_hash.size() != kZipHashHexBytes) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "invalid zip_hash key");
+    ReportExtensionInstallError("invalid zip_hash key");
     return NULL;
   }
 
@@ -394,19 +462,16 @@
 
   std::vector<uint8> zip_hash_bytes;
   if (!HexStringToBytes(zip_hash, &zip_hash_bytes)) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "invalid zip_hash key");
+    ReportExtensionInstallError("invalid zip_hash key");
     return NULL;
   }
   if (zip_hash_bytes.size() != kZipHashBytes) {
-    ReportExtensionInstallError(frontend, extension_path,
-                                "invalid zip_hash key");
+    ReportExtensionInstallError("invalid zip_hash key");
     return NULL;
   }
   for (size_t i = 0; i < kZipHashBytes; ++i) {
     if (zip_hash_bytes[i] != hash[i]) {
-      ReportExtensionInstallError(frontend, extension_path,
-                                  "zip_hash key didn't match zip hash");
+      ReportExtensionInstallError("zip_hash key didn't match zip hash");
       return NULL;
     }
   }
@@ -419,11 +484,10 @@
   return manifest;
 }
 
-bool ExtensionsServiceBackend::ReadCurrentVersion(
-    const FilePath& extension_path,
-    std::string* version_string) {
+bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir,
+                                                  std::string* version_string) {
   FilePath current_version =
-      extension_path.AppendASCII(ExtensionsService::kCurrentVersionFileName);
+      dir.AppendASCII(ExtensionsService::kCurrentVersionFileName);
   if (file_util::PathExists(current_version)) {
     if (file_util::ReadFileToString(current_version, version_string)) {
       TrimWhitespace(*version_string, TRIM_ALL, version_string);
@@ -434,66 +498,38 @@
 }
 
 bool ExtensionsServiceBackend::CheckCurrentVersion(
-    const FilePath& extension_path,
-    const std::string& version,
-    const FilePath& dest_dir,
-    scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
-  std::string version_str;
-  if (ReadCurrentVersion(dest_dir, &version_str)) {
-    if (version_str == version) {
-      FilePath version_dir = dest_dir.AppendASCII(version_str);
-      if (file_util::PathExists(version_dir)) {
-        ReportExtensionInstallError(frontend, extension_path,
-            "Extension version already installed");
-        return false;
-      }
-      // If the existing version_dir doesn't exist, then we'll return true
-      // so that we attempt to repair the broken installation.
-    } else {
-      scoped_ptr<Version> cur_version(
-          Version::GetVersionFromString(version_str));
-      scoped_ptr<Version> new_version(
-          Version::GetVersionFromString(version));
-      if (cur_version->CompareTo(*new_version) >= 0) {
-        ReportExtensionInstallError(frontend, extension_path,
-            "More recent version of extension already installed");
-        return false;
-      }
+    const std::string& new_version_str,
+    const std::string& current_version_str,
+    const FilePath& dest_dir) {
+  scoped_ptr<Version> current_version(
+      Version::GetVersionFromString(current_version_str));
+  scoped_ptr<Version> new_version(
+      Version::GetVersionFromString(new_version_str));
+  if (current_version->CompareTo(*new_version) >= 0) {
+
+    // Verify that the directory actually exists.  If it doesn't we'll return
+    // true so that the install code will repair the broken installation.
+    // TODO(erikkay): A further step would be to verify that the extension
+    // has actually loaded successfully.
+    FilePath version_dir = dest_dir.AppendASCII(current_version_str);
+    if (file_util::PathExists(version_dir)) {
+      ReportExtensionInstallError(
+          "Existing version is already up to date.");
+      return false;
     }
   }
   return true;
 }
 
-bool ExtensionsServiceBackend::UnzipExtension(const FilePath& extension_path,
-    const FilePath& temp_dir,
-    scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
-  // <profile>/Extensions/INSTALL_TEMP/<version>
-  if (!file_util::CreateDirectory(temp_dir)) {
-    ReportExtensionInstallError(frontend, extension_path,
-        "Couldn't create version directory.");
-    return false;
-  }
-  if (!Unzip(extension_path, temp_dir, NULL)) {
-    // Remove what we just installed.
-    file_util::Delete(temp_dir, true);
-    ReportExtensionInstallError(frontend, extension_path,
-        "Couldn't unzip extension.");
-    return false;
-  }
-  return true;
-}
-
-bool ExtensionsServiceBackend::InstallDirSafely(
-    const FilePath& extension_path,
-    const FilePath& source_dir, 
-    const FilePath& dest_dir,
-    scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
+bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir, 
+                                                const FilePath& dest_dir) {
 
   if (file_util::PathExists(dest_dir)) {
+
     // By the time we get here, it should be safe to assume that this directory
     // is not currently in use (it's not the current active version).
     if (!file_util::Delete(dest_dir, true)) {
-      ReportExtensionInstallError(frontend, extension_path,
+      ReportExtensionInstallError(
           "Can't delete existing version directory.");
       return false;
     }
@@ -501,26 +537,21 @@
     FilePath parent = dest_dir.DirName();
     if (!file_util::DirectoryExists(parent)) {
       if (!file_util::CreateDirectory(parent)) {
-        ReportExtensionInstallError(frontend, extension_path,
-            "Couldn't create extension directory.");
+        ReportExtensionInstallError("Couldn't create extension directory.");
         return false;
       }
     }
   }
   if (!file_util::Move(source_dir, dest_dir)) {
-    ReportExtensionInstallError(frontend, extension_path,
-        "Couldn't move temporary directory.");
+    ReportExtensionInstallError("Couldn't move temporary directory.");
     return false;
   }
 
   return true;
 }
 
-bool ExtensionsServiceBackend::SetCurrentVersion(
-    const FilePath& extension_path,
-    const FilePath& dest_dir,
-    std::string version,
-    scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
+bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir,
+                                                 std::string version) {
   // Write out the new CurrentVersion file.
   // <profile>/Extension/<name>/CurrentVersion
   FilePath current_version =
@@ -529,15 +560,13 @@
     current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old"));
   if (file_util::PathExists(current_version_old)) {
     if (!file_util::Delete(current_version_old, false)) {
-      ReportExtensionInstallError(frontend, extension_path,
-          "Couldn't remove CurrentVersion_old file.");
+      ReportExtensionInstallError("Couldn't remove CurrentVersion_old file.");
       return false;
     }
   }
   if (file_util::PathExists(current_version)) {
     if (!file_util::Move(current_version, current_version_old)) {
-      ReportExtensionInstallError(frontend, extension_path,
-          "Couldn't move CurrentVersion file.");
+      ReportExtensionInstallError("Couldn't move CurrentVersion file.");
       return false;
     }
   }
@@ -546,103 +575,250 @@
   if (stream.Open(current_version, flags) != 0)
     return false;
   if (stream.Write(version.c_str(), version.size(), NULL) < 0) {
+
     // Restore the old CurrentVersion.
     if (file_util::PathExists(current_version_old)) {
       if (!file_util::Move(current_version_old, current_version)) {
         LOG(WARNING) << "couldn't restore " << current_version_old.value() << 
             " to " << current_version.value();
+
         // TODO(erikkay): This is an ugly state to be in.  Try harder?
       }
     }
-    ReportExtensionInstallError(frontend, extension_path, 
-        "Couldn't create CurrentVersion file.");
+    ReportExtensionInstallError("Couldn't create CurrentVersion file.");
     return false;
   }
   return true;
 }
 
-bool ExtensionsServiceBackend::InstallExtension(
+void ExtensionsServiceBackend::InstallExtension(
     const FilePath& extension_path,
     const FilePath& install_dir,
+    bool alert_on_error,
     scoped_refptr<ExtensionsServiceFrontendInterface> frontend) {
   LOG(INFO) << "Installing extension " << extension_path.value();
 
-  // <profile>/Extensions/INSTALL_TEMP
-  FilePath temp_dir = install_dir.AppendASCII(kTempExtensionName);
-  // Ensure we're starting with a clean slate.
-  if (file_util::PathExists(temp_dir)) {
-    if (!file_util::Delete(temp_dir, true)) {
-      ReportExtensionInstallError(frontend, extension_path,
-          "Couldn't delete existing temporary directory.");
-      return false;
-    }
-  }
-  ScopedTempDir scoped_temp;
-  scoped_temp.Set(temp_dir);
-  if (!scoped_temp.IsValid()) {
-    ReportExtensionInstallError(frontend, extension_path,
-        "Couldn't create temporary directory.");
-    return false;
-  }
+  frontend_ = frontend;
+  alert_on_error_ = alert_on_error;
+  external_install_ = false;
+  extension_path_ = extension_path;
+  install_directory_ = install_dir;
+
+  InstallOrUpdateExtension(std::string());
+}
+
+void ExtensionsServiceBackend::InstallOrUpdateExtension(
+    const std::string& expected_id) {
+  bool update = false;
 
   // Read and verify the extension.
-  scoped_ptr<DictionaryValue> manifest(ReadManifest(extension_path, frontend));
+  scoped_ptr<DictionaryValue> manifest(ReadManifest());
   if (!manifest.get()) {
+
     // ReadManifest has already reported the extension error.
-    return false;
+    return;
   }
   DictionaryValue* dict = manifest.get();
   Extension extension;
   std::string error;
   if (!extension.InitFromValue(*dict, &error)) {
-    ReportExtensionInstallError(frontend, extension_path,
-        "Invalid extension manifest.");
-    return false;
+    ReportExtensionInstallError("Invalid extension manifest.");
+    return;
+  }
+
+  // If an expected id was provided, make sure it matches.
+  if (expected_id.length() && expected_id != extension.id()) {
+    ReportExtensionInstallError(
+        "ID in new extension manifest does not match expected ID.");
+    return;
   }
 
   // <profile>/Extensions/<id>
-  FilePath dest_dir = install_dir.AppendASCII(extension.id());
+  FilePath dest_dir = install_directory_.AppendASCII(extension.id());
   std::string version = extension.VersionString();
-  if (!CheckCurrentVersion(extension_path, version, dest_dir, frontend))
-    return false;
+  std::string current_version;
+  if (ReadCurrentVersion(dest_dir, &current_version)) {
+    if (!CheckCurrentVersion(version, current_version, dest_dir))
+      return;
+    update = true;
+  }
+
+  // <profile>/Extensions/INSTALL_TEMP
+  FilePath temp_dir = install_directory_.AppendASCII(kTempExtensionName);
+
+  // Ensure we're starting with a clean slate.
+  if (file_util::PathExists(temp_dir)) {
+    if (!file_util::Delete(temp_dir, true)) {
+      ReportExtensionInstallError(
+          "Couldn't delete existing temporary directory.");
+      return;
+    }
+  }
+  ScopedTempDir scoped_temp;
+  scoped_temp.Set(temp_dir);
+  if (!scoped_temp.IsValid()) {
+    ReportExtensionInstallError("Couldn't create temporary directory.");
+    return;
+  }
 
   // <profile>/Extensions/INSTALL_TEMP/<version>
   FilePath temp_version = temp_dir.AppendASCII(version);
-  if (!UnzipExtension(extension_path, temp_version, frontend))
-    return false;
+  file_util::CreateDirectory(temp_version);
+  if (!Unzip(extension_path_, temp_version, NULL)) {
+    ReportExtensionInstallError("Couldn't unzip extension.");
+    return;
+  }
 
   // <profile>/Extensions/<dir_name>/<version>
   FilePath version_dir = dest_dir.AppendASCII(version);
-  if (!InstallDirSafely(extension_path, temp_version, version_dir, frontend))
-    return false;
+  if (!InstallDirSafely(temp_version, version_dir))
+    return;
 
-  if (!SetCurrentVersion(extension_path, dest_dir, version, frontend)) {
+  if (!SetCurrentVersion(dest_dir, version)) {
     if (!file_util::Delete(version_dir, true))
       LOG(WARNING) << "Can't remove " << dest_dir.value();
-    return false;
+    return;
   }
 
-  ReportExtensionInstalled(frontend, dest_dir);
-  return true;
+  if (external_install_) {
+
+    // To mark that this extension was installed from an external source,
+    // create a zero-length file.  At load time, this is used to indicate
+    // that the extension should be uninstalled.
+    // TODO(erikkay): move this into per-extension config storage when
+    // it appears.
+    FilePath marker = version_dir.AppendASCII(kExternalInstallFile);
+    file_util::WriteFile(marker, NULL, 0);
+  }
+
+  ReportExtensionInstalled(dest_dir, update);
 }
 
 void ExtensionsServiceBackend::ReportExtensionInstallError(
-    ExtensionsServiceFrontendInterface *frontend, const FilePath& path,
     const std::string &error) {
+
   // TODO(erikkay): note that this isn't guaranteed to work properly on Linux.
-  std::string path_str = WideToASCII(path.ToWStringHack());
+  std::string path_str = WideToASCII(extension_path_.ToWStringHack());
   std::string message =
       StringPrintf("Could not install extension from '%s'. %s",
                    path_str.c_str(), error.c_str());
-  frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
-      frontend, &ExtensionsServiceFrontendInterface::OnExtensionInstallError,
-      message));
+  frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
+      frontend_, &ExtensionsServiceFrontendInterface::OnExtensionInstallError,
+      alert_on_error_, message));
 }
 
 void ExtensionsServiceBackend::ReportExtensionInstalled(
-    ExtensionsServiceFrontendInterface *frontend, FilePath path) {
-  frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
-      frontend,
+    FilePath path, bool update) {
+  frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod(
+      frontend_,
       &ExtensionsServiceFrontendInterface::OnExtensionInstalled,
-      path));
+      path,
+      update));
+
+  // After it's installed, load it right away with the same settings.
+  extension_path_ = path;
+  LoadExtensionCurrentVersion();
+}
+
+// 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
+// check that location for a .crx file, which it will then install locally if
+// a new version is available.
+void ExtensionsServiceBackend::CheckForExternalUpdates(
+    const FilePath& install_dir,
+    scoped_refptr<ExtensionsServiceFrontendInterface> 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
+  // trusted.  In general, if something has HKLM or filesystem access,
+  // they could install an extension manually themselves anyway.
+  alert_on_error_ = false;
+  frontend_ = frontend;
+  external_install_ = true;
+  install_directory_ = install_dir;
+
+#if defined(OS_WIN)
+  HKEY reg_root = HKEY_LOCAL_MACHINE;
+  RegistryKeyIterator iterator(reg_root, kRegistryExtensions);
+  while (iterator.Valid()) {
+    RegKey key;
+    std::wstring key_path = kRegistryExtensions;
+    key_path.append(L"\\");
+    key_path.append(iterator.Name());
+    if (key.Open(reg_root, key_path.c_str())) {
+      std::wstring extension_path;
+      if (key.ReadValue(kRegistryExtensionPath, &extension_path)) {
+        std::string id = WideToASCII(iterator.Name());
+        extension_path_ = FilePath(extension_path);
+        std::wstring extension_version;
+        if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) {
+          if (ShouldInstall(id, WideToASCII(extension_version)))
+            InstallOrUpdateExtension(id);
+        } else {
+
+          // TODO(erikkay): find a way to get this into about:extensions
+          LOG(WARNING) << "Missing value " << kRegistryExtensionVersion <<
+              " for key " << key_path;
+        }
+      } else {
+
+        // TODO(erikkay): find a way to get this into about:extensions
+        LOG(WARNING) << "Missing value " << kRegistryExtensionPath <<
+            " for key " << key_path;
+      }
+    }
+    ++iterator;
+  }
+#else
+  NOTREACHED();
+#endif
+}
+
+bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& path,
+                                                      const std::string& id) {
+  FilePath external_file = path.AppendASCII(kExternalInstallFile);
+  if (file_util::PathExists(external_file)) {
+#if defined(OS_WIN)
+    HKEY reg_root = HKEY_LOCAL_MACHINE;
+    RegKey key;
+    std::wstring key_path = kRegistryExtensions;
+    key_path.append(L"\\");
+    key_path.append(ASCIIToWide(id));
+
+    // If the key doesn't exist, then we should uninstall.
+    return !key.Open(reg_root, key_path.c_str());
+#else
+    NOTREACHED();
+#endif
+  }
+  return false;
+}
+
+// Assumes that the extension isn't currently loaded or in use.
+void ExtensionsServiceBackend::UninstallExtension(const FilePath& path) {
+  FilePath parent = path.DirName();
+  FilePath version =
+      parent.AppendASCII(ExtensionsService::kCurrentVersionFileName);
+  bool version_exists = file_util::PathExists(version);
+  DCHECK(version_exists);
+  if (!version_exists) {
+    LOG(WARNING) << "Asked to uninstall bogus extension dir " << parent.value();
+    return;
+  }
+  if (!file_util::Delete(parent, true)) {
+    LOG(WARNING) << "Failed to delete " << parent.value();
+  }
+}
+
+bool ExtensionsServiceBackend::ShouldInstall(const std::string& id,
+                                             const std::string& version) {
+  FilePath dir(install_directory_.AppendASCII(id.c_str()));
+  std::string current_version;
+  if (ReadCurrentVersion(dir, &current_version)) {
+    return !CheckCurrentVersion(version, current_version, dir);
+  }
+  return true;
 }