[Extensions] Rework inline installation observation

Instead of observing through the WebstoreAPI, observe directly in the TabHelper.
This is a great deal less code, more direct, and also fixes a lifetime issue
with the TabHelper being deleted before the inline installation completes.

BUG=613949

Review-Url: https://codereview.chromium.org/2103663002
Cr-Commit-Position: refs/heads/master@{#403188}
diff --git a/chrome/browser/extensions/api/webstore/webstore_api.cc b/chrome/browser/extensions/api/webstore/webstore_api.cc
deleted file mode 100644
index 007b85d..0000000
--- a/chrome/browser/extensions/api/webstore/webstore_api.cc
+++ /dev/null
@@ -1,141 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/extensions/api/webstore/webstore_api.h"
-
-#include "base/lazy_instance.h"
-#include "base/values.h"
-#include "chrome/browser/extensions/install_tracker.h"
-#include "chrome/browser/extensions/install_tracker_factory.h"
-#include "chrome/common/extensions/chrome_extension_messages.h"
-#include "extensions/browser/extension_system.h"
-#include "ipc/ipc_sender.h"
-
-namespace extensions {
-
-namespace {
-
-base::LazyInstance<BrowserContextKeyedAPIFactory<WebstoreAPI> > g_factory =
-    LAZY_INSTANCE_INITIALIZER;
-
-}  // namespace
-
-struct WebstoreAPI::ObservedInstallInfo {
-  ObservedInstallInfo(int routing_id,
-                      const std::string& extension_id,
-                      IPC::Sender* ipc_sender);
-  ~ObservedInstallInfo();
-
-  int routing_id;
-  std::string extension_id;
-  IPC::Sender* ipc_sender;
-};
-
-WebstoreAPI::ObservedInstallInfo::ObservedInstallInfo(
-    int routing_id,
-    const std::string& extension_id,
-    IPC::Sender* ipc_sender)
-    : routing_id(routing_id),
-      extension_id(extension_id),
-      ipc_sender(ipc_sender) {}
-
-WebstoreAPI::ObservedInstallInfo::~ObservedInstallInfo() {}
-
-WebstoreAPI::WebstoreAPI(content::BrowserContext* browser_context)
-    : browser_context_(browser_context),
-      install_observer_(
-          new ScopedObserver<InstallTracker, InstallObserver>(this)) {
-  install_observer_->Add(
-      InstallTrackerFactory::GetForBrowserContext(browser_context));
-}
-
-WebstoreAPI::~WebstoreAPI() {}
-
-// static
-WebstoreAPI* WebstoreAPI::Get(content::BrowserContext* browser_context) {
-  return BrowserContextKeyedAPIFactory<WebstoreAPI>::Get(browser_context);
-}
-
-void WebstoreAPI::OnInlineInstallStart(int routing_id,
-                                       IPC::Sender* ipc_sender,
-                                       const std::string& extension_id,
-                                       int listeners_mask) {
-  if (listeners_mask & api::webstore::INSTALL_STAGE_LISTENER) {
-    install_stage_listeners_.push_back(
-        ObservedInstallInfo(routing_id, extension_id, ipc_sender));
-  }
-
-  if (listeners_mask & api::webstore::DOWNLOAD_PROGRESS_LISTENER) {
-    download_progress_listeners_.push_back(
-        ObservedInstallInfo(routing_id, extension_id, ipc_sender));
-  }
-}
-
-void WebstoreAPI::OnInlineInstallFinished(int routing_id,
-                                          const std::string& extension_id) {
-  RemoveListeners(routing_id, extension_id, &download_progress_listeners_);
-  RemoveListeners(routing_id, extension_id, &install_stage_listeners_);
-}
-
-void WebstoreAPI::OnBeginExtensionDownload(const std::string& extension_id) {
-  SendInstallMessageIfObserved(extension_id,
-                               api::webstore::INSTALL_STAGE_DOWNLOADING);
-}
-
-void WebstoreAPI::OnDownloadProgress(const std::string& extension_id,
-                                     int percent_downloaded) {
-  for (ObservedInstallInfoList::const_iterator iter =
-           download_progress_listeners_.begin();
-       iter != download_progress_listeners_.end();
-       ++iter) {
-    if (iter->extension_id == extension_id) {
-      iter->ipc_sender->Send(new ExtensionMsg_InlineInstallDownloadProgress(
-          iter->routing_id, percent_downloaded));
-    }
-  }
-}
-
-void WebstoreAPI::OnBeginCrxInstall(const std::string& extension_id) {
-  SendInstallMessageIfObserved(extension_id,
-                               api::webstore::INSTALL_STAGE_INSTALLING);
-}
-
-void WebstoreAPI::OnShutdown() {
-  install_observer_.reset();
-}
-
-void WebstoreAPI::Shutdown() {}
-
-// static
-BrowserContextKeyedAPIFactory<WebstoreAPI>* WebstoreAPI::GetFactoryInstance() {
-  return g_factory.Pointer();
-}
-
-void WebstoreAPI::SendInstallMessageIfObserved(
-    const std::string& extension_id,
-    api::webstore::InstallStage install_stage) {
-  for (ObservedInstallInfoList::const_iterator iter =
-           install_stage_listeners_.begin();
-       iter != install_stage_listeners_.end();
-       ++iter) {
-    if (iter->extension_id == extension_id) {
-      iter->ipc_sender->Send(new ExtensionMsg_InlineInstallStageChanged(
-          iter->routing_id, install_stage));
-    }
-  }
-}
-
-void WebstoreAPI::RemoveListeners(int routing_id,
-                                  const std::string& extension_id,
-                                  ObservedInstallInfoList* listeners) {
-  for (ObservedInstallInfoList::iterator iter = listeners->begin();
-       iter != listeners->end();) {
-    if (iter->extension_id == extension_id && iter->routing_id == routing_id)
-      iter = listeners->erase(iter);
-    else
-      ++iter;
-  }
-}
-
-}  // namespace extensions
diff --git a/chrome/browser/extensions/api/webstore/webstore_api.h b/chrome/browser/extensions/api/webstore/webstore_api.h
deleted file mode 100644
index 378d581b..0000000
--- a/chrome/browser/extensions/api/webstore/webstore_api.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_H_
-#define CHROME_BROWSER_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_H_
-
-#include <list>
-#include <memory>
-#include <string>
-
-#include "base/macros.h"
-#include "base/scoped_observer.h"
-#include "chrome/browser/extensions/install_observer.h"
-#include "chrome/common/extensions/api/webstore/webstore_api_constants.h"
-#include "extensions/browser/browser_context_keyed_api_factory.h"
-#include "extensions/browser/event_router.h"
-
-namespace base {
-class ListValue;
-}
-
-namespace content {
-class BrowserContext;
-}
-
-namespace IPC {
-class Sender;
-}
-
-namespace extensions {
-class InstallTracker;
-
-class WebstoreAPI : public BrowserContextKeyedAPI,
-                    public InstallObserver {
- public:
-  explicit WebstoreAPI(content::BrowserContext* browser_context);
-  ~WebstoreAPI() override;
-
-  static WebstoreAPI* Get(content::BrowserContext* browser_context);
-
-  // Called whenever an inline extension install is started. Examines
-  // |listener_mask| to determine if a download progress or install
-  // stage listener should be added.
-  // |routing_id| refers to the id to which we send any return messages;
-  // |ipc_sender| is the sender through which we send them (typically this
-  // is the TabHelper which started the inline install).
-  void OnInlineInstallStart(int routing_id,
-                            IPC::Sender* ipc_sender,
-                            const std::string& extension_id,
-                            int listener_mask);
-
-  // Called when an inline extension install finishes. Removes any listeners
-  // related to the |routing_id|-|extension_id| pair.
-  void OnInlineInstallFinished(int routing_id, const std::string& extension_id);
-
-  // BrowserContextKeyedAPI implementation.
-  static BrowserContextKeyedAPIFactory<WebstoreAPI>* GetFactoryInstance();
-
- private:
-  friend class BrowserContextKeyedAPIFactory<WebstoreAPI>;
-
-  // A simple struct to hold our listeners' information for each observed
-  // install.
-  struct ObservedInstallInfo;
-  typedef std::list<ObservedInstallInfo> ObservedInstallInfoList;
-
-  // Sends an installation stage update message if we are observing
-  // the extension's install.
-  void SendInstallMessageIfObserved(const std::string& extension_id,
-                                    api::webstore::InstallStage install_stage);
-
-  // Removes listeners for the given |extension_id|-|routing_id| pair from
-  // |listeners|.
-  void RemoveListeners(int routing_id,
-                       const std::string& extension_id,
-                       ObservedInstallInfoList* listeners);
-
-  // InstallObserver implementation.
-  void OnBeginExtensionDownload(const std::string& extension_id) override;
-  void OnDownloadProgress(const std::string& extension_id,
-                          int percent_downloaded) override;
-  void OnBeginCrxInstall(const std::string& extension_id) override;
-  void OnShutdown() override;
-
-  // BrowserContextKeyedService implementation.
-  void Shutdown() override;
-
-  // BrowserContextKeyedAPI implementation.
-  static const char* service_name() { return "WebstoreAPI"; }
-  static const bool kServiceIsNULLWhileTesting = true;
-
-  ObservedInstallInfoList download_progress_listeners_;
-  ObservedInstallInfoList install_stage_listeners_;
-  content::BrowserContext* browser_context_;
-  std::unique_ptr<ScopedObserver<InstallTracker, InstallObserver>>
-      install_observer_;
-
-  DISALLOW_COPY_AND_ASSIGN(WebstoreAPI);
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_API_WEBSTORE_WEBSTORE_API_H_
diff --git a/chrome/browser/extensions/browser_context_keyed_service_factories.cc b/chrome/browser/extensions/browser_context_keyed_service_factories.cc
index ddbd97f..9b39773 100644
--- a/chrome/browser/extensions/browser_context_keyed_service_factories.cc
+++ b/chrome/browser/extensions/browser_context_keyed_service_factories.cc
@@ -42,7 +42,6 @@
 #include "chrome/browser/extensions/api/tabs/tabs_windows_api.h"
 #include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h"
 #include "chrome/browser/extensions/api/webrtc_audio_private/webrtc_audio_private_api.h"
-#include "chrome/browser/extensions/api/webstore/webstore_api.h"
 #include "chrome/browser/extensions/extension_garbage_collector_factory.h"
 #include "chrome/browser/extensions/extension_gcm_app_handler.h"
 #include "chrome/browser/extensions/extension_storage_monitor_factory.h"
@@ -138,7 +137,6 @@
   extensions::WarningBadgeServiceFactory::GetInstance();
   extensions::WebNavigationAPI::GetFactoryInstance();
   extensions::WebrtcAudioPrivateEventService::GetFactoryInstance();
-  extensions::WebstoreAPI::GetFactoryInstance();
 #if defined(OS_CHROMEOS)
   file_manager::EventRouterFactory::GetInstance();
 #endif
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index bc92af0..7f66c06 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -12,12 +12,14 @@
 #include "chrome/browser/extensions/activity_log/activity_log.h"
 #include "chrome/browser/extensions/api/declarative_content/chrome_content_rules_registry.h"
 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
-#include "chrome/browser/extensions/api/webstore/webstore_api.h"
 #include "chrome/browser/extensions/bookmark_app_helper.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
 #include "chrome/browser/extensions/extension_action_runner.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/install_observer.h"
+#include "chrome/browser/extensions/install_tracker.h"
+#include "chrome/browser/extensions/install_tracker_factory.h"
 #include "chrome/browser/extensions/location_bar_controller.h"
 #include "chrome/browser/extensions/webstore_inline_installer.h"
 #include "chrome/browser/extensions/webstore_inline_installer_factory.h"
@@ -28,6 +30,7 @@
 #include "chrome/browser/ui/browser_dialogs.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/extensions/api/webstore/webstore_api_constants.h"
 #include "chrome/common/extensions/chrome_extension_messages.h"
 #include "chrome/common/extensions/extension_constants.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
@@ -72,6 +75,76 @@
 
 namespace extensions {
 
+// A helper class to watch the progress of inline installation and update the
+// renderer. Owned by the TabHelper.
+class TabHelper::InlineInstallObserver : public InstallObserver {
+ public:
+  InlineInstallObserver(TabHelper* tab_helper,
+                        content::BrowserContext* browser_context,
+                        int routing_id,
+                        const std::string& extension_id,
+                        bool observe_download_progress,
+                        bool observe_install_stage)
+      : tab_helper_(tab_helper),
+        routing_id_(routing_id),
+        extension_id_(extension_id),
+        observe_download_progress_(observe_download_progress),
+        observe_install_stage_(observe_install_stage),
+        install_observer_(this) {
+    DCHECK(tab_helper);
+    DCHECK(observe_download_progress || observe_install_stage);
+    InstallTracker* install_tracker =
+        InstallTrackerFactory::GetForBrowserContext(browser_context);
+    if (install_tracker)
+      install_observer_.Add(install_tracker);
+  }
+  ~InlineInstallObserver() override {}
+
+ private:
+  // InstallObserver:
+  void OnBeginExtensionDownload(const std::string& extension_id) override {
+    SendInstallStageChangedMessage(extension_id,
+                                   api::webstore::INSTALL_STAGE_DOWNLOADING);
+  }
+  void OnDownloadProgress(const std::string& extension_id,
+                          int percent_downloaded) override {
+    if (observe_download_progress_ && extension_id == extension_id_) {
+      tab_helper_->Send(new ExtensionMsg_InlineInstallDownloadProgress(
+          routing_id_, percent_downloaded));
+    }
+  }
+  void OnBeginCrxInstall(const std::string& extension_id) override {
+    SendInstallStageChangedMessage(extension_id,
+                                   api::webstore::INSTALL_STAGE_INSTALLING);
+  }
+  void OnShutdown() override { install_observer_.RemoveAll(); }
+
+  void SendInstallStageChangedMessage(const std::string& extension_id,
+                                      api::webstore::InstallStage stage) {
+    if (observe_install_stage_ && extension_id == extension_id_) {
+      tab_helper_->Send(
+          new ExtensionMsg_InlineInstallStageChanged(routing_id_, stage));
+    }
+  }
+
+  // The owning TabHelper (guaranteed to be valid).
+  TabHelper* const tab_helper_;
+
+  // The routing id to use in sending IPC updates.
+  int routing_id_;
+
+  // The id of the extension to observe.
+  std::string extension_id_;
+
+  // Whether or not to observe download/install progress.
+  const bool observe_download_progress_;
+  const bool observe_install_stage_;
+
+  ScopedObserver<InstallTracker, InstallObserver> install_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(InlineInstallObserver);
+};
+
 TabHelper::TabHelper(content::WebContents* web_contents)
     : content::WebContentsObserver(web_contents),
       profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
@@ -369,6 +442,16 @@
     NOTREACHED();
     return;
   }
+
+  if (pending_inline_installations_.count(webstore_item_id) != 0) {
+    Send(new ExtensionMsg_InlineWebstoreInstallResponse(
+        return_route_id, install_id, false,
+        webstore_install::kInstallInProgressError,
+        webstore_install::INSTALL_IN_PROGRESS));
+    return;
+  }
+
+  pending_inline_installations_.insert(webstore_item_id);
   // Inform the Webstore API that an inline install is happening, in case the
   // page requested status updates.
   ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
@@ -382,25 +465,30 @@
       // For clarity, explicitly end any prior reenable process.
       extension_reenabler_.reset();
       extension_reenabler_ = ExtensionReenabler::PromptForReenable(
-          registry->disabled_extensions().GetByID(webstore_item_id),
-          profile_,
-          web_contents(),
-          requestor_url,
+          registry->disabled_extensions().GetByID(webstore_item_id), profile_,
+          web_contents(), requestor_url,
           base::Bind(&TabHelper::OnReenableComplete,
-                     weak_ptr_factory_.GetWeakPtr(),
-                     install_id,
-                     return_route_id));
+                     weak_ptr_factory_.GetWeakPtr(), install_id,
+                     return_route_id, webstore_item_id));
   } else {
     // TODO(devlin): We should adddress the case of the extension already
     // being installed and enabled.
-    WebstoreAPI::Get(profile_)->OnInlineInstallStart(
-        return_route_id, this, webstore_item_id, listeners_mask);
+    bool observe_download_progress =
+        (listeners_mask & api::webstore::DOWNLOAD_PROGRESS_LISTENER) != 0;
+    bool observe_install_stage =
+        (listeners_mask & api::webstore::INSTALL_STAGE_LISTENER) != 0;
+    if (observe_install_stage || observe_download_progress) {
+      DCHECK_EQ(0u, install_observers_.count(webstore_item_id));
+      install_observers_[webstore_item_id] =
+          base::MakeUnique<InlineInstallObserver>(
+              this, web_contents()->GetBrowserContext(), return_route_id,
+              webstore_item_id, observe_download_progress,
+              observe_install_stage);
+    }
 
-    WebstoreStandaloneInstaller::Callback callback =
-        base::Bind(&TabHelper::OnInlineInstallComplete,
-                   base::Unretained(this),
-                   install_id,
-                   return_route_id);
+    WebstoreStandaloneInstaller::Callback callback = base::Bind(
+        &TabHelper::OnInlineInstallComplete, weak_ptr_factory_.GetWeakPtr(),
+        install_id, return_route_id, webstore_item_id);
     scoped_refptr<WebstoreInlineInstaller> installer(
         webstore_inline_installer_factory_->CreateInstaller(
             web_contents(), host, webstore_item_id, requestor_url, callback));
@@ -494,8 +582,8 @@
 
 void TabHelper::OnReenableComplete(int install_id,
                                    int return_route_id,
+                                   const std::string& extension_id,
                                    ExtensionReenabler::ReenableResult result) {
-  extension_reenabler_.reset();
   // Map the re-enable results to webstore-install results.
   webstore_install::Result webstore_result = webstore_install::SUCCESS;
   std::string error;
@@ -516,18 +604,23 @@
       break;
   }
 
-  OnInlineInstallComplete(install_id,
-                          return_route_id,
-                          result == ExtensionReenabler::REENABLE_SUCCESS,
-                          error,
+  OnInlineInstallComplete(install_id, return_route_id, extension_id,
+                          result == ExtensionReenabler::REENABLE_SUCCESS, error,
                           webstore_result);
+  // Note: ExtensionReenabler contained the callback with the curried-in
+  // |extension_id|; delete it last.
+  extension_reenabler_.reset();
 }
 
 void TabHelper::OnInlineInstallComplete(int install_id,
                                         int return_route_id,
+                                        const std::string& extension_id,
                                         bool success,
                                         const std::string& error,
                                         webstore_install::Result result) {
+  DCHECK_EQ(1u, pending_inline_installations_.count(extension_id));
+  pending_inline_installations_.erase(extension_id);
+  install_observers_.erase(extension_id);
   Send(new ExtensionMsg_InlineWebstoreInstallResponse(
       return_route_id,
       install_id,
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index c3d368ec..6a79054 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -126,6 +126,8 @@
       WebstoreInlineInstallerFactory* factory);
 
  private:
+  class InlineInstallObserver;
+
   // Utility function to invoke member functions on all relevant
   // ContentRulesRegistries.
   template <class Func>
@@ -198,6 +200,7 @@
   // WebstoreStandaloneInstaller::Callback.
   void OnInlineInstallComplete(int install_id,
                                int return_route_id,
+                               const std::string& extension_id,
                                bool success,
                                const std::string& error,
                                webstore_install::Result result);
@@ -205,6 +208,7 @@
   // ExtensionReenabler::Callback.
   void OnReenableComplete(int install_id,
                           int return_route_id,
+                          const std::string& extension_id,
                           ExtensionReenabler::ReenableResult result);
 
   // content::NotificationObserver.
@@ -270,6 +274,14 @@
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
       registry_observer_;
 
+  // Map of extension id -> InlineInstallObserver for inline installations that
+  // have progress listeners.
+  std::map<std::string, std::unique_ptr<InlineInstallObserver>>
+      install_observers_;
+
+  // The set of extension ids that are currently being installed.
+  std::set<std::string> pending_inline_installations_;
+
   // Vend weak pointers that can be invalidated to stop in-progress loads.
   base::WeakPtrFactory<TabHelper> image_loader_ptr_factory_;
 
diff --git a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc
index d6c561dc0..a2a25b7 100644
--- a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc
+++ b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc
@@ -309,6 +309,14 @@
             ExtensionInstallPrompt::g_last_prompt_type_for_tests);
 }
 
+// Test calling chrome.webstore.install() twice without waiting for the first to
+// finish.
+IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, DoubleInlineInstallTest) {
+  ui_test_utils::NavigateToURL(
+      browser(), GenerateTestServerUrl(kAppDomain, "double_install.html"));
+  RunTest("runTest");
+}
+
 class WebstoreInlineInstallerListenerTest : public WebstoreInlineInstallerTest {
  public:
   WebstoreInlineInstallerListenerTest() {}
@@ -335,6 +343,26 @@
 
 IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerListenerTest, BothListenersTest) {
   RunTest("both_listeners.html");
+  // The extension should be installed.
+  ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
+  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
+
+  // Rinse and repeat: uninstall the extension, open a new tab, and install it
+  // again. Regression test for crbug.com/613949.
+  extension_service()->UninstallExtension(
+      kTestExtensionId, UNINSTALL_REASON_FOR_TESTING,
+      base::Bind(&base::DoNothing), nullptr);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(registry->enabled_extensions().GetByID(kTestExtensionId));
+  int old_tab_index = browser()->tab_strip_model()->active_index();
+  ui_test_utils::NavigateToURLWithDisposition(
+      browser(), GenerateTestServerUrl(kAppDomain, "both_listeners.html"),
+      NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
+  DCHECK_NE(old_tab_index, browser()->tab_strip_model()->active_index());
+  browser()->tab_strip_model()->CloseWebContentsAt(old_tab_index,
+                                                   TabStripModel::CLOSE_NONE);
+  WebstoreInstallerTest::RunTest("runTest");
+  EXPECT_TRUE(registry->enabled_extensions().GetByID(kTestExtensionId));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/webstore_standalone_installer.cc b/chrome/browser/extensions/webstore_standalone_installer.cc
index 33c164f..ac7f2f22 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.cc
+++ b/chrome/browser/extensions/webstore_standalone_installer.cc
@@ -29,15 +29,6 @@
 
 namespace extensions {
 
-const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
-const char kWebstoreRequestError[] =
-    "Could not fetch data from the Chrome Web Store";
-const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
-const char kInvalidManifestError[] = "Invalid manifest";
-const char kUserCancelledError[] = "User cancelled install";
-const char kExtensionIsBlacklisted[] = "Extension is blacklisted";
-const char kInstallInProgressError[] = "An install is already in progress";
-
 WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
     const std::string& webstore_item_id,
     Profile* profile,
@@ -58,7 +49,8 @@
   AddRef();
 
   if (!crx_file::id_util::IdIsValid(id_)) {
-    CompleteInstall(webstore_install::INVALID_ID, kInvalidWebstoreItemId);
+    CompleteInstall(webstore_install::INVALID_ID,
+                    webstore_install::kInvalidWebstoreItemId);
     return;
   }
 
@@ -113,7 +105,7 @@
       tracker->GetActiveInstall(id_);
   if (existing_install_data) {
     *reason = webstore_install::INSTALL_IN_PROGRESS;
-    *error = kInstallInProgressError;
+    *error = webstore_install::kInstallInProgressError;
     return false;
   }
 
@@ -192,7 +184,8 @@
 void WebstoreStandaloneInstaller::OnInstallPromptDone(
     ExtensionInstallPrompt::Result result) {
   if (result == ExtensionInstallPrompt::Result::USER_CANCELED) {
-    CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
+    CompleteInstall(webstore_install::USER_CANCELLED,
+                    webstore_install::kUserCancelledError);
     return;
   }
 
@@ -218,7 +211,7 @@
     if (ExtensionPrefs::Get(profile_)->IsExtensionBlacklisted(id_)) {
       // Don't install a blacklisted extension.
       install_result = webstore_install::BLACKLISTED;
-      install_message = kExtensionIsBlacklisted;
+      install_message = webstore_install::kExtensionIsBlacklisted;
     } else if (!extension_service->IsExtensionEnabled(id_)) {
       // If the extension is installed but disabled, and not blacklisted,
       // enable it.
@@ -240,7 +233,7 @@
 void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
   OnWebStoreDataFetcherDone();
   CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR,
-                  kWebstoreRequestError);
+                  webstore_install::kWebstoreRequestError);
 }
 
 void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
@@ -271,7 +264,7 @@
       !webstore_data->GetDouble(kAverageRatingKey, &average_rating_) ||
       !webstore_data->GetInteger(kRatingCountKey, &rating_count_)) {
     CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
-                    kInvalidWebstoreResponseError);
+                    webstore_install::kInvalidWebstoreResponseError);
     return;
   }
 
