[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>