1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/api/webstore_private/webstore_private_api.h"
7 #include "base/bind_helpers.h"
8 #include "base/command_line.h"
9 #include "base/lazy_instance.h"
10 #include "base/memory/scoped_vector.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/browser/about_flags.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/chrome_notification_types.h"
19 #include "chrome/browser/extensions/crx_installer.h"
20 #include "chrome/browser/extensions/extension_function_dispatcher.h"
21 #include "chrome/browser/extensions/extension_service.h"
22 #include "chrome/browser/extensions/extension_system.h"
23 #include "chrome/browser/extensions/extension_util.h"
24 #include "chrome/browser/extensions/webstore_installer.h"
25 #include "chrome/browser/gpu/gpu_feature_checker.h"
26 #include "chrome/browser/profiles/profile_manager.h"
27 #include "chrome/browser/signin/signin_manager.h"
28 #include "chrome/browser/signin/signin_manager_factory.h"
29 #include "chrome/browser/sync/profile_sync_service.h"
30 #include "chrome/browser/sync/profile_sync_service_factory.h"
31 #include "chrome/browser/ui/app_list/app_list_service.h"
32 #include "chrome/browser/ui/app_list/app_list_util.h"
33 #include "chrome/browser/ui/browser.h"
34 #include "chrome/common/extensions/extension_constants.h"
35 #include "chrome/common/extensions/extension_l10n_util.h"
36 #include "chrome/common/pref_names.h"
37 #include "content/public/browser/gpu_data_manager.h"
38 #include "content/public/browser/notification_details.h"
39 #include "content/public/browser/notification_source.h"
40 #include "content/public/browser/web_contents.h"
41 #include "extensions/browser/extension_prefs.h"
42 #include "extensions/common/error_utils.h"
43 #include "extensions/common/extension.h"
44 #include "grit/chromium_strings.h"
45 #include "grit/generated_resources.h"
46 #include "ui/base/l10n/l10n_util.h"
48 using content::GpuDataManager;
50 namespace extensions {
52 namespace BeginInstallWithManifest3 =
53 api::webstore_private::BeginInstallWithManifest3;
54 namespace CompleteInstall = api::webstore_private::CompleteInstall;
55 namespace GetBrowserLogin = api::webstore_private::GetBrowserLogin;
56 namespace GetIsLauncherEnabled = api::webstore_private::GetIsLauncherEnabled;
57 namespace GetStoreLogin = api::webstore_private::GetStoreLogin;
58 namespace GetWebGLStatus = api::webstore_private::GetWebGLStatus;
59 namespace InstallBundle = api::webstore_private::InstallBundle;
60 namespace IsInIncognitoMode = api::webstore_private::IsInIncognitoMode;
61 namespace SetStoreLogin = api::webstore_private::SetStoreLogin;
65 // Holds the Approvals between the time we prompt and start the installs.
66 class PendingApprovals {
71 void PushApproval(scoped_ptr<WebstoreInstaller::Approval> approval);
72 scoped_ptr<WebstoreInstaller::Approval> PopApproval(
73 Profile* profile, const std::string& id);
75 typedef ScopedVector<WebstoreInstaller::Approval> ApprovalList;
77 ApprovalList approvals_;
79 DISALLOW_COPY_AND_ASSIGN(PendingApprovals);
82 PendingApprovals::PendingApprovals() {}
83 PendingApprovals::~PendingApprovals() {}
85 void PendingApprovals::PushApproval(
86 scoped_ptr<WebstoreInstaller::Approval> approval) {
87 approvals_.push_back(approval.release());
90 scoped_ptr<WebstoreInstaller::Approval> PendingApprovals::PopApproval(
91 Profile* profile, const std::string& id) {
92 for (size_t i = 0; i < approvals_.size(); ++i) {
93 WebstoreInstaller::Approval* approval = approvals_[i];
94 if (approval->extension_id == id &&
95 profile->IsSameProfile(approval->profile)) {
96 approvals_.weak_erase(approvals_.begin() + i);
97 return scoped_ptr<WebstoreInstaller::Approval>(approval);
100 return scoped_ptr<WebstoreInstaller::Approval>();
103 // Uniquely holds the profile and extension id of an install between the time we
104 // prompt and complete the installs.
105 class PendingInstalls {
110 bool InsertInstall(Profile* profile, const std::string& id);
111 void EraseInstall(Profile* profile, const std::string& id);
113 typedef std::pair<Profile*, std::string> ProfileAndExtensionId;
114 typedef std::vector<ProfileAndExtensionId> InstallList;
116 InstallList::iterator FindInstall(Profile* profile, const std::string& id);
118 InstallList installs_;
120 DISALLOW_COPY_AND_ASSIGN(PendingInstalls);
123 PendingInstalls::PendingInstalls() {}
124 PendingInstalls::~PendingInstalls() {}
126 // Returns true and inserts the profile/id pair if it is not present. Otherwise
128 bool PendingInstalls::InsertInstall(Profile* profile, const std::string& id) {
129 if (FindInstall(profile, id) != installs_.end())
131 installs_.push_back(make_pair(profile, id));
135 // Removes the given profile/id pair.
136 void PendingInstalls::EraseInstall(Profile* profile, const std::string& id) {
137 InstallList::iterator it = FindInstall(profile, id);
138 if (it != installs_.end())
142 PendingInstalls::InstallList::iterator PendingInstalls::FindInstall(
144 const std::string& id) {
145 for (size_t i = 0; i < installs_.size(); ++i) {
146 ProfileAndExtensionId install = installs_[i];
147 if (install.second == id && profile->IsSameProfile(install.first))
148 return (installs_.begin() + i);
150 return installs_.end();
153 static base::LazyInstance<PendingApprovals> g_pending_approvals =
154 LAZY_INSTANCE_INITIALIZER;
155 static base::LazyInstance<PendingInstalls> g_pending_installs =
156 LAZY_INSTANCE_INITIALIZER;
158 // A preference set by the web store to indicate login information for
160 const char kWebstoreLogin[] = "extensions.webstore_login";
161 const char kAlreadyInstalledError[] = "This item is already installed";
162 const char kCannotSpecifyIconDataAndUrlError[] =
163 "You cannot specify both icon data and an icon url";
164 const char kInvalidIconUrlError[] = "Invalid icon url";
165 const char kInvalidIdError[] = "Invalid id";
166 const char kInvalidManifestError[] = "Invalid manifest";
167 const char kNoPreviousBeginInstallWithManifestError[] =
168 "* does not match a previous call to beginInstallWithManifest3";
169 const char kUserCancelledError[] = "User cancelled install";
171 WebstoreInstaller::Delegate* test_webstore_installer_delegate = NULL;
173 // We allow the web store to set a string containing login information when a
174 // purchase is made, so that when a user logs into sync with a different
175 // account we can recognize the situation. The Get function returns the login if
176 // there was previously stored data, or an empty string otherwise. The Set will
177 // overwrite any previous login.
178 std::string GetWebstoreLogin(Profile* profile) {
179 if (profile->GetPrefs()->HasPrefPath(kWebstoreLogin))
180 return profile->GetPrefs()->GetString(kWebstoreLogin);
181 return std::string();
184 void SetWebstoreLogin(Profile* profile, const std::string& login) {
185 profile->GetPrefs()->SetString(kWebstoreLogin, login);
188 void RecordWebstoreExtensionInstallResult(bool success) {
189 UMA_HISTOGRAM_BOOLEAN("Webstore.ExtensionInstallResult", success);
195 void WebstorePrivateApi::SetWebstoreInstallerDelegateForTesting(
196 WebstoreInstaller::Delegate* delegate) {
197 test_webstore_installer_delegate = delegate;
201 scoped_ptr<WebstoreInstaller::Approval>
202 WebstorePrivateApi::PopApprovalForTesting(
203 Profile* profile, const std::string& extension_id) {
204 return g_pending_approvals.Get().PopApproval(profile, extension_id);
207 WebstorePrivateInstallBundleFunction::WebstorePrivateInstallBundleFunction() {}
208 WebstorePrivateInstallBundleFunction::~WebstorePrivateInstallBundleFunction() {}
210 bool WebstorePrivateInstallBundleFunction::RunImpl() {
211 scoped_ptr<InstallBundle::Params> params(
212 InstallBundle::Params::Create(*args_));
213 EXTENSION_FUNCTION_VALIDATE(params);
215 BundleInstaller::ItemList items;
216 if (!ReadBundleInfo(*params, &items))
219 bundle_ = new BundleInstaller(GetCurrentBrowser(), items);
221 AddRef(); // Balanced in OnBundleInstallCompleted / OnBundleInstallCanceled.
223 bundle_->PromptForApproval(this);
227 bool WebstorePrivateInstallBundleFunction::
228 ReadBundleInfo(const InstallBundle::Params& params,
229 BundleInstaller::ItemList* items) {
230 for (size_t i = 0; i < params.details.size(); ++i) {
231 BundleInstaller::Item item;
232 item.id = params.details[i]->id;
233 item.manifest = params.details[i]->manifest;
234 item.localized_name = params.details[i]->localized_name;
235 items->push_back(item);
241 void WebstorePrivateInstallBundleFunction::OnBundleInstallApproved() {
242 bundle_->CompleteInstall(
243 &(dispatcher()->delegate()->GetAssociatedWebContents()->GetController()),
247 void WebstorePrivateInstallBundleFunction::OnBundleInstallCanceled(
248 bool user_initiated) {
250 error_ = "user_canceled";
252 error_ = "unknown_error";
256 Release(); // Balanced in RunImpl().
259 void WebstorePrivateInstallBundleFunction::OnBundleInstallCompleted() {
262 Release(); // Balanced in RunImpl().
265 WebstorePrivateBeginInstallWithManifest3Function::
266 WebstorePrivateBeginInstallWithManifest3Function() {}
268 WebstorePrivateBeginInstallWithManifest3Function::
269 ~WebstorePrivateBeginInstallWithManifest3Function() {}
271 bool WebstorePrivateBeginInstallWithManifest3Function::RunImpl() {
272 params_ = BeginInstallWithManifest3::Params::Create(*args_);
273 EXTENSION_FUNCTION_VALIDATE(params_);
275 if (!extensions::Extension::IdIsValid(params_->details.id)) {
276 SetResultCode(INVALID_ID);
277 error_ = kInvalidIdError;
281 if (params_->details.icon_data && params_->details.icon_url) {
282 SetResultCode(ICON_ERROR);
283 error_ = kCannotSpecifyIconDataAndUrlError;
288 if (params_->details.icon_url) {
290 icon_url = source_url().Resolve(*params_->details.icon_url);
291 if (!icon_url.is_valid()) {
292 SetResultCode(INVALID_ICON_URL);
293 error_ = kInvalidIconUrlError;
298 std::string icon_data = params_->details.icon_data ?
299 *params_->details.icon_data : std::string();
301 Profile* profile = GetProfile();
302 if (util::IsExtensionInstalledPermanently(params_->details.id, profile) ||
303 !g_pending_installs.Get().InsertInstall(profile, params_->details.id)) {
304 SetResultCode(ALREADY_INSTALLED);
305 error_ = kAlreadyInstalledError;
309 net::URLRequestContextGetter* context_getter = NULL;
310 if (!icon_url.is_empty())
311 context_getter = GetProfile()->GetRequestContext();
313 scoped_refptr<WebstoreInstallHelper> helper = new WebstoreInstallHelper(
314 this, params_->details.id, params_->details.manifest, icon_data, icon_url,
317 // The helper will call us back via OnWebstoreParseSuccess or
318 // OnWebstoreParseFailure.
321 // Matched with a Release in OnWebstoreParseSuccess/OnWebstoreParseFailure.
324 // The response is sent asynchronously in OnWebstoreParseSuccess/
325 // OnWebstoreParseFailure.
329 const char* WebstorePrivateBeginInstallWithManifest3Function::
330 ResultCodeToString(ResultCode code) {
335 return "unknown_error";
337 return "user_cancelled";
339 return "manifest_error";
344 case PERMISSION_DENIED:
345 return "permission_denied";
346 case INVALID_ICON_URL:
347 return "invalid_icon_url";
349 return "signin_failed";
350 case ALREADY_INSTALLED:
351 return "already_installed";
357 void WebstorePrivateBeginInstallWithManifest3Function::SetResultCode(
359 results_ = BeginInstallWithManifest3::Results::Create(
360 ResultCodeToString(code));
363 void WebstorePrivateBeginInstallWithManifest3Function::OnWebstoreParseSuccess(
364 const std::string& id,
365 const SkBitmap& icon,
366 base::DictionaryValue* parsed_manifest) {
367 CHECK_EQ(params_->details.id, id);
368 CHECK(parsed_manifest);
370 parsed_manifest_.reset(parsed_manifest);
372 std::string localized_name = params_->details.localized_name ?
373 *params_->details.localized_name : std::string();
376 dummy_extension_ = ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
377 parsed_manifest_.get(),
378 Extension::FROM_WEBSTORE,
384 if (!dummy_extension_.get()) {
385 OnWebstoreParseFailure(params_->details.id,
386 WebstoreInstallHelper::Delegate::MANIFEST_ERROR,
387 kInvalidManifestError);
391 SigninManagerBase* signin_manager =
392 SigninManagerFactory::GetForProfile(GetProfile());
393 if (dummy_extension_->is_platform_app() &&
395 signin_manager->GetAuthenticatedUsername().empty() &&
396 signin_manager->AuthInProgress()) {
397 signin_tracker_.reset(new SigninTracker(GetProfile(), this));
401 SigninCompletedOrNotNeeded();
404 void WebstorePrivateBeginInstallWithManifest3Function::OnWebstoreParseFailure(
405 const std::string& id,
406 WebstoreInstallHelper::Delegate::InstallHelperResultCode result_code,
407 const std::string& error_message) {
408 CHECK_EQ(params_->details.id, id);
410 // Map from WebstoreInstallHelper's result codes to ours.
411 switch (result_code) {
412 case WebstoreInstallHelper::Delegate::UNKNOWN_ERROR:
413 SetResultCode(UNKNOWN_ERROR);
415 case WebstoreInstallHelper::Delegate::ICON_ERROR:
416 SetResultCode(ICON_ERROR);
418 case WebstoreInstallHelper::Delegate::MANIFEST_ERROR:
419 SetResultCode(MANIFEST_ERROR);
424 error_ = error_message;
425 g_pending_installs.Get().EraseInstall(GetProfile(), id);
428 // Matches the AddRef in RunImpl().
432 void WebstorePrivateBeginInstallWithManifest3Function::SigninFailed(
433 const GoogleServiceAuthError& error) {
434 signin_tracker_.reset();
436 SetResultCode(SIGNIN_FAILED);
437 error_ = error.ToString();
438 g_pending_installs.Get().EraseInstall(GetProfile(), params_->details.id);
441 // Matches the AddRef in RunImpl().
445 void WebstorePrivateBeginInstallWithManifest3Function::SigninSuccess() {
446 signin_tracker_.reset();
448 SigninCompletedOrNotNeeded();
451 void WebstorePrivateBeginInstallWithManifest3Function::MergeSessionComplete(
452 const GoogleServiceAuthError& error) {
453 // TODO(rogerta): once the embeded inline flow is enabled, the code in
454 // WebstorePrivateBeginInstallWithManifest3Function::SigninSuccess()
455 // should move to here.
458 void WebstorePrivateBeginInstallWithManifest3Function::
459 SigninCompletedOrNotNeeded() {
460 content::WebContents* web_contents = GetAssociatedWebContents();
461 if (!web_contents) // The browser window has gone away.
463 install_prompt_.reset(new ExtensionInstallPrompt(web_contents));
464 install_prompt_->ConfirmWebstoreInstall(
466 dummy_extension_.get(),
468 ExtensionInstallPrompt::GetDefaultShowDialogCallback());
469 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
472 void WebstorePrivateBeginInstallWithManifest3Function::InstallUIProceed() {
473 // This gets cleared in CrxInstaller::ConfirmInstall(). TODO(asargent) - in
474 // the future we may also want to add time-based expiration, where a whitelist
475 // entry is only valid for some number of minutes.
476 scoped_ptr<WebstoreInstaller::Approval> approval(
477 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
478 GetProfile(), params_->details.id, parsed_manifest_.Pass(), false));
479 approval->use_app_installed_bubble = params_->details.app_install_bubble;
480 approval->enable_launcher = params_->details.enable_launcher;
481 // If we are enabling the launcher, we should not show the app list in order
482 // to train the user to open it themselves at least once.
483 approval->skip_post_install_ui = params_->details.enable_launcher;
484 approval->dummy_extension = dummy_extension_;
485 approval->installing_icon = gfx::ImageSkia::CreateFrom1xBitmap(icon_);
486 g_pending_approvals.Get().PushApproval(approval.Pass());
488 SetResultCode(ERROR_NONE);
491 // The Permissions_Install histogram is recorded from the ExtensionService
492 // for all extension installs, so we only need to record the web store
493 // specific histogram here.
494 ExtensionService::RecordPermissionMessagesHistogram(
495 dummy_extension_.get(), "Extensions.Permissions_WebStoreInstall");
497 // Matches the AddRef in RunImpl().
501 void WebstorePrivateBeginInstallWithManifest3Function::InstallUIAbort(
502 bool user_initiated) {
503 error_ = kUserCancelledError;
504 SetResultCode(USER_CANCELLED);
505 g_pending_installs.Get().EraseInstall(GetProfile(), params_->details.id);
508 // The web store install histograms are a subset of the install histograms.
509 // We need to record both histograms here since CrxInstaller::InstallUIAbort
510 // is never called for web store install cancellations.
511 std::string histogram_name = user_initiated ?
512 "Extensions.Permissions_WebStoreInstallCancel" :
513 "Extensions.Permissions_WebStoreInstallAbort";
514 ExtensionService::RecordPermissionMessagesHistogram(dummy_extension_.get(),
515 histogram_name.c_str());
517 histogram_name = user_initiated ?
518 "Extensions.Permissions_InstallCancel" :
519 "Extensions.Permissions_InstallAbort";
520 ExtensionService::RecordPermissionMessagesHistogram(dummy_extension_.get(),
521 histogram_name.c_str());
523 // Matches the AddRef in RunImpl().
527 WebstorePrivateCompleteInstallFunction::
528 WebstorePrivateCompleteInstallFunction() {}
530 WebstorePrivateCompleteInstallFunction::
531 ~WebstorePrivateCompleteInstallFunction() {}
533 bool WebstorePrivateCompleteInstallFunction::RunImpl() {
534 scoped_ptr<CompleteInstall::Params> params(
535 CompleteInstall::Params::Create(*args_));
536 EXTENSION_FUNCTION_VALIDATE(params);
537 if (!extensions::Extension::IdIsValid(params->expected_id)) {
538 error_ = kInvalidIdError;
542 approval_ = g_pending_approvals.Get()
543 .PopApproval(GetProfile(), params->expected_id)
546 error_ = ErrorUtils::FormatErrorMessage(
547 kNoPreviousBeginInstallWithManifestError, params->expected_id);
551 // Balanced in OnExtensionInstallSuccess() or OnExtensionInstallFailure().
553 AppListService* app_list_service =
554 AppListService::Get(GetCurrentBrowser()->host_desktop_type());
556 if (approval_->enable_launcher)
557 app_list_service->EnableAppList(GetProfile());
559 if (IsAppLauncherEnabled() && approval_->manifest->is_app()) {
560 // Show the app list to show download is progressing. Don't show the app
561 // list on first app install so users can be trained to open it themselves.
562 if (approval_->enable_launcher)
563 app_list_service->CreateForProfile(GetProfile());
565 app_list_service->ShowForProfile(GetProfile());
568 // The extension will install through the normal extension install flow, but
569 // the whitelist entry will bypass the normal permissions install dialog.
570 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
573 &(dispatcher()->delegate()->GetAssociatedWebContents()->GetController()),
576 WebstoreInstaller::INSTALL_SOURCE_OTHER);
582 void WebstorePrivateCompleteInstallFunction::OnExtensionInstallSuccess(
583 const std::string& id) {
584 if (test_webstore_installer_delegate)
585 test_webstore_installer_delegate->OnExtensionInstallSuccess(id);
587 VLOG(1) << "Install success, sending response";
588 g_pending_installs.Get().EraseInstall(GetProfile(), id);
591 RecordWebstoreExtensionInstallResult(true);
593 // Matches the AddRef in RunImpl().
597 void WebstorePrivateCompleteInstallFunction::OnExtensionInstallFailure(
598 const std::string& id,
599 const std::string& error,
600 WebstoreInstaller::FailureReason reason) {
601 if (test_webstore_installer_delegate) {
602 test_webstore_installer_delegate->OnExtensionInstallFailure(
607 VLOG(1) << "Install failed, sending response";
608 g_pending_installs.Get().EraseInstall(GetProfile(), id);
611 RecordWebstoreExtensionInstallResult(false);
613 // Matches the AddRef in RunImpl().
617 WebstorePrivateEnableAppLauncherFunction::
618 WebstorePrivateEnableAppLauncherFunction() {}
620 WebstorePrivateEnableAppLauncherFunction::
621 ~WebstorePrivateEnableAppLauncherFunction() {}
623 bool WebstorePrivateEnableAppLauncherFunction::RunImpl() {
624 AppListService::Get(GetCurrentBrowser()->host_desktop_type())->
625 EnableAppList(GetProfile());
629 bool WebstorePrivateGetBrowserLoginFunction::RunImpl() {
630 GetBrowserLogin::Results::Info info;
631 info.login = GetProfile()->GetOriginalProfile()->GetPrefs()->GetString(
632 prefs::kGoogleServicesUsername);
633 results_ = GetBrowserLogin::Results::Create(info);
637 bool WebstorePrivateGetStoreLoginFunction::RunImpl() {
638 results_ = GetStoreLogin::Results::Create(GetWebstoreLogin(GetProfile()));
642 bool WebstorePrivateSetStoreLoginFunction::RunImpl() {
643 scoped_ptr<SetStoreLogin::Params> params(
644 SetStoreLogin::Params::Create(*args_));
645 EXTENSION_FUNCTION_VALIDATE(params);
646 SetWebstoreLogin(GetProfile(), params->login);
650 WebstorePrivateGetWebGLStatusFunction::WebstorePrivateGetWebGLStatusFunction() {
651 feature_checker_ = new GPUFeatureChecker(
652 gpu::GPU_FEATURE_TYPE_WEBGL,
653 base::Bind(&WebstorePrivateGetWebGLStatusFunction::OnFeatureCheck,
654 base::Unretained(this)));
657 WebstorePrivateGetWebGLStatusFunction::
658 ~WebstorePrivateGetWebGLStatusFunction() {}
660 void WebstorePrivateGetWebGLStatusFunction::CreateResult(bool webgl_allowed) {
661 results_ = GetWebGLStatus::Results::Create(GetWebGLStatus::Results::
662 ParseWebgl_status(webgl_allowed ? "webgl_allowed" : "webgl_blocked"));
665 bool WebstorePrivateGetWebGLStatusFunction::RunImpl() {
666 feature_checker_->CheckGPUFeatureAvailability();
670 void WebstorePrivateGetWebGLStatusFunction::
671 OnFeatureCheck(bool feature_allowed) {
672 CreateResult(feature_allowed);
676 bool WebstorePrivateGetIsLauncherEnabledFunction::RunImpl() {
677 results_ = GetIsLauncherEnabled::Results::Create(IsAppLauncherEnabled());
681 bool WebstorePrivateIsInIncognitoModeFunction::RunImpl() {
682 results_ = IsInIncognitoMode::Results::Create(
683 GetProfile() != GetProfile()->GetOriginalProfile());
687 } // namespace extensions