Commit 773272b5 authored by tmdiep@chromium.org's avatar tmdiep@chromium.org

Prevent duplicate concurrent installs of the same extension

This patch stores the state of active installs in the InstallTracker
for global access. This allows various install initiators to check for
duplicate installs before proceeding.

Install progress bars in the app launcher can be initialized with the
state of an active install.

BUG=393854
TEST=unit_tests, browser_tests
For manual testing steps, please see bug.

Review URL: https://codereview.chromium.org/389613006

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284014 0039d316-1c4b-4281-b951-d872f2087c98
parent 0dbab65e
...@@ -224,17 +224,25 @@ bool EphemeralAppLauncher::CanLaunchInstalledApp( ...@@ -224,17 +224,25 @@ bool EphemeralAppLauncher::CanLaunchInstalledApp(
} }
void EphemeralAppLauncher::EnableInstalledApp(const Extension* extension) { void EphemeralAppLauncher::EnableInstalledApp(const Extension* extension) {
// Check whether an install is already in progress.
webstore_install::Result result = webstore_install::UNKNOWN_ERROR;
std::string error;
if (!EnsureUniqueInstall(&result, &error)) {
InvokeCallback(result, error);
return;
}
// Keep this object alive until the enable flow is complete. Either
// ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be
// called.
AddRef();
extension_enable_flow_.reset( extension_enable_flow_.reset(
new ExtensionEnableFlow(profile(), extension->id(), this)); new ExtensionEnableFlow(profile(), extension->id(), this));
if (web_contents()) if (web_contents())
extension_enable_flow_->StartForWebContents(web_contents()); extension_enable_flow_->StartForWebContents(web_contents());
else else
extension_enable_flow_->StartForNativeWindow(parent_window_); extension_enable_flow_->StartForNativeWindow(parent_window_);
// Keep this object alive until the enable flow is complete. Either
// ExtensionEnableFlowFinished() or ExtensionEnableFlowAborted() will be
// called.
AddRef();
} }
void EphemeralAppLauncher::MaybeLaunchApp() { void EphemeralAppLauncher::MaybeLaunchApp() {
...@@ -292,8 +300,9 @@ bool EphemeralAppLauncher::LaunchHostedApp(const Extension* extension) const { ...@@ -292,8 +300,9 @@ bool EphemeralAppLauncher::LaunchHostedApp(const Extension* extension) const {
void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result, void EphemeralAppLauncher::InvokeCallback(webstore_install::Result result,
const std::string& error) { const std::string& error) {
if (!launch_callback_.is_null()) { if (!launch_callback_.is_null()) {
launch_callback_.Run(result, error); LaunchCallback callback = launch_callback_;
launch_callback_.Reset(); launch_callback_.Reset();
callback.Run(result, error);
} }
} }
...@@ -339,6 +348,11 @@ void EphemeralAppLauncher::OnInstallChecked(int check_failures) { ...@@ -339,6 +348,11 @@ void EphemeralAppLauncher::OnInstallChecked(int check_failures) {
ProceedWithInstallPrompt(); ProceedWithInstallPrompt();
} }
void EphemeralAppLauncher::InitInstallData(
extensions::ActiveInstallData* install_data) const {
install_data->is_ephemeral = true;
}
bool EphemeralAppLauncher::CheckRequestorAlive() const { bool EphemeralAppLauncher::CheckRequestorAlive() const {
return dummy_web_contents_.get() != NULL || web_contents() != NULL; return dummy_web_contents_.get() != NULL || web_contents() != NULL;
} }
...@@ -439,10 +453,13 @@ void EphemeralAppLauncher::WebContentsDestroyed() { ...@@ -439,10 +453,13 @@ void EphemeralAppLauncher::WebContentsDestroyed() {
void EphemeralAppLauncher::ExtensionEnableFlowFinished() { void EphemeralAppLauncher::ExtensionEnableFlowFinished() {
MaybeLaunchApp(); MaybeLaunchApp();
Release(); // Matches the AddRef in EnableInstalledApp().
// CompleteInstall will call Release.
WebstoreStandaloneInstaller::CompleteInstall(webstore_install::SUCCESS,
std::string());
} }
void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) { void EphemeralAppLauncher::ExtensionEnableFlowAborted(bool user_initiated) {
InvokeCallback(webstore_install::USER_CANCELLED, kUserCancelledError); // CompleteInstall will call Release.
Release(); // Matches the AddRef in EnableInstalledApp(). CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
} }
...@@ -116,6 +116,8 @@ class EphemeralAppLauncher : public extensions::WebstoreStandaloneInstaller, ...@@ -116,6 +116,8 @@ class EphemeralAppLauncher : public extensions::WebstoreStandaloneInstaller,
void OnInstallChecked(int check_failures); void OnInstallChecked(int check_failures);
// WebstoreStandaloneInstaller implementation. // WebstoreStandaloneInstaller implementation.
virtual void InitInstallData(
extensions::ActiveInstallData* install_data) const OVERRIDE;
virtual bool CheckRequestorAlive() const OVERRIDE; virtual bool CheckRequestorAlive() const OVERRIDE;
virtual const GURL& GetRequestorURL() const OVERRIDE; virtual const GURL& GetRequestorURL() const OVERRIDE;
virtual bool ShouldShowPostInstallUI() const OVERRIDE; virtual bool ShouldShowPostInstallUI() const OVERRIDE;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "chrome/browser/extensions/extension_install_checker.h" #include "chrome/browser/extensions/extension_install_checker.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_test_message_listener.h" #include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/browser/extensions/install_tracker.h"
#include "chrome/browser/extensions/test_blacklist.h" #include "chrome/browser/extensions/test_blacklist.h"
#include "chrome/browser/extensions/webstore_installer_test.h" #include "chrome/browser/extensions/webstore_installer_test.h"
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
...@@ -24,6 +25,7 @@ using extensions::Extension; ...@@ -24,6 +25,7 @@ using extensions::Extension;
using extensions::ExtensionPrefs; using extensions::ExtensionPrefs;
using extensions::ExtensionRegistry; using extensions::ExtensionRegistry;
using extensions::ExtensionSystem; using extensions::ExtensionSystem;
using extensions::InstallTracker;
namespace webstore_install = extensions::webstore_install; namespace webstore_install = extensions::webstore_install;
namespace { namespace {
...@@ -242,10 +244,17 @@ class EphemeralAppLauncherTest : public WebstoreInstallerTest { ...@@ -242,10 +244,17 @@ class EphemeralAppLauncherTest : public WebstoreInstallerTest {
void RunLaunchTest(const std::string& id, void RunLaunchTest(const std::string& id,
webstore_install::Result expected_result, webstore_install::Result expected_result,
bool expect_install_initiated) { bool expect_install_initiated) {
InstallTracker* tracker = InstallTracker::Get(profile());
ASSERT_TRUE(tracker);
bool was_install_active = !!tracker->GetActiveInstall(id);
scoped_refptr<EphemeralAppLauncherForTest> launcher( scoped_refptr<EphemeralAppLauncherForTest> launcher(
new EphemeralAppLauncherForTest(id, profile())); new EphemeralAppLauncherForTest(id, profile()));
StartLauncherAndCheckResult( StartLauncherAndCheckResult(
launcher.get(), expected_result, expect_install_initiated); launcher.get(), expected_result, expect_install_initiated);
// Verify that the install was deregistered from the InstallTracker.
EXPECT_EQ(was_install_active, !!tracker->GetActiveInstall(id));
} }
void ValidateAppInstalledEphemerally(const std::string& id) { void ValidateAppInstalledEphemerally(const std::string& id) {
...@@ -519,3 +528,20 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, ...@@ -519,3 +528,20 @@ IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest,
RunLaunchTest(app->id(), webstore_install::REQUIREMENT_VIOLATIONS, false); RunLaunchTest(app->id(), webstore_install::REQUIREMENT_VIOLATIONS, false);
} }
// Verifies that a launch will fail if the app is currently being installed.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, InstallInProgress) {
extensions::ActiveInstallData install_data(kDefaultAppId);
InstallTracker::Get(profile())->AddActiveInstall(install_data);
RunLaunchTest(kDefaultAppId, webstore_install::INSTALL_IN_PROGRESS, false);
}
// Verifies that a launch will fail if a duplicate launch is in progress.
IN_PROC_BROWSER_TEST_F(EphemeralAppLauncherTest, DuplicateLaunchInProgress) {
extensions::ActiveInstallData install_data(kDefaultAppId);
install_data.is_ephemeral = true;
InstallTracker::Get(profile())->AddActiveInstall(install_data);
RunLaunchTest(kDefaultAppId, webstore_install::LAUNCH_IN_PROGRESS, false);
}
// 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/active_install_data.h"
#include "chrome/browser/extensions/install_tracker.h"
namespace extensions {
// ActiveInstallData:
ActiveInstallData::ActiveInstallData()
: percent_downloaded(0), is_ephemeral(false) {
}
ActiveInstallData::ActiveInstallData(const std::string& extension_id)
: extension_id(extension_id), percent_downloaded(0), is_ephemeral(false) {
}
// ScopedActiveInstall:
ScopedActiveInstall::ScopedActiveInstall(InstallTracker* tracker,
const ActiveInstallData& install_data)
: tracker_(tracker),
tracker_observer_(this),
extension_id_(install_data.extension_id) {
Init();
tracker_->AddActiveInstall(install_data);
}
ScopedActiveInstall::ScopedActiveInstall(InstallTracker* tracker,
const std::string& extension_id)
: tracker_(tracker), tracker_observer_(this), extension_id_(extension_id) {
Init();
}
ScopedActiveInstall::~ScopedActiveInstall() {
if (tracker_)
tracker_->RemoveActiveInstall(extension_id_);
}
void ScopedActiveInstall::CancelDeregister() {
tracker_observer_.RemoveAll();
tracker_ = NULL;
}
void ScopedActiveInstall::Init() {
DCHECK(!extension_id_.empty());
DCHECK(tracker_);
tracker_observer_.Add(tracker_);
}
void ScopedActiveInstall::OnShutdown() {
CancelDeregister();
}
} // namespace extensions
// 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_ACTIVE_INSTALL_DATA_H_
#define CHROME_BROWSER_EXTENSIONS_ACTIVE_INSTALL_DATA_H_
#include <string>
#include "base/macros.h"
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/install_observer.h"
class Profile;
namespace extensions {
class InstallTracker;
// Details of an active extension install.
struct ActiveInstallData {
public:
ActiveInstallData();
explicit ActiveInstallData(const std::string& extension_id);
std::string extension_id;
int percent_downloaded;
bool is_ephemeral;
};
// Registers and deregisters and an active extension install with the
// InstallTracker.
class ScopedActiveInstall : public InstallObserver {
public:
// This constructor registers an active install with the InstallTracker.
ScopedActiveInstall(InstallTracker* tracker,
const ActiveInstallData& install_data);
// This constructor does not register an active install. The extension install
// is still deregistered upon destruction.
ScopedActiveInstall(InstallTracker* tracker, const std::string& extension_id);
virtual ~ScopedActiveInstall();
// Ensures that the active install is not deregistered upon destruction. This
// may be necessary if the extension install outlives the lifetime of this
// instance.
void CancelDeregister();
private:
void Init();
// InstallObserver implementation.
virtual void OnShutdown() OVERRIDE;
InstallTracker* tracker_;
ScopedObserver<InstallTracker, InstallObserver> tracker_observer_;
std::string extension_id_;
DISALLOW_COPY_AND_ASSIGN(ScopedActiveInstall);
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_ACTIVE_INSTALL_DATA_H_
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/crx_installer.h" #include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/install_tracker.h"
#include "chrome/browser/extensions/webstore_installer.h" #include "chrome/browser/extensions/webstore_installer.h"
#include "chrome/browser/gpu/gpu_feature_checker.h" #include "chrome/browser/gpu/gpu_feature_checker.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
...@@ -116,65 +117,8 @@ scoped_ptr<WebstoreInstaller::Approval> PendingApprovals::PopApproval( ...@@ -116,65 +117,8 @@ scoped_ptr<WebstoreInstaller::Approval> PendingApprovals::PopApproval(
return scoped_ptr<WebstoreInstaller::Approval>(); return scoped_ptr<WebstoreInstaller::Approval>();
} }
// Uniquely holds the profile and extension id of an install between the time we
// prompt and complete the installs.
class PendingInstalls {
public:
PendingInstalls();
~PendingInstalls();
bool InsertInstall(Profile* profile, const std::string& id);
void EraseInstall(Profile* profile, const std::string& id);
bool ContainsInstall(Profile* profile, const std::string& id);
private:
typedef std::pair<Profile*, std::string> ProfileAndExtensionId;
typedef std::vector<ProfileAndExtensionId> InstallList;
InstallList::iterator FindInstall(Profile* profile, const std::string& id);
InstallList installs_;
DISALLOW_COPY_AND_ASSIGN(PendingInstalls);
};
PendingInstalls::PendingInstalls() {}
PendingInstalls::~PendingInstalls() {}
// Returns true and inserts the profile/id pair if it is not present. Otherwise
// returns false.
bool PendingInstalls::InsertInstall(Profile* profile, const std::string& id) {
if (FindInstall(profile, id) != installs_.end())
return false;
installs_.push_back(make_pair(profile, id));
return true;
}
// Removes the given profile/id pair.
void PendingInstalls::EraseInstall(Profile* profile, const std::string& id) {
InstallList::iterator it = FindInstall(profile, id);
if (it != installs_.end())
installs_.erase(it);
}
bool PendingInstalls::ContainsInstall(Profile* profile, const std::string& id) {
return FindInstall(profile, id) != installs_.end();
}
PendingInstalls::InstallList::iterator PendingInstalls::FindInstall(
Profile* profile,
const std::string& id) {
for (size_t i = 0; i < installs_.size(); ++i) {
ProfileAndExtensionId install = installs_[i];
if (install.second == id && profile->IsSameProfile(install.first))
return (installs_.begin() + i);
}
return installs_.end();
}
static base::LazyInstance<PendingApprovals> g_pending_approvals = static base::LazyInstance<PendingApprovals> g_pending_approvals =
LAZY_INSTANCE_INITIALIZER; LAZY_INSTANCE_INITIALIZER;
static base::LazyInstance<PendingInstalls> g_pending_installs =
LAZY_INSTANCE_INITIALIZER;
// A preference set by the web store to indicate login information for // A preference set by the web store to indicate login information for
// purchased apps. // purchased apps.
...@@ -326,12 +270,16 @@ bool WebstorePrivateBeginInstallWithManifest3Function::RunAsync() { ...@@ -326,12 +270,16 @@ bool WebstorePrivateBeginInstallWithManifest3Function::RunAsync() {
*params_->details.icon_data : std::string(); *params_->details.icon_data : std::string();
Profile* profile = GetProfile(); Profile* profile = GetProfile();
InstallTracker* tracker = InstallTracker::Get(profile);
DCHECK(tracker);
if (util::IsExtensionInstalledPermanently(params_->details.id, profile) || if (util::IsExtensionInstalledPermanently(params_->details.id, profile) ||
!g_pending_installs.Get().InsertInstall(profile, params_->details.id)) { tracker->GetActiveInstall(params_->details.id)) {
SetResultCode(ALREADY_INSTALLED); SetResultCode(ALREADY_INSTALLED);
error_ = kAlreadyInstalledError; error_ = kAlreadyInstalledError;
return false; return false;
} }
ActiveInstallData install_data(params_->details.id);
scoped_active_install_.reset(new ScopedActiveInstall(tracker, install_data));
net::URLRequestContextGetter* context_getter = NULL; net::URLRequestContextGetter* context_getter = NULL;
if (!icon_url.is_empty()) if (!icon_url.is_empty())
...@@ -450,7 +398,6 @@ void WebstorePrivateBeginInstallWithManifest3Function::OnWebstoreParseFailure( ...@@ -450,7 +398,6 @@ void WebstorePrivateBeginInstallWithManifest3Function::OnWebstoreParseFailure(
CHECK(false); CHECK(false);
} }
error_ = error_message; error_ = error_message;
g_pending_installs.Get().EraseInstall(GetProfile(), id);
SendResponse(false); SendResponse(false);
// Matches the AddRef in RunAsync(). // Matches the AddRef in RunAsync().
...@@ -463,7 +410,6 @@ void WebstorePrivateBeginInstallWithManifest3Function::SigninFailed( ...@@ -463,7 +410,6 @@ void WebstorePrivateBeginInstallWithManifest3Function::SigninFailed(
SetResultCode(SIGNIN_FAILED); SetResultCode(SIGNIN_FAILED);
error_ = error.ToString(); error_ = error.ToString();
g_pending_installs.Get().EraseInstall(GetProfile(), params_->details.id);
SendResponse(false); SendResponse(false);
// Matches the AddRef in RunAsync(). // Matches the AddRef in RunAsync().
...@@ -514,6 +460,9 @@ void WebstorePrivateBeginInstallWithManifest3Function::InstallUIProceed() { ...@@ -514,6 +460,9 @@ void WebstorePrivateBeginInstallWithManifest3Function::InstallUIProceed() {
approval->authuser = authuser_; approval->authuser = authuser_;
g_pending_approvals.Get().PushApproval(approval.Pass()); g_pending_approvals.Get().PushApproval(approval.Pass());
DCHECK(scoped_active_install_.get());
scoped_active_install_->CancelDeregister();
SetResultCode(ERROR_NONE); SetResultCode(ERROR_NONE);
SendResponse(true); SendResponse(true);
...@@ -531,7 +480,6 @@ void WebstorePrivateBeginInstallWithManifest3Function::InstallUIAbort( ...@@ -531,7 +480,6 @@ void WebstorePrivateBeginInstallWithManifest3Function::InstallUIAbort(
bool user_initiated) { bool user_initiated) {
error_ = kUserCancelledError; error_ = kUserCancelledError;
SetResultCode(USER_CANCELLED); SetResultCode(USER_CANCELLED);
g_pending_installs.Get().EraseInstall(GetProfile(), params_->details.id);
SendResponse(false); SendResponse(false);
// The web store install histograms are a subset of the install histograms. // The web store install histograms are a subset of the install histograms.
...@@ -576,6 +524,9 @@ bool WebstorePrivateCompleteInstallFunction::RunAsync() { ...@@ -576,6 +524,9 @@ bool WebstorePrivateCompleteInstallFunction::RunAsync() {
return false; return false;
} }
scoped_active_install_.reset(new ScopedActiveInstall(
InstallTracker::Get(GetProfile()), params->expected_id));
AppListService* app_list_service = AppListService* app_list_service =
AppListService::Get(GetCurrentBrowser()->host_desktop_type()); AppListService::Get(GetCurrentBrowser()->host_desktop_type());
...@@ -646,7 +597,6 @@ void WebstorePrivateCompleteInstallFunction::OnExtensionInstallFailure( ...@@ -646,7 +597,6 @@ void WebstorePrivateCompleteInstallFunction::OnExtensionInstallFailure(
error_ = error; error_ = error;
VLOG(1) << "Install failed, sending response"; VLOG(1) << "Install failed, sending response";
g_pending_installs.Get().EraseInstall(GetProfile(), id);
SendResponse(false); SendResponse(false);
RecordWebstoreExtensionInstallResult(false); RecordWebstoreExtensionInstallResult(false);
...@@ -661,7 +611,6 @@ void WebstorePrivateCompleteInstallFunction::OnInstallSuccess( ...@@ -661,7 +611,6 @@ void WebstorePrivateCompleteInstallFunction::OnInstallSuccess(
test_webstore_installer_delegate->OnExtensionInstallSuccess(id); test_webstore_installer_delegate->OnExtensionInstallSuccess(id);
VLOG(1) << "Install success, sending response"; VLOG(1) << "Install success, sending response";
g_pending_installs.Get().EraseInstall(GetProfile(), id);
SendResponse(true); SendResponse(true);
} }
...@@ -856,13 +805,6 @@ bool WebstorePrivateLaunchEphemeralAppFunction::RunAsync() { ...@@ -856,13 +805,6 @@ bool WebstorePrivateLaunchEphemeralAppFunction::RunAsync() {
LaunchEphemeralApp::Params::Create(*args_)); LaunchEphemeralApp::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params); EXTENSION_FUNCTION_VALIDATE(params);
// If a full install is in progress, do not install ephemerally.
if (g_pending_installs.Get().ContainsInstall(GetProfile(), params->id)) {
SetResult(LaunchEphemeralAppResult::RESULT_INSTALL_IN_PROGRESS,
"An install is already in progress");
return false;
}
AddRef(); // Balanced in OnLaunchComplete() AddRef(); // Balanced in OnLaunchComplete()
scoped_refptr<EphemeralAppLauncher> launcher = scoped_refptr<EphemeralAppLauncher> launcher =
...@@ -919,6 +861,12 @@ void WebstorePrivateLaunchEphemeralAppFunction::OnLaunchComplete( ...@@ -919,6 +861,12 @@ void WebstorePrivateLaunchEphemeralAppFunction::OnLaunchComplete(
case webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE: case webstore_install::LAUNCH_UNSUPPORTED_EXTENSION_TYPE:
api_result = LaunchEphemeralAppResult::RESULT_UNSUPPORTED_EXTENSION_TYPE; api_result = LaunchEphemeralAppResult::RESULT_UNSUPPORTED_EXTENSION_TYPE;
break; break;
case webstore_install::INSTALL_IN_PROGRESS:
api_result = LaunchEphemeralAppResult::RESULT_INSTALL_IN_PROGRESS;
break;
case webstore_install::LAUNCH_IN_PROGRESS:
api_result = LaunchEphemeralAppResult::RESULT_LAUNCH_IN_PROGRESS;
break;
default: default:
NOTREACHED(); NOTREACHED();
break; break;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <string> #include <string>
#include "chrome/browser/extensions/active_install_data.h"
#include "chrome/browser/extensions/bundle_installer.h" #include "chrome/browser/extensions/bundle_installer.h"
#include "chrome/browser/extensions/chrome_extension_function.h" #include "chrome/browser/extensions/chrome_extension_function.h"
#include "chrome/browser/extensions/extension_install_prompt.h" #include "chrome/browser/extensions/extension_install_prompt.h"
...@@ -171,6 +172,8 @@ class WebstorePrivateBeginInstallWithManifest3Function ...@@ -171,6 +172,8 @@ class WebstorePrivateBeginInstallWithManifest3Function
scoped_ptr<SigninTracker> signin_tracker_; scoped_ptr<SigninTracker> signin_tracker_;
scoped_ptr<ScopedActiveInstall> scoped_active_install_;
// The authuser query parameter value which should be used with CRX download // The authuser query parameter value which should be used with CRX download
// requests. This is empty if authuser should not be set on download requests. // requests. This is empty if authuser should not be set on download requests.
std::string authuser_; std::string authuser_;
...@@ -200,6 +203,7 @@ class WebstorePrivateCompleteInstallFunction ...@@ -200,6 +203,7 @@ class WebstorePrivateCompleteInstallFunction
private: private:
scoped_ptr<WebstoreInstaller::Approval> approval_; scoped_ptr<WebstoreInstaller::Approval> approval_;
scoped_ptr<ScopedActiveInstall> scoped_active_install_;
void OnInstallSuccess(const std::string& id); void OnInstallSuccess(const std::string& id);
}; };
......
...@@ -11,25 +11,32 @@ ...@@ -11,25 +11,32 @@
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "content/public/browser/notification_service.h" #include "content/public/browser/notification_service.h"
#include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/pref_names.h" #include "extensions/browser/pref_names.h"
namespace extensions { namespace extensions {
InstallTracker::InstallTracker(Profile* profile, InstallTracker::InstallTracker(Profile* profile,
extensions::ExtensionPrefs* prefs) { extensions::ExtensionPrefs* prefs)
: extension_registry_observer_(this) {
registrar_.Add(this, registrar_.Add(this,
chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED,
content::Source<Profile>(profile)); content::Source<Profile>(profile));
AppSorting* sorting = prefs->app_sorting();
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
content::Source<AppSorting>(sorting));
registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST, registrar_.Add(this, chrome::NOTIFICATION_APP_INSTALLED_TO_APPLIST,
content::Source<Profile>(profile)); content::Source<Profile>(profile));
extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
pref_change_registrar_.Init(prefs->pref_service());
pref_change_registrar_.Add(pref_names::kExtensions, // Prefs may be null in tests.
base::Bind(&InstallTracker::OnAppsReordered, if (prefs) {
base::Unretained(this))); AppSorting* sorting = prefs->app_sorting();
registrar_.Add(this,
chrome::NOTIFICATION_EXTENSION_LAUNCHER_REORDERED,
content::Source<AppSorting>(sorting));
pref_change_registrar_.Init(prefs->pref_service());
pref_change_registrar_.Add(
pref_names::kExtensions,
base::Bind(&InstallTracker::OnAppsReordered, base::Unretained(this)));
}
} }
InstallTracker::~InstallTracker() { InstallTracker::~InstallTracker() {
...@@ -49,8 +56,38 @@ void InstallTracker::RemoveObserver(InstallObserver* observer) { ...@@ -49,8 +56,38 @@ void InstallTracker::RemoveObserver(InstallObserver* observer) {
observers_.RemoveObserver(observer); observers_.RemoveObserver(observer);
} }
const ActiveInstallData* InstallTracker::GetActiveInstall(
const std::string& extension_id) const {
ActiveInstallsMap::const_iterator install_data =
active_installs_.find(extension_id);
if (install_data == active_installs_.end())
return NULL;
else
return &(install_data->second);
}
void InstallTracker::AddActiveInstall(const ActiveInstallData& install_data) {
DCHECK(!install_data.extension_id.empty());
DCHECK(active_installs_.find(install_data.extension_id) ==
active_installs_.end());
active_installs_.insert(
std::make_pair(install_data.extension_id, install_data));
}
void InstallTracker::RemoveActiveInstall(const std::string& extension_id) {
active_installs_.erase(extension_id);
}
void InstallTracker::OnBeginExtensionInstall( void InstallTracker::OnBeginExtensionInstall(
const InstallObserver::ExtensionInstallParams& params) { const InstallObserver::ExtensionInstallParams& params) {
ActiveInstallsMap::iterator install_data =
active_installs_.find(params.extension_id);
if (install_data == active_installs_.end()) {
ActiveInstallData install_data(params.extension_id);
install_data.is_ephemeral = params.is_ephemeral;
active_installs_.insert(std::make_pair(params.extension_id, install_data));
}
FOR_EACH_OBSERVER(InstallObserver, observers_, FOR_EACH_OBSERVER(InstallObserver, observers_,
OnBeginExtensionInstall(params)); OnBeginExtensionInstall(params));
} }
...@@ -62,6 +99,14 @@ void InstallTracker::OnBeginExtensionDownload(const std::string& extension_id) { ...@@ -62,6 +99,14 @@ void InstallTracker::OnBeginExtensionDownload(const std::string& extension_id) {
void InstallTracker::OnDownloadProgress(const std::string& extension_id, void InstallTracker::OnDownloadProgress(const std::string& extension_id,
int percent_downloaded) { int percent_downloaded) {
ActiveInstallsMap::iterator install_data =
active_installs_.find(extension_id);
if (install_data != active_installs_.end()) {
install_data->second.percent_downloaded = percent_downloaded;
} else {
NOTREACHED();
}
FOR_EACH_OBSERVER(InstallObserver, observers_, FOR_EACH_OBSERVER(InstallObserver, observers_,
OnDownloadProgress(extension_id, percent_downloaded)); OnDownloadProgress(extension_id, percent_downloaded));
} }
...@@ -79,6 +124,7 @@ void InstallTracker::OnFinishCrxInstall(const std::string& extension_id, ...@@ -79,6 +124,7 @@ void InstallTracker::OnFinishCrxInstall(const std::string& extension_id,
void InstallTracker::OnInstallFailure( void InstallTracker::OnInstallFailure(
const std::string& extension_id) { const std::string& extension_id) {
RemoveActiveInstall(extension_id);
FOR_EACH_OBSERVER(InstallObserver, observers_, FOR_EACH_OBSERVER(InstallObserver, observers_,
OnInstallFailure(extension_id)); OnInstallFailure(extension_id));
} }
...@@ -114,6 +160,13 @@ void InstallTracker::Observe(int type, ...@@ -114,6 +160,13 @@ void InstallTracker::Observe(int type,
} }
} }
void InstallTracker::OnExtensionInstalled(
content::BrowserContext* browser_context,
const Extension* extension,
bool is_update) {
RemoveActiveInstall(extension->id());
}
void InstallTracker::OnAppsReordered() { void InstallTracker::OnAppsReordered() {
FOR_EACH_OBSERVER(InstallObserver, observers_, OnAppsReordered()); FOR_EACH_OBSERVER(InstallObserver, observers_, OnAppsReordered());
} }
......
...@@ -5,12 +5,17 @@ ...@@ -5,12 +5,17 @@
#ifndef CHROME_BROWSER_EXTENSIONS_INSTALL_TRACKER_H_ #ifndef CHROME_BROWSER_EXTENSIONS_INSTALL_TRACKER_H_
#define CHROME_BROWSER_EXTENSIONS_INSTALL_TRACKER_H_ #define CHROME_BROWSER_EXTENSIONS_INSTALL_TRACKER_H_
#include <map>
#include "base/observer_list.h" #include "base/observer_list.h"
#include "base/prefs/pref_change_registrar.h" #include "base/prefs/pref_change_registrar.h"
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/active_install_data.h"
#include "chrome/browser/extensions/install_observer.h" #include "chrome/browser/extensions/install_observer.h"
#include "components/keyed_service/core/keyed_service.h" #include "components/keyed_service/core/keyed_service.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
#include "extensions/browser/extension_registry_observer.h"
class Profile; class Profile;
...@@ -21,9 +26,11 @@ class BrowserContext; ...@@ -21,9 +26,11 @@ class BrowserContext;
namespace extensions { namespace extensions {
class ExtensionPrefs; class ExtensionPrefs;
class ExtensionRegistry;
class InstallTracker : public KeyedService, class InstallTracker : public KeyedService,
public content::NotificationObserver { public content::NotificationObserver,
public ExtensionRegistryObserver {
public: public:
InstallTracker(Profile* profile, InstallTracker(Profile* profile,
extensions::ExtensionPrefs* prefs); extensions::ExtensionPrefs* prefs);
...@@ -34,6 +41,22 @@ class InstallTracker : public KeyedService, ...@@ -34,6 +41,22 @@ class InstallTracker : public KeyedService,
void AddObserver(InstallObserver* observer); void AddObserver(InstallObserver* observer);
void RemoveObserver(InstallObserver* observer); void RemoveObserver(InstallObserver* observer);
// If an install is currently in progress for |extension_id|, returns details
// of the installation. This instance retains ownership of the returned
// pointer. Returns NULL if the extension is not currently being installed.
const ActiveInstallData* GetActiveInstall(
const std::string& extension_id) const;
// Registers an install initiated by the user to allow checking of duplicate
// installs. Download of the extension has not necessarily started.
// RemoveActiveInstall() must be called when install is complete regardless of
// success or failure. Consider using ScopedActiveInstall rather than calling
// this directly.
void AddActiveInstall(const ActiveInstallData& install_data);
// Deregisters an active install.
void RemoveActiveInstall(const std::string& extension_id);
void OnBeginExtensionInstall( void OnBeginExtensionInstall(
const InstallObserver::ExtensionInstallParams& params); const InstallObserver::ExtensionInstallParams& params);
void OnBeginExtensionDownload(const std::string& extension_id); void OnBeginExtensionDownload(const std::string& extension_id);
...@@ -57,9 +80,20 @@ class InstallTracker : public KeyedService, ...@@ -57,9 +80,20 @@ class InstallTracker : public KeyedService,
const content::NotificationSource& source, const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE; const content::NotificationDetails& details) OVERRIDE;
// ExtensionRegistryObserver implementation.
virtual void OnExtensionInstalled(content::BrowserContext* browser_context,
const Extension* extension,
bool is_update) OVERRIDE;
// Maps extension id to the details of an active install.
typedef std::map<std::string, ActiveInstallData> ActiveInstallsMap;
ActiveInstallsMap active_installs_;
ObserverList<InstallObserver> observers_; ObserverList<InstallObserver> observers_;
content::NotificationRegistrar registrar_; content::NotificationRegistrar registrar_;
PrefChangeRegistrar pref_change_registrar_; PrefChangeRegistrar pref_change_registrar_;
ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
extension_registry_observer_;
DISALLOW_COPY_AND_ASSIGN(InstallTracker); DISALLOW_COPY_AND_ASSIGN(InstallTracker);
}; };
......
// 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 "base/files/file_path.h"
#include "chrome/browser/extensions/active_install_data.h"
#include "chrome/browser/extensions/install_tracker.h"
#include "chrome/test/base/testing_profile.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::ActiveInstallData;
using extensions::Extension;
using extensions::ExtensionRegistry;
using extensions::InstallTracker;
using extensions::InstallObserver;
using extensions::ScopedActiveInstall;
namespace {
// Random extension ids for testing.
const char kExtensionId1[] = "oochhailbdickimldhhodijaldpllppf";
const char kExtensionId2[] = "ahionppacfhbbmpmlcbkdgcpokfpflji";
const char kExtensionId3[] = "ladmcjmmmmgonboiadnaindoekpbljde";
scoped_refptr<Extension> CreateDummyExtension(const std::string& id) {
base::DictionaryValue manifest;
manifest.SetString(extensions::manifest_keys::kVersion, "1.0");
manifest.SetString(extensions::manifest_keys::kName, "Dummy name");
std::string error;
scoped_refptr<Extension> extension;
extension = Extension::Create(base::FilePath(),
extensions::Manifest::INTERNAL,
manifest,
Extension::NO_FLAGS,
id,
&error);
EXPECT_TRUE(extension.get()) << "Error creating extension: " << error;
return extension;
}
} // namespace
class InstallTrackerTest : public testing::Test {
public:
InstallTrackerTest() {
profile_.reset(new TestingProfile());
tracker_.reset(new InstallTracker(profile_.get(), NULL));
}
virtual ~InstallTrackerTest() {}
protected:
Profile* profile() { return profile_.get(); }
InstallTracker* tracker() { return tracker_.get(); }
void VerifyInstallData(const ActiveInstallData& original,
const ActiveInstallData& retrieved) {
EXPECT_EQ(original.extension_id, retrieved.extension_id);
EXPECT_EQ(original.is_ephemeral, retrieved.is_ephemeral);
EXPECT_EQ(original.percent_downloaded, retrieved.percent_downloaded);
}
scoped_ptr<TestingProfile> profile_;
scoped_ptr<InstallTracker> tracker_;
};
// Verifies that active installs are registered and deregistered correctly.
TEST_F(InstallTrackerTest, AddAndRemoveActiveInstalls) {
ActiveInstallData install_data1(kExtensionId1);
install_data1.percent_downloaded = 76;
ActiveInstallData install_data2(kExtensionId2);
install_data2.is_ephemeral = true;
tracker_->AddActiveInstall(install_data1);
tracker_->AddActiveInstall(install_data2);
const ActiveInstallData* retrieved_data1 =
tracker_->GetActiveInstall(kExtensionId1);
const ActiveInstallData* retrieved_data2 =
tracker_->GetActiveInstall(kExtensionId2);
const ActiveInstallData* retrieved_data3 =
tracker_->GetActiveInstall(kExtensionId3);
ASSERT_TRUE(retrieved_data1);
ASSERT_TRUE(retrieved_data2);
ASSERT_FALSE(retrieved_data3);
VerifyInstallData(install_data1, *retrieved_data1);
VerifyInstallData(install_data2, *retrieved_data2);
retrieved_data1 = NULL;
retrieved_data2 = NULL;
tracker_->RemoveActiveInstall(kExtensionId1);
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId1));
EXPECT_TRUE(tracker_->GetActiveInstall(kExtensionId2));
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId3));
}
// Verifies that active installs are registered and deregistered correctly
// using ScopedActiveInstall.
TEST_F(InstallTrackerTest, ScopedActiveInstallDeregister) {
// Verify the constructor that registers the install.
ActiveInstallData install_data(kExtensionId1);
install_data.percent_downloaded = 6;
scoped_ptr<ScopedActiveInstall> scoped_active_install(
new ScopedActiveInstall(tracker(), install_data));
const ActiveInstallData* retrieved_data =
tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
VerifyInstallData(install_data, *retrieved_data);
retrieved_data = NULL;
scoped_active_install.reset();
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId1));
// Verify the constructor that doesn't register the install.
scoped_active_install.reset(
new ScopedActiveInstall(tracker(), kExtensionId1));
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId1));
tracker_->AddActiveInstall(install_data);
EXPECT_TRUE(tracker_->GetActiveInstall(kExtensionId1));
scoped_active_install.reset();
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId1));
}
// Verifies that ScopedActiveInstall can be cancelled.
TEST_F(InstallTrackerTest, ScopedActiveInstallCancelled) {
ActiveInstallData install_data(kExtensionId1);
install_data.percent_downloaded = 87;
scoped_ptr<ScopedActiveInstall> scoped_active_install(
new ScopedActiveInstall(tracker(), install_data));
const ActiveInstallData* retrieved_data =
tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
VerifyInstallData(install_data, *retrieved_data);
retrieved_data = NULL;
scoped_active_install->CancelDeregister();
scoped_active_install.reset();
retrieved_data = tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
VerifyInstallData(install_data, *retrieved_data);
}
// Verifies that the download progress is updated correctly.
TEST_F(InstallTrackerTest, DownloadProgressUpdated) {
ActiveInstallData install_data(kExtensionId1);
tracker_->AddActiveInstall(install_data);
const ActiveInstallData* retrieved_data =
tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
EXPECT_EQ(0, retrieved_data->percent_downloaded);
const int kUpdatedDownloadProgress = 23;
tracker_->OnDownloadProgress(kExtensionId1, kUpdatedDownloadProgress);
retrieved_data = tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
EXPECT_EQ(kUpdatedDownloadProgress, retrieved_data->percent_downloaded);
}
// Verifies that OnBeginExtensionInstall() registers an active install and
// OnInstallFailure() removes an active install.
TEST_F(InstallTrackerTest, ExtensionInstallFailure) {
InstallObserver::ExtensionInstallParams install_params(
kExtensionId1, std::string(), gfx::ImageSkia(), false, false);
install_params.is_ephemeral = true;
tracker_->OnBeginExtensionInstall(install_params);
const ActiveInstallData* retrieved_data =
tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
EXPECT_EQ(0, retrieved_data->percent_downloaded);
EXPECT_EQ(install_params.extension_id, retrieved_data->extension_id);
EXPECT_EQ(install_params.is_ephemeral, retrieved_data->is_ephemeral);
retrieved_data = NULL;
tracker_->OnInstallFailure(kExtensionId1);
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId1));
}
// Verifies that OnExtensionInstalled() notification removes an active install.
TEST_F(InstallTrackerTest, ExtensionInstalledEvent) {
InstallObserver::ExtensionInstallParams install_params(
kExtensionId1, std::string(), gfx::ImageSkia(), false, false);
tracker_->OnBeginExtensionInstall(install_params);
const ActiveInstallData* retrieved_data =
tracker_->GetActiveInstall(kExtensionId1);
ASSERT_TRUE(retrieved_data);
EXPECT_EQ(0, retrieved_data->percent_downloaded);
EXPECT_EQ(install_params.extension_id, retrieved_data->extension_id);
EXPECT_EQ(install_params.is_ephemeral, retrieved_data->is_ephemeral);
retrieved_data = NULL;
// Simulate an extension install.
scoped_refptr<Extension> extension = CreateDummyExtension(kExtensionId1);
ASSERT_TRUE(extension.get());
ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
ASSERT_TRUE(registry);
registry->AddEnabled(extension);
registry->TriggerOnInstalled(extension.get(), false);
EXPECT_FALSE(tracker_->GetActiveInstall(kExtensionId1));
}
...@@ -20,6 +20,9 @@ enum Result { ...@@ -20,6 +20,9 @@ enum Result {
// The operation was aborted as the requestor is no longer alive. // The operation was aborted as the requestor is no longer alive.
ABORTED, ABORTED,
// An installation of the same extension is in progress.
INSTALL_IN_PROGRESS,
// The installation is not permitted. // The installation is not permitted.
NOT_PERMITTED, NOT_PERMITTED,
...@@ -59,7 +62,10 @@ enum Result { ...@@ -59,7 +62,10 @@ enum Result {
LAUNCH_FEATURE_DISABLED, LAUNCH_FEATURE_DISABLED,
// The launch feature is not supported for the extension type. // The launch feature is not supported for the extension type.
LAUNCH_UNSUPPORTED_EXTENSION_TYPE LAUNCH_UNSUPPORTED_EXTENSION_TYPE,
// A launch of the same extension is in progress.
LAUNCH_IN_PROGRESS
}; };
} // namespace webstore_install } // namespace webstore_install
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "chrome/browser/extensions/extension_install_prompt.h" #include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_install_ui.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/install_tracker.h"
#include "chrome/browser/extensions/webstore_data_fetcher.h" #include "chrome/browser/extensions/webstore_data_fetcher.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
...@@ -31,6 +32,8 @@ const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse"; ...@@ -31,6 +32,8 @@ const char kInvalidWebstoreResponseError[] = "Invalid Chrome Web Store reponse";
const char kInvalidManifestError[] = "Invalid manifest"; const char kInvalidManifestError[] = "Invalid manifest";
const char kUserCancelledError[] = "User cancelled install"; const char kUserCancelledError[] = "User cancelled install";
const char kExtensionIsBlacklisted[] = "Extension is blacklisted"; const char kExtensionIsBlacklisted[] = "Extension is blacklisted";
const char kInstallInProgressError[] = "An install is already in progress";
const char kLaunchInProgressError[] = "A launch is already in progress";
WebstoreStandaloneInstaller::WebstoreStandaloneInstaller( WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
const std::string& webstore_item_id, const std::string& webstore_item_id,
...@@ -56,6 +59,13 @@ void WebstoreStandaloneInstaller::BeginInstall() { ...@@ -56,6 +59,13 @@ void WebstoreStandaloneInstaller::BeginInstall() {
return; return;
} }
webstore_install::Result result = webstore_install::UNKNOWN_ERROR;
std::string error;
if (!EnsureUniqueInstall(&result, &error)) {
CompleteInstall(result, error);
return;
}
// Use the requesting page as the referrer both since that is more correct // Use the requesting page as the referrer both since that is more correct
// (it is the page that caused this request to happen) and so that we can // (it is the page that caused this request to happen) and so that we can
// track top sites that trigger inline install requests. // track top sites that trigger inline install requests.
...@@ -79,13 +89,40 @@ void WebstoreStandaloneInstaller::AbortInstall() { ...@@ -79,13 +89,40 @@ void WebstoreStandaloneInstaller::AbortInstall() {
// Abort any in-progress fetches. // Abort any in-progress fetches.
if (webstore_data_fetcher_) { if (webstore_data_fetcher_) {
webstore_data_fetcher_.reset(); webstore_data_fetcher_.reset();
scoped_active_install_.reset();
Release(); // Matches the AddRef in BeginInstall. Release(); // Matches the AddRef in BeginInstall.
} }
} }
bool WebstoreStandaloneInstaller::EnsureUniqueInstall(
webstore_install::Result* reason,
std::string* error) {
InstallTracker* tracker = InstallTracker::Get(profile_);
DCHECK(tracker);
const ActiveInstallData* existing_install_data =
tracker->GetActiveInstall(id_);
if (existing_install_data) {
if (existing_install_data->is_ephemeral) {
*reason = webstore_install::LAUNCH_IN_PROGRESS;
*error = kLaunchInProgressError;
} else {
*reason = webstore_install::INSTALL_IN_PROGRESS;
*error = kInstallInProgressError;
}
return false;
}
ActiveInstallData install_data(id_);
InitInstallData(&install_data);
scoped_active_install_.reset(new ScopedActiveInstall(tracker, install_data));
return true;
}
void WebstoreStandaloneInstaller::CompleteInstall( void WebstoreStandaloneInstaller::CompleteInstall(
webstore_install::Result result, webstore_install::Result result,
const std::string& error) { const std::string& error) {
scoped_active_install_.reset();
if (!callback_.is_null()) if (!callback_.is_null())
callback_.Run(result == webstore_install::SUCCESS, error); callback_.Run(result == webstore_install::SUCCESS, error);
Release(); // Matches the AddRef in BeginInstall. Release(); // Matches the AddRef in BeginInstall.
...@@ -121,6 +158,11 @@ WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() { ...@@ -121,6 +158,11 @@ WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() {
return localized_extension_for_display_.get(); return localized_extension_for_display_.get();
} }
void WebstoreStandaloneInstaller::InitInstallData(
ActiveInstallData* install_data) const {
// Default implementation sets no properties.
}
void WebstoreStandaloneInstaller::OnManifestParsed() { void WebstoreStandaloneInstaller::OnManifestParsed() {
ProceedWithInstallPrompt(); ProceedWithInstallPrompt();
} }
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "chrome/browser/extensions/active_install_data.h"
#include "chrome/browser/extensions/extension_install_prompt.h" #include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/extensions/webstore_data_fetcher_delegate.h" #include "chrome/browser/extensions/webstore_data_fetcher_delegate.h"
#include "chrome/browser/extensions/webstore_install_helper.h" #include "chrome/browser/extensions/webstore_install_helper.h"
...@@ -64,6 +65,11 @@ class WebstoreStandaloneInstaller ...@@ -64,6 +65,11 @@ class WebstoreStandaloneInstaller
// Called when the install should be aborted. The callback is cleared. // Called when the install should be aborted. The callback is cleared.
void AbortInstall(); void AbortInstall();
// Checks InstallTracker and returns true if the same extension is not
// currently being installed. Registers this install with the InstallTracker.
bool EnsureUniqueInstall(webstore_install::Result* reason,
std::string* error);
// Called when the install is complete. // Called when the install is complete.
virtual void CompleteInstall(webstore_install::Result result, virtual void CompleteInstall(webstore_install::Result result,
const std::string& error); const std::string& error);
...@@ -78,6 +84,10 @@ class WebstoreStandaloneInstaller ...@@ -78,6 +84,10 @@ class WebstoreStandaloneInstaller
// Template Method's hooks to be implemented by subclasses. // Template Method's hooks to be implemented by subclasses.
// Called when this install is about to be registered with the InstallTracker.
// Allows subclasses to set properties of the install data.
virtual void InitInstallData(ActiveInstallData* install_data) const;
// Called at certain check points of the workflow to decide whether it makes // Called at certain check points of the workflow to decide whether it makes
// sense to proceed with installation. A requestor can be a website that // sense to proceed with installation. A requestor can be a website that
// initiated an inline installation, or a command line option. // initiated an inline installation, or a command line option.
...@@ -228,6 +238,9 @@ class WebstoreStandaloneInstaller ...@@ -228,6 +238,9 @@ class WebstoreStandaloneInstaller
scoped_ptr<base::DictionaryValue> manifest_; scoped_ptr<base::DictionaryValue> manifest_;
SkBitmap icon_; SkBitmap icon_;
// Active install registered with the InstallTracker.
scoped_ptr<ScopedActiveInstall> scoped_active_install_;
// Created by ShowInstallUI() when a prompt is shown (if // Created by ShowInstallUI() when a prompt is shown (if
// the implementor returns a non-NULL in CreateInstallPrompt()). // the implementor returns a non-NULL in CreateInstallPrompt()).
scoped_refptr<Extension> localized_extension_for_display_; scoped_refptr<Extension> localized_extension_for_display_;
......
...@@ -82,6 +82,7 @@ WebstoreResult::WebstoreResult(Profile* profile, ...@@ -82,6 +82,7 @@ WebstoreResult::WebstoreResult(Profile* profile,
set_title(base::UTF8ToUTF16(localized_name_)); set_title(base::UTF8ToUTF16(localized_name_));
SetDefaultDetails(); SetDefaultDetails();
InitAndStartObserving();
UpdateActions(); UpdateActions();
icon_ = gfx::ImageSkia( icon_ = gfx::ImageSkia(
...@@ -93,8 +94,6 @@ WebstoreResult::WebstoreResult(Profile* profile, ...@@ -93,8 +94,6 @@ WebstoreResult::WebstoreResult(Profile* profile,
IDR_WEBSTORE_ICON_32), IDR_WEBSTORE_ICON_32),
gfx::Size(kIconSize, kIconSize)); gfx::Size(kIconSize, kIconSize));
SetIcon(icon_); SetIcon(icon_);
StartObserving();
} }
WebstoreResult::~WebstoreResult() { WebstoreResult::~WebstoreResult() {
...@@ -124,6 +123,23 @@ scoped_ptr<ChromeSearchResult> WebstoreResult::Duplicate() { ...@@ -124,6 +123,23 @@ scoped_ptr<ChromeSearchResult> WebstoreResult::Duplicate() {
profile_, app_id_, localized_name_, icon_url_, controller_)).Pass(); profile_, app_id_, localized_name_, icon_url_, controller_)).Pass();
} }
void WebstoreResult::InitAndStartObserving() {
DCHECK(!install_tracker_ && !extension_registry_);
install_tracker_ = extensions::InstallTrackerFactory::GetForProfile(profile_);
extension_registry_ = extensions::ExtensionRegistry::Get(profile_);
const extensions::ActiveInstallData* install_data =
install_tracker_->GetActiveInstall(app_id_);
if (install_data) {
SetPercentDownloaded(install_data->percent_downloaded);
SetIsInstalling(true);
}
install_tracker_->AddObserver(this);
extension_registry_->AddObserver(this);
}
void WebstoreResult::UpdateActions() { void WebstoreResult::UpdateActions() {
Actions actions; Actions actions;
...@@ -206,7 +222,7 @@ void WebstoreResult::InstallCallback(bool success, const std::string& error) { ...@@ -206,7 +222,7 @@ void WebstoreResult::InstallCallback(bool success, const std::string& error) {
return; return;
} }
// Success handling is continued in OnExtensionWillBeInstalled. // Success handling is continued in OnExtensionInstalled.
SetPercentDownloaded(100); SetPercentDownloaded(100);
} }
...@@ -218,16 +234,6 @@ void WebstoreResult::LaunchCallback(extensions::webstore_install::Result result, ...@@ -218,16 +234,6 @@ void WebstoreResult::LaunchCallback(extensions::webstore_install::Result result,
SetIsInstalling(false); SetIsInstalling(false);
} }
void WebstoreResult::StartObserving() {
DCHECK(!install_tracker_ && !extension_registry_);
install_tracker_ = extensions::InstallTrackerFactory::GetForProfile(profile_);
install_tracker_->AddObserver(this);
extension_registry_ = extensions::ExtensionRegistry::Get(profile_);
extension_registry_->AddObserver(this);
}
void WebstoreResult::StopObservingInstall() { void WebstoreResult::StopObservingInstall() {
if (install_tracker_) if (install_tracker_)
install_tracker_->RemoveObserver(this); install_tracker_->RemoveObserver(this);
...@@ -248,20 +254,18 @@ void WebstoreResult::OnDownloadProgress(const std::string& extension_id, ...@@ -248,20 +254,18 @@ void WebstoreResult::OnDownloadProgress(const std::string& extension_id,
SetPercentDownloaded(percent_downloaded); SetPercentDownloaded(percent_downloaded);
} }
void WebstoreResult::OnExtensionWillBeInstalled( void WebstoreResult::OnExtensionInstalled(
content::BrowserContext* browser_context, content::BrowserContext* browser_context,
const extensions::Extension* extension, const extensions::Extension* extension,
bool is_update, bool is_update) {
bool from_ephemeral,
const std::string& old_name) {
if (extension->id() != app_id_) if (extension->id() != app_id_)
return; return;
SetIsInstalling(false); SetIsInstalling(false);
UpdateActions();
if (extensions::util::IsExtensionInstalledPermanently(extension->id(), if (extensions::util::IsExtensionInstalledPermanently(extension->id(),
profile_)) { profile_)) {
UpdateActions();
NotifyItemInstalled(); NotifyItemInstalled();
} }
} }
......
...@@ -43,6 +43,10 @@ class WebstoreResult : public ChromeSearchResult, ...@@ -43,6 +43,10 @@ class WebstoreResult : public ChromeSearchResult,
virtual ChromeSearchResultType GetType() OVERRIDE; virtual ChromeSearchResultType GetType() OVERRIDE;
private: private:
// Set the initial state and start observing both InstallObserver and
// ExtensionRegistryObserver.
void InitAndStartObserving();
void UpdateActions(); void UpdateActions();
void SetDefaultDetails(); void SetDefaultDetails();
void OnIconLoaded(); void OnIconLoaded();
...@@ -52,9 +56,6 @@ class WebstoreResult : public ChromeSearchResult, ...@@ -52,9 +56,6 @@ class WebstoreResult : public ChromeSearchResult,
void LaunchCallback(extensions::webstore_install::Result result, void LaunchCallback(extensions::webstore_install::Result result,
const std::string& error); const std::string& error);
// Start observing both InstallObserver and ExtensionRegistryObserver.
void StartObserving();
void StopObservingInstall(); void StopObservingInstall();
void StopObservingRegistry(); void StopObservingRegistry();
...@@ -64,12 +65,10 @@ class WebstoreResult : public ChromeSearchResult, ...@@ -64,12 +65,10 @@ class WebstoreResult : public ChromeSearchResult,
virtual void OnShutdown() OVERRIDE; virtual void OnShutdown() OVERRIDE;
// extensions::ExtensionRegistryObserver overides: // extensions::ExtensionRegistryObserver overides:
virtual void OnExtensionWillBeInstalled( virtual void OnExtensionInstalled(
content::BrowserContext* browser_context, content::BrowserContext* browser_context,
const extensions::Extension* extension, const extensions::Extension* extension,
bool is_update, bool is_update) OVERRIDE;
bool from_ephemeral,
const std::string& old_name) OVERRIDE;
virtual void OnShutdown(extensions::ExtensionRegistry* registry) OVERRIDE; virtual void OnShutdown(extensions::ExtensionRegistry* registry) OVERRIDE;
Profile* profile_; Profile* profile_;
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
'chrome_browser_extensions_extensions_sources': [ 'chrome_browser_extensions_extensions_sources': [
'browser/apps/ephemeral_app_service.cc', 'browser/apps/ephemeral_app_service.cc',
'browser/apps/ephemeral_app_service.h', 'browser/apps/ephemeral_app_service.h',
'browser/extensions/active_install_data.h',
'browser/extensions/active_install_data.cc',
'browser/extensions/app_icon_loader.h', 'browser/extensions/app_icon_loader.h',
'browser/extensions/app_icon_loader_impl.cc', 'browser/extensions/app_icon_loader_impl.cc',
'browser/extensions/app_icon_loader_impl.h', 'browser/extensions/app_icon_loader_impl.h',
......
...@@ -1008,6 +1008,7 @@ ...@@ -1008,6 +1008,7 @@
'browser/extensions/external_provider_impl_unittest.cc', 'browser/extensions/external_provider_impl_unittest.cc',
'browser/extensions/external_provider_impl_chromeos_unittest.cc', 'browser/extensions/external_provider_impl_chromeos_unittest.cc',
'browser/extensions/favicon_downloader_unittest.cc', 'browser/extensions/favicon_downloader_unittest.cc',
'browser/extensions/install_tracker_unittest.cc',
'browser/extensions/menu_manager_unittest.cc', 'browser/extensions/menu_manager_unittest.cc',
'browser/extensions/pack_extension_unittest.cc', 'browser/extensions/pack_extension_unittest.cc',
'browser/extensions/page_action_controller_unittest.cc', 'browser/extensions/page_action_controller_unittest.cc',
......
...@@ -307,7 +307,7 @@ ...@@ -307,7 +307,7 @@
{ {
"name": "result", "name": "result",
"type": "string", "type": "string",
"enum": ["success", "user_gesture_required", "unknown_error", "feature_disabled", "unsupported_extension_type", "missing_dependencies", "install_error", "user_cancelled", "invalid_id", "blacklisted", "blocked_by_policy", "install_in_progress"], "enum": ["success", "user_gesture_required", "unknown_error", "feature_disabled", "unsupported_extension_type", "missing_dependencies", "install_error", "user_cancelled", "invalid_id", "blacklisted", "blocked_by_policy", "install_in_progress", "launch_in_progress"],
"description": "Whether an attempt to launch an app succeeded, or the reason for failure." "description": "Whether an attempt to launch an app succeeded, or the reason for failure."
} }
] ]
......
...@@ -19,6 +19,8 @@ var kUnsupportedExtensionTypeError = ...@@ -19,6 +19,8 @@ var kUnsupportedExtensionTypeError =
// App ids. // App ids.
var kDefaultAppId = "kbiancnbopdghkfedjhfdoegjadfjeal"; var kDefaultAppId = "kbiancnbopdghkfedjhfdoegjadfjeal";
var kDefaultAppManifestPath = "app/manifest.json"; var kDefaultAppManifestPath = "app/manifest.json";
var kAppWithPermissionsId = "mbfcnecjknjpipkfkoangpfnhhlpamki";
var kAppWithPermissionsManifestPath = "app_with_permissions/manifest.json";
var kExtensionId = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeid"; var kExtensionId = "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeid";
var assertEq = chrome.test.assertEq; var assertEq = chrome.test.assertEq;
......
...@@ -47,16 +47,16 @@ var tests = [ ...@@ -47,16 +47,16 @@ var tests = [
// progress. // progress.
function pendingInstall() { function pendingInstall() {
// First initiate a full install of the app. // First initiate a full install of the app.
var manifest = getManifest(kDefaultAppManifestPath); var manifest = getManifest(kAppWithPermissionsManifestPath);
chrome.webstorePrivate.beginInstallWithManifest3( chrome.webstorePrivate.beginInstallWithManifest3(
{ "id": kDefaultAppId, "manifest": manifest }, { "id": kAppWithPermissionsId, "manifest": manifest },
callbackPass(function(result) { callbackPass(function(result) {
assertEq(result, ""); assertEq(result, "");
// Attempt to launch the app ephemerally. // Attempt to launch the app ephemerally.
chrome.test.runWithUserGesture(function() { chrome.test.runWithUserGesture(function() {
chrome.webstorePrivate.launchEphemeralApp( chrome.webstorePrivate.launchEphemeralApp(
kDefaultAppId, kAppWithPermissionsId,
callbackFail(kInstallInProgressError, function(result) { callbackFail(kInstallInProgressError, function(result) {
assertEq(kInstallInProgressCode, result); assertEq(kInstallInProgressCode, result);
})); }));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment