Added support for pending extensions to ExtensionsService and
ExtensionUpdater.  This is needed for theme syncing.

Basically a pending extension is an (id, update_url) pair.  This change
makes it so that one can pass pending extensions to the extension service
and they will be installed if necessary on the next auto-update cycle.

BUG=32414
TEST=unittests, trybots, in-progress theme syncing change

Review URL: http://codereview.chromium.org/1232003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@42711 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 28ba10c..6807f6a 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -12,6 +12,7 @@
 #include "base/string_util.h"
 #include "base/time.h"
 #include "base/values.h"
+#include "base/version.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_thread.h"
 #include "chrome/browser/debugger/devtools_manager.h"
@@ -97,6 +98,21 @@
 
 }  // namespace
 
+PendingExtensionInfo::PendingExtensionInfo(const GURL& update_url,
+                                           const Version& version,
+                                           bool is_theme,
+                                           bool install_silently)
+    : update_url(update_url),
+      version(version),
+      is_theme(is_theme),
+      install_silently(install_silently) {}
+
+PendingExtensionInfo::PendingExtensionInfo()
+    : update_url(),
+      version(),
+      is_theme(false),
+      install_silently(false) {}
+
 // ExtensionsService.
 
 const char* ExtensionsService::kInstallDirectoryName = "Extensions";
@@ -203,19 +219,41 @@
   installer->InstallCrx(extension_path);
 }
 
+namespace {
+  // TODO(akalin): Put this somewhere where both crx_installer.cc and
+  // this file can use it.
+  void DeleteFileHelper(const FilePath& path, bool recursive) {
+    DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE));
+    file_util::Delete(path, recursive);
+  }
+}  // namespace
+
 void ExtensionsService::UpdateExtension(const std::string& id,
                                         const FilePath& extension_path,
                                         const GURL& download_url) {
-  if (!GetExtensionByIdInternal(id, true, true)) {
-    LOG(WARNING) << "Will not update extension " << id << " because it is not "
-                 << "installed";
+  PendingExtensionMap::const_iterator it = pending_extensions_.find(id);
+  if ((it == pending_extensions_.end()) &&
+      !GetExtensionByIdInternal(id, true, true)) {
+    LOG(WARNING) << "Will not update extension " << id
+                 << " because it is not installed or pending";
+    // Delete extension_path since we're not creating a CrxInstaller
+    // that would do it for us.
+    ChromeThread::PostTask(
+        ChromeThread::FILE, FROM_HERE,
+        NewRunnableFunction(&DeleteFileHelper, extension_path, false));
     return;
   }
 
+  // We want a silent install only for non-pending extensions and
+  // pending extensions that have install_silently set.
+  ExtensionInstallUI* client =
+      ((it == pending_extensions_.end()) || it->second.install_silently) ?
+      NULL : new ExtensionInstallUI(profile_);
+
   scoped_refptr<CrxInstaller> installer(
       new CrxInstaller(install_directory_,
                        this,  // frontend
-                       NULL));  // no client (silent install)
+                       client));
   installer->set_expected_id(id);
   installer->set_delete_source(true);
   installer->set_force_web_origin_to_download_url(true);
@@ -223,6 +261,23 @@
   installer->InstallCrx(extension_path);
 }
 
+void ExtensionsService::AddPendingExtension(
+    const std::string& id, const GURL& update_url,
+    const Version& version, bool is_theme, bool install_silently) {
+  if (GetExtensionByIdInternal(id, true, true)) {
+    return;
+  }
+  AddPendingExtensionInternal(id, update_url, version,
+                              is_theme, install_silently);
+}
+
+void ExtensionsService::AddPendingExtensionInternal(
+    const std::string& id, const GURL& update_url,
+    const Version& version, bool is_theme, bool install_silently) {
+  pending_extensions_[id] =
+      PendingExtensionInfo(update_url, version, is_theme, install_silently);
+}
+
 void ExtensionsService::ReloadExtension(const std::string& extension_id) {
   FilePath path;
   Extension* current_extension = GetExtensionById(extension_id, false);
@@ -823,6 +878,22 @@
 
 void ExtensionsService::OnExtensionInstalled(Extension* extension,
                                              bool allow_privilege_increase) {
+  PendingExtensionMap::iterator it =
+      pending_extensions_.find(extension->id());
+  if (it != pending_extensions_.end() &&
+      (it->second.is_theme != extension->IsTheme())) {
+    LOG(WARNING) << "Not installing pending extension " << extension->id()
+                 << " with is_theme = " << extension->IsTheme()
+                 << "; expected is_theme = " << it->second.is_theme;
+    // Delete the extension directory since we're not going to load
+    // it.
+    ChromeThread::PostTask(
+        ChromeThread::FILE, FROM_HERE,
+        NewRunnableFunction(&DeleteFileHelper, extension->path(), true));
+    delete extension;
+    return;
+  }
+
   extension_prefs_->OnExtensionInstalled(extension);
 
   // If the extension is a theme, tell the profile (and therefore ThemeProvider)
@@ -841,6 +912,11 @@
 
   // Also load the extension.
   OnExtensionLoaded(extension, allow_privilege_increase);
+
+  // Erase any pending extension.
+  if (it != pending_extensions_.end()) {
+    pending_extensions_.erase(it);
+  }
 }
 
 void ExtensionsService::OnExtensionOverinstallAttempted(const std::string& id) {