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/webstore_standalone_installer.h"
7 #include "base/values.h"
8 #include "base/version.h"
9 #include "chrome/browser/extensions/crx_installer.h"
10 #include "chrome/browser/extensions/extension_install_prompt.h"
11 #include "chrome/browser/extensions/extension_install_ui.h"
12 #include "chrome/browser/extensions/extension_install_ui_util.h"
13 #include "chrome/browser/extensions/extension_service.h"
14 #include "chrome/browser/extensions/install_tracker.h"
15 #include "chrome/browser/extensions/webstore_data_fetcher.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "components/crx_file/id_util.h"
18 #include "content/public/browser/web_contents.h"
19 #include "extensions/browser/extension_prefs.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/extension_util.h"
23 #include "extensions/common/extension.h"
24 #include "extensions/common/extension_urls.h"
27 using content::WebContents
;
29 namespace extensions
{
31 const char kInvalidWebstoreItemId
[] = "Invalid Chrome Web Store item ID";
32 const char kWebstoreRequestError
[] =
33 "Could not fetch data from the Chrome Web Store";
34 const char kInvalidWebstoreResponseError
[] = "Invalid Chrome Web Store reponse";
35 const char kInvalidManifestError
[] = "Invalid manifest";
36 const char kUserCancelledError
[] = "User cancelled install";
37 const char kExtensionIsBlacklisted
[] = "Extension is blacklisted";
38 const char kInstallInProgressError
[] = "An install is already in progress";
39 const char kLaunchInProgressError
[] = "A launch is already in progress";
41 WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
42 const std::string
& webstore_item_id
,
44 const Callback
& callback
)
45 : id_(webstore_item_id
),
48 install_source_(WebstoreInstaller::INSTALL_SOURCE_INLINE
),
49 show_user_count_(true),
54 void WebstoreStandaloneInstaller::BeginInstall() {
55 // Add a ref to keep this alive for WebstoreDataFetcher.
56 // All code paths from here eventually lead to either CompleteInstall or
57 // AbortInstall, which both release this ref.
60 if (!crx_file::id_util::IdIsValid(id_
)) {
61 CompleteInstall(webstore_install::INVALID_ID
, kInvalidWebstoreItemId
);
65 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
67 if (!EnsureUniqueInstall(&result
, &error
)) {
68 CompleteInstall(result
, error
);
72 // Use the requesting page as the referrer both since that is more correct
73 // (it is the page that caused this request to happen) and so that we can
74 // track top sites that trigger inline install requests.
75 webstore_data_fetcher_
.reset(new WebstoreDataFetcher(
77 profile_
->GetRequestContext(),
80 webstore_data_fetcher_
->Start();
84 // Private interface implementation.
87 WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
90 void WebstoreStandaloneInstaller::RunCallback(bool success
,
91 const std::string
& error
,
92 webstore_install::Result result
) {
93 callback_
.Run(success
, error
, result
);
96 void WebstoreStandaloneInstaller::AbortInstall() {
98 // Abort any in-progress fetches.
99 if (webstore_data_fetcher_
) {
100 webstore_data_fetcher_
.reset();
101 scoped_active_install_
.reset();
102 Release(); // Matches the AddRef in BeginInstall.
106 bool WebstoreStandaloneInstaller::EnsureUniqueInstall(
107 webstore_install::Result
* reason
,
108 std::string
* error
) {
109 InstallTracker
* tracker
= InstallTracker::Get(profile_
);
112 const ActiveInstallData
* existing_install_data
=
113 tracker
->GetActiveInstall(id_
);
114 if (existing_install_data
) {
115 if (existing_install_data
->is_ephemeral
) {
116 *reason
= webstore_install::LAUNCH_IN_PROGRESS
;
117 *error
= kLaunchInProgressError
;
119 *reason
= webstore_install::INSTALL_IN_PROGRESS
;
120 *error
= kInstallInProgressError
;
125 ActiveInstallData
install_data(id_
);
126 InitInstallData(&install_data
);
127 scoped_active_install_
.reset(new ScopedActiveInstall(tracker
, install_data
));
131 void WebstoreStandaloneInstaller::CompleteInstall(
132 webstore_install::Result result
,
133 const std::string
& error
) {
134 scoped_active_install_
.reset();
135 if (!callback_
.is_null())
136 callback_
.Run(result
== webstore_install::SUCCESS
, error
, result
);
137 Release(); // Matches the AddRef in BeginInstall.
140 void WebstoreStandaloneInstaller::ProceedWithInstallPrompt() {
141 install_prompt_
= CreateInstallPrompt();
142 if (install_prompt_
.get()) {
144 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
150 scoped_refptr
<const Extension
>
151 WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() {
152 if (!localized_extension_for_display_
.get()) {
153 DCHECK(manifest_
.get());
154 if (!manifest_
.get())
158 localized_extension_for_display_
=
159 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
161 Extension::REQUIRE_KEY
| Extension::FROM_WEBSTORE
,
164 localized_description_
,
167 return localized_extension_for_display_
.get();
170 void WebstoreStandaloneInstaller::InitInstallData(
171 ActiveInstallData
* install_data
) const {
172 // Default implementation sets no properties.
175 void WebstoreStandaloneInstaller::OnManifestParsed() {
176 ProceedWithInstallPrompt();
179 scoped_ptr
<ExtensionInstallPrompt
>
180 WebstoreStandaloneInstaller::CreateInstallUI() {
181 return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents()));
184 scoped_ptr
<WebstoreInstaller::Approval
>
185 WebstoreStandaloneInstaller::CreateApproval() const {
186 scoped_ptr
<WebstoreInstaller::Approval
> approval(
187 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
190 scoped_ptr
<base::DictionaryValue
>(manifest_
.get()->DeepCopy()),
192 approval
->skip_post_install_ui
= !ShouldShowPostInstallUI();
193 approval
->use_app_installed_bubble
= ShouldShowAppInstalledBubble();
194 approval
->installing_icon
= gfx::ImageSkia::CreateFrom1xBitmap(icon_
);
195 return approval
.Pass();
198 void WebstoreStandaloneInstaller::InstallUIProceed() {
199 if (!CheckRequestorAlive()) {
200 CompleteInstall(webstore_install::ABORTED
, std::string());
204 scoped_ptr
<WebstoreInstaller::Approval
> approval
= CreateApproval();
206 ExtensionService
* extension_service
=
207 ExtensionSystem::Get(profile_
)->extension_service();
208 const Extension
* installed_extension
=
209 extension_service
->GetExtensionById(id_
, true /* include disabled */);
210 if (installed_extension
) {
211 std::string install_message
;
212 webstore_install::Result install_result
= webstore_install::SUCCESS
;
215 if (ExtensionPrefs::Get(profile_
)->IsExtensionBlacklisted(id_
)) {
216 // Don't install a blacklisted extension.
217 install_result
= webstore_install::BLACKLISTED
;
218 install_message
= kExtensionIsBlacklisted
;
219 } else if (util::IsEphemeralApp(installed_extension
->id(), profile_
) &&
220 !approval
->is_ephemeral
) {
221 // If the target extension has already been installed ephemerally and is
222 // up to date, it can be promoted to a regular installed extension and
223 // downloading from the Web Store is not necessary.
224 scoped_refptr
<const Extension
> extension_to_install
=
225 GetLocalizedExtensionForDisplay();
226 if (!extension_to_install
.get()) {
227 CompleteInstall(webstore_install::INVALID_MANIFEST
,
228 kInvalidManifestError
);
232 if (installed_extension
->version()->CompareTo(
233 *extension_to_install
->version()) < 0) {
234 // If the existing extension is out of date, proceed with the install
235 // to update the extension.
238 install_ui::ShowPostInstallUIForApproval(
239 profile_
, *approval
, installed_extension
);
240 extension_service
->PromoteEphemeralApp(installed_extension
, false);
242 } else if (!extension_service
->IsExtensionEnabled(id_
)) {
243 // If the extension is installed but disabled, and not blacklisted,
245 extension_service
->EnableExtension(id_
);
246 } // else extension is installed and enabled; no work to be done.
249 CompleteInstall(install_result
, install_message
);
254 scoped_refptr
<WebstoreInstaller
> installer
= new WebstoreInstaller(
264 void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated
) {
265 CompleteInstall(webstore_install::USER_CANCELLED
, kUserCancelledError
);
268 void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
269 OnWebStoreDataFetcherDone();
270 CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR
,
271 kWebstoreRequestError
);
274 void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
275 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
276 OnWebStoreDataFetcherDone();
278 if (!CheckRequestorAlive()) {
279 CompleteInstall(webstore_install::ABORTED
, std::string());
285 if (!CheckInlineInstallPermitted(*webstore_data
, &error
)) {
286 CompleteInstall(webstore_install::NOT_PERMITTED
, error
);
290 if (!CheckRequestorPermitted(*webstore_data
, &error
)) {
291 CompleteInstall(webstore_install::NOT_PERMITTED
, error
);
295 // Manifest, number of users, average rating and rating count are required.
296 std::string manifest
;
297 if (!webstore_data
->GetString(kManifestKey
, &manifest
) ||
298 !webstore_data
->GetString(kUsersKey
, &localized_user_count_
) ||
299 !webstore_data
->GetDouble(kAverageRatingKey
, &average_rating_
) ||
300 !webstore_data
->GetInteger(kRatingCountKey
, &rating_count_
)) {
301 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
302 kInvalidWebstoreResponseError
);
307 show_user_count_
= true;
308 webstore_data
->GetBoolean(kShowUserCountKey
, &show_user_count_
);
310 if (average_rating_
< ExtensionInstallPrompt::kMinExtensionRating
||
311 average_rating_
> ExtensionInstallPrompt::kMaxExtensionRating
) {
312 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
313 kInvalidWebstoreResponseError
);
317 // Localized name and description are optional.
318 if ((webstore_data
->HasKey(kLocalizedNameKey
) &&
319 !webstore_data
->GetString(kLocalizedNameKey
, &localized_name_
)) ||
320 (webstore_data
->HasKey(kLocalizedDescriptionKey
) &&
321 !webstore_data
->GetString(
322 kLocalizedDescriptionKey
, &localized_description_
))) {
323 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
324 kInvalidWebstoreResponseError
);
328 // Icon URL is optional.
330 if (webstore_data
->HasKey(kIconUrlKey
)) {
331 std::string icon_url_string
;
332 if (!webstore_data
->GetString(kIconUrlKey
, &icon_url_string
)) {
333 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
334 kInvalidWebstoreResponseError
);
337 icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
339 if (!icon_url
.is_valid()) {
340 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
341 kInvalidWebstoreResponseError
);
346 // Assume ownership of webstore_data.
347 webstore_data_
= webstore_data
.Pass();
349 scoped_refptr
<WebstoreInstallHelper
> helper
=
350 new WebstoreInstallHelper(this,
353 std::string(), // We don't have any icon data.
355 profile_
->GetRequestContext());
356 // The helper will call us back via OnWebstoreParseSucces or
357 // OnWebstoreParseFailure.
361 void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
362 const std::string
& error
) {
363 OnWebStoreDataFetcherDone();
364 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
, error
);
367 void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
368 const std::string
& id
,
369 const SkBitmap
& icon
,
370 base::DictionaryValue
* manifest
) {
373 if (!CheckRequestorAlive()) {
374 CompleteInstall(webstore_install::ABORTED
, std::string());
378 manifest_
.reset(manifest
);
384 void WebstoreStandaloneInstaller::OnWebstoreParseFailure(
385 const std::string
& id
,
386 InstallHelperResultCode result_code
,
387 const std::string
& error_message
) {
388 webstore_install::Result install_result
= webstore_install::OTHER_ERROR
;
389 switch (result_code
) {
390 case WebstoreInstallHelper::Delegate::MANIFEST_ERROR
:
391 install_result
= webstore_install::INVALID_MANIFEST
;
393 case WebstoreInstallHelper::Delegate::ICON_ERROR
:
394 install_result
= webstore_install::ICON_ERROR
;
400 CompleteInstall(install_result
, error_message
);
403 void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
404 const std::string
& id
) {
406 CompleteInstall(webstore_install::SUCCESS
, std::string());
409 void WebstoreStandaloneInstaller::OnExtensionInstallFailure(
410 const std::string
& id
,
411 const std::string
& error
,
412 WebstoreInstaller::FailureReason reason
) {
415 webstore_install::Result install_result
= webstore_install::OTHER_ERROR
;
417 case WebstoreInstaller::FAILURE_REASON_CANCELLED
:
418 install_result
= webstore_install::USER_CANCELLED
;
420 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_FOUND
:
421 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE
:
422 install_result
= webstore_install::MISSING_DEPENDENCIES
;
428 CompleteInstall(install_result
, error
);
431 void WebstoreStandaloneInstaller::ShowInstallUI() {
432 scoped_refptr
<const Extension
> localized_extension
=
433 GetLocalizedExtensionForDisplay();
434 if (!localized_extension
.get()) {
435 CompleteInstall(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
439 install_ui_
= CreateInstallUI();
440 install_ui_
->ConfirmStandaloneInstall(
441 this, localized_extension
.get(), &icon_
, install_prompt_
);
444 void WebstoreStandaloneInstaller::OnWebStoreDataFetcherDone() {
445 // An instance of this class is passed in as a delegate for the
446 // WebstoreInstallHelper, ExtensionInstallPrompt and WebstoreInstaller, and
447 // therefore needs to remain alive until they are done. Clear the webstore
448 // data fetcher to avoid calling Release in AbortInstall while any of these
449 // operations are in progress.
450 webstore_data_fetcher_
.reset();
453 } // namespace extensions