@@ -282,7 +275,7 @@
   if (average_rating_ < ExtensionInstallPrompt::kMinExtensionRating ||
       average_rating_ > ExtensionInstallPrompt::kMaxExtensionRating) {
     CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
-                    kInvalidWebstoreResponseError);
+                    webstore_install::kInvalidWebstoreResponseError);
     return;
   }
 
@@ -293,7 +286,7 @@
       !webstore_data->GetString(
           kLocalizedDescriptionKey, &localized_description_))) {
     CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
-                    kInvalidWebstoreResponseError);
+                    webstore_install::kInvalidWebstoreResponseError);
     return;
   }
 
@@ -303,14 +296,14 @@
     std::string icon_url_string;
     if (!webstore_data->GetString(kIconUrlKey, &icon_url_string)) {
       CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
-                      kInvalidWebstoreResponseError);
+                      webstore_install::kInvalidWebstoreResponseError);
       return;
     }
     icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
         icon_url_string);
     if (!icon_url.is_valid()) {
       CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
-                      kInvalidWebstoreResponseError);
+                      webstore_install::kInvalidWebstoreResponseError);
       return;
     }
   }
@@ -403,7 +396,8 @@
   scoped_refptr<const Extension> localized_extension =
       GetLocalizedExtensionForDisplay();
   if (!localized_extension.get()) {
-    CompleteInstall(webstore_install::INVALID_MANIFEST, kInvalidManifestError);
+    CompleteInstall(webstore_install::INVALID_MANIFEST,
+                    webstore_install::kInvalidManifestError);
     return;
   }
 
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 1753b07e..141c77fb 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -535,8 +535,6 @@
       'browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc',
       'browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.h',
       'browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h',
-      'browser/extensions/api/webstore/webstore_api.cc',
-      'browser/extensions/api/webstore/webstore_api.h',
       'browser/extensions/api/webstore_private/webstore_private_api.cc',
       'browser/extensions/api/webstore_private/webstore_private_api.h',
       'browser/extensions/app_data_migrator.cc',
diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi
index 6cc96e7..73391aa 100644
--- a/chrome/chrome_common.gypi
+++ b/chrome/chrome_common.gypi
@@ -192,6 +192,8 @@
       'common/extensions/permissions/chrome_permission_message_rules.h',
       'common/extensions/sync_helper.cc',
       'common/extensions/sync_helper.h',
+      'common/extensions/webstore_install_result.cc',
+      'common/extensions/webstore_install_result.h',
     ],
     'chrome_common_printing_sources': [
       'common/chrome_utility_printing_messages.h',
diff --git a/chrome/common/extensions/webstore_install_result.cc b/chrome/common/extensions/webstore_install_result.cc
new file mode 100644
index 0000000..2519419
--- /dev/null
+++ b/chrome/common/extensions/webstore_install_result.cc
@@ -0,0 +1,20 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/common/extensions/webstore_install_result.h"
+
+namespace extensions {
+namespace webstore_install {
+
+const char kInvalidWebstoreItemId[] = "Invalid Chrome Web Store item ID";
+const char kWebstoreRequestError[] =
+    "Could not fetch data from the Chrome Web Store";
+const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
+const char kInvalidManifestError[] = "Invalid manifest";
+const char kUserCancelledError[] = "User cancelled install";
+const char kExtensionIsBlacklisted[] = "Extension is blacklisted";
+const char kInstallInProgressError[] = "An install is already in progress";
+
+}  // namespace webstore_install
+}  // namespace extensions
diff --git a/chrome/common/extensions/webstore_install_result.h b/chrome/common/extensions/webstore_install_result.h
index 1378dae..74d8184 100644
--- a/chrome/common/extensions/webstore_install_result.h
+++ b/chrome/common/extensions/webstore_install_result.h
@@ -9,6 +9,14 @@
 
 namespace webstore_install {
 
+extern const char kInvalidWebstoreItemId[];
+extern const char kWebstoreRequestError[];
+extern const char kInvalidWebstoreResponseError[];
+extern const char kInvalidManifestError[];
+extern const char kUserCancelledError[];
+extern const char kExtensionIsBlacklisted[];
+extern const char kInstallInProgressError[];
+
 // Result codes returned by WebstoreStandaloneInstaller and its subclasses.
 enum Result {
   // Successful operation.
diff --git a/chrome/test/data/extensions/api_test/webstore_inline_install/double_install.html b/chrome/test/data/extensions/api_test/webstore_inline_install/double_install.html
new file mode 100644
index 0000000..8007f6f
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/webstore_inline_install/double_install.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <link rel="chrome-webstore-item">
+</head>
+<body>
+<script>
+  function runTest(galleryUrl) {
+    // Link URL has to be generated dynamically in order to include the right
+    // port number. The ID corresponds to the data in the "extension" directory.
+    document.getElementsByTagName('link')[0].href =
+        galleryUrl + '/detail/ecglahbcnmdpdciemllbhojghbkagdje';
+
+    try {
+      chrome.webstore.install(
+          undefined,
+          function() {},
+          function() {});
+      chrome.webstore.install(
+          undefined,
+          function() {
+            console.log('Unexpected success');
+            window.domAutomationController.send(false);
+          },
+          function(errorMessage, errorCode) {
+            var success = errorCode == 3;  // Install in progress error.
+            if (!success)
+              console.log('Unexpected error: ' + errorMessage);
+            window.domAutomationController.send(success);
+          });
+    } catch (e) {
+      // Yuck. We can throw this "already installing" error either from the
+      // renderer (if we catch the fact that it's already installing early) or
+      // from the browser. In the first case, it's a thrown error, and in the
+      // second, it triggers the callback from chrome.webstore.install. And to
+      // top it off, they're different messages. Unfortunately, it's hard to say
+      // which, if either, we can change, since someone may be relying on one or
+      // the other.
+      // TODO(devlin): Make this less bad somehow. crbug.com/624614
+      var success =
+          e.message == 'A Chrome Web Store installation is already pending.';
+      if (!success)
+        console.log('Unexpected exception: ' + e);
+      window.domAutomationController.send(success);
+    }
+  }
+</script>
+
+</body>
+</html>