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::AbortInstall() {
92 // Abort any in-progress fetches.
93 if (webstore_data_fetcher_
) {
94 webstore_data_fetcher_
.reset();
95 scoped_active_install_
.reset();
96 Release(); // Matches the AddRef in BeginInstall.
100 bool WebstoreStandaloneInstaller::EnsureUniqueInstall(
101 webstore_install::Result
* reason
,
102 std::string
* error
) {
103 InstallTracker
* tracker
= InstallTracker::Get(profile_
);
106 const ActiveInstallData
* existing_install_data
=
107 tracker
->GetActiveInstall(id_
);
108 if (existing_install_data
) {
109 if (existing_install_data
->is_ephemeral
) {
110 *reason
= webstore_install::LAUNCH_IN_PROGRESS
;
111 *error
= kLaunchInProgressError
;
113 *reason
= webstore_install::INSTALL_IN_PROGRESS
;
114 *error
= kInstallInProgressError
;
119 ActiveInstallData
install_data(id_
);
120 InitInstallData(&install_data
);
121 scoped_active_install_
.reset(new ScopedActiveInstall(tracker
, install_data
));
125 void WebstoreStandaloneInstaller::CompleteInstall(
126 webstore_install::Result result
,
127 const std::string
& error
) {
128 scoped_active_install_
.reset();
129 if (!callback_
.is_null())
130 callback_
.Run(result
== webstore_install::SUCCESS
, error
, result
);
131 Release(); // Matches the AddRef in BeginInstall.
134 void WebstoreStandaloneInstaller::ProceedWithInstallPrompt() {
135 install_prompt_
= CreateInstallPrompt();
136 if (install_prompt_
.get()) {
138 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
144 scoped_refptr
<const Extension
>
145 WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() {
146 if (!localized_extension_for_display_
.get()) {
147 DCHECK(manifest_
.get());
148 if (!manifest_
.get())
152 localized_extension_for_display_
=
153 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
155 Extension::REQUIRE_KEY
| Extension::FROM_WEBSTORE
,
158 localized_description_
,
161 return localized_extension_for_display_
.get();
164 void WebstoreStandaloneInstaller::InitInstallData(
165 ActiveInstallData
* install_data
) const {
166 // Default implementation sets no properties.
169 void WebstoreStandaloneInstaller::OnManifestParsed() {
170 ProceedWithInstallPrompt();
173 scoped_ptr
<ExtensionInstallPrompt
>
174 WebstoreStandaloneInstaller::CreateInstallUI() {
175 return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents()));
178 scoped_ptr
<WebstoreInstaller::Approval
>
179 WebstoreStandaloneInstaller::CreateApproval() const {
180 scoped_ptr
<WebstoreInstaller::Approval
> approval(
181 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
184 scoped_ptr
<base::DictionaryValue
>(manifest_
.get()->DeepCopy()),
186 approval
->skip_post_install_ui
= !ShouldShowPostInstallUI();
187 approval
->use_app_installed_bubble
= ShouldShowAppInstalledBubble();
188 approval
->installing_icon
= gfx::ImageSkia::CreateFrom1xBitmap(icon_
);
189 return approval
.Pass();
192 void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
193 OnWebStoreDataFetcherDone();
194 CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR
,
195 kWebstoreRequestError
);
198 void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
199 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
200 OnWebStoreDataFetcherDone();
202 if (!CheckRequestorAlive()) {
203 CompleteInstall(webstore_install::ABORTED
, std::string());
209 if (!CheckInlineInstallPermitted(*webstore_data
, &error
)) {
210 CompleteInstall(webstore_install::NOT_PERMITTED
, error
);
214 if (!CheckRequestorPermitted(*webstore_data
, &error
)) {
215 CompleteInstall(webstore_install::NOT_PERMITTED
, error
);
219 // Manifest, number of users, average rating and rating count are required.
220 std::string manifest
;
221 if (!webstore_data
->GetString(kManifestKey
, &manifest
) ||
222 !webstore_data
->GetString(kUsersKey
, &localized_user_count_
) ||
223 !webstore_data
->GetDouble(kAverageRatingKey
, &average_rating_
) ||
224 !webstore_data
->GetInteger(kRatingCountKey
, &rating_count_
)) {
225 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
226 kInvalidWebstoreResponseError
);
231 show_user_count_
= true;
232 webstore_data
->GetBoolean(kShowUserCountKey
, &show_user_count_
);
234 if (average_rating_
< ExtensionInstallPrompt::kMinExtensionRating
||
235 average_rating_
> ExtensionInstallPrompt::kMaxExtensionRating
) {
236 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
237 kInvalidWebstoreResponseError
);
241 // Localized name and description are optional.
242 if ((webstore_data
->HasKey(kLocalizedNameKey
) &&
243 !webstore_data
->GetString(kLocalizedNameKey
, &localized_name_
)) ||
244 (webstore_data
->HasKey(kLocalizedDescriptionKey
) &&
245 !webstore_data
->GetString(
246 kLocalizedDescriptionKey
, &localized_description_
))) {
247 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
248 kInvalidWebstoreResponseError
);
252 // Icon URL is optional.
254 if (webstore_data
->HasKey(kIconUrlKey
)) {
255 std::string icon_url_string
;
256 if (!webstore_data
->GetString(kIconUrlKey
, &icon_url_string
)) {
257 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
258 kInvalidWebstoreResponseError
);
261 icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
263 if (!icon_url
.is_valid()) {
264 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
265 kInvalidWebstoreResponseError
);
270 // Assume ownership of webstore_data.
271 webstore_data_
= webstore_data
.Pass();
273 scoped_refptr
<WebstoreInstallHelper
> helper
=
274 new WebstoreInstallHelper(this,
277 std::string(), // We don't have any icon data.
279 profile_
->GetRequestContext());
280 // The helper will call us back via OnWebstoreParseSucces or
281 // OnWebstoreParseFailure.
285 void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
286 const std::string
& error
) {
287 OnWebStoreDataFetcherDone();
288 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
, error
);
291 void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
292 const std::string
& id
,
293 const SkBitmap
& icon
,
294 base::DictionaryValue
* manifest
) {
297 if (!CheckRequestorAlive()) {
298 CompleteInstall(webstore_install::ABORTED
, std::string());
302 manifest_
.reset(manifest
);
308 void WebstoreStandaloneInstaller::OnWebstoreParseFailure(
309 const std::string
& id
,
310 InstallHelperResultCode result_code
,
311 const std::string
& error_message
) {
312 webstore_install::Result install_result
= webstore_install::OTHER_ERROR
;
313 switch (result_code
) {
314 case WebstoreInstallHelper::Delegate::MANIFEST_ERROR
:
315 install_result
= webstore_install::INVALID_MANIFEST
;
317 case WebstoreInstallHelper::Delegate::ICON_ERROR
:
318 install_result
= webstore_install::ICON_ERROR
;
324 CompleteInstall(install_result
, error_message
);
327 void WebstoreStandaloneInstaller::InstallUIProceed() {
328 if (!CheckRequestorAlive()) {
329 CompleteInstall(webstore_install::ABORTED
, std::string());
333 scoped_ptr
<WebstoreInstaller::Approval
> approval
= CreateApproval();
335 ExtensionService
* extension_service
=
336 ExtensionSystem::Get(profile_
)->extension_service();
337 const Extension
* installed_extension
=
338 extension_service
->GetExtensionById(id_
, true /* include disabled */);
339 if (installed_extension
) {
340 std::string install_message
;
341 webstore_install::Result install_result
= webstore_install::SUCCESS
;
344 if (ExtensionPrefs::Get(profile_
)->IsExtensionBlacklisted(id_
)) {
345 // Don't install a blacklisted extension.
346 install_result
= webstore_install::BLACKLISTED
;
347 install_message
= kExtensionIsBlacklisted
;
348 } else if (util::IsEphemeralApp(installed_extension
->id(), profile_
) &&
349 !approval
->is_ephemeral
) {
350 // If the target extension has already been installed ephemerally and is
351 // up to date, it can be promoted to a regular installed extension and
352 // downloading from the Web Store is not necessary.
353 scoped_refptr
<const Extension
> extension_to_install
=
354 GetLocalizedExtensionForDisplay();
355 if (!extension_to_install
.get()) {
356 CompleteInstall(webstore_install::INVALID_MANIFEST
,
357 kInvalidManifestError
);
361 if (installed_extension
->version()->CompareTo(
362 *extension_to_install
->version()) < 0) {
363 // If the existing extension is out of date, proceed with the install
364 // to update the extension.
367 install_ui::ShowPostInstallUIForApproval(
368 profile_
, *approval
, installed_extension
);
369 extension_service
->PromoteEphemeralApp(installed_extension
, false);
371 } else if (!extension_service
->IsExtensionEnabled(id_
)) {
372 // If the extension is installed but disabled, and not blacklisted,
374 extension_service
->EnableExtension(id_
);
375 } // else extension is installed and enabled; no work to be done.
378 CompleteInstall(install_result
, install_message
);
383 scoped_refptr
<WebstoreInstaller
> installer
= new WebstoreInstaller(
393 void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated
) {
394 CompleteInstall(webstore_install::USER_CANCELLED
, kUserCancelledError
);
397 void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
398 const std::string
& id
) {
400 CompleteInstall(webstore_install::SUCCESS
, std::string());
403 void WebstoreStandaloneInstaller::OnExtensionInstallFailure(
404 const std::string
& id
,
405 const std::string
& error
,
406 WebstoreInstaller::FailureReason reason
) {
409 webstore_install::Result install_result
= webstore_install::OTHER_ERROR
;
411 case WebstoreInstaller::FAILURE_REASON_CANCELLED
:
412 install_result
= webstore_install::USER_CANCELLED
;
414 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_FOUND
:
415 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE
:
416 install_result
= webstore_install::MISSING_DEPENDENCIES
;
422 CompleteInstall(install_result
, error
);
425 void WebstoreStandaloneInstaller::ShowInstallUI() {
426 scoped_refptr
<const Extension
> localized_extension
=
427 GetLocalizedExtensionForDisplay();
428 if (!localized_extension
.get()) {
429 CompleteInstall(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
433 install_ui_
= CreateInstallUI();
434 install_ui_
->ConfirmStandaloneInstall(
435 this, localized_extension
.get(), &icon_
, install_prompt_
);
438 void WebstoreStandaloneInstaller::OnWebStoreDataFetcherDone() {
439 // An instance of this class is passed in as a delegate for the
440 // WebstoreInstallHelper, ExtensionInstallPrompt and WebstoreInstaller, and
441 // therefore needs to remain alive until they are done. Clear the webstore
442 // data fetcher to avoid calling Release in AbortInstall while any of these
443 // operations are in progress.
444 webstore_data_fetcher_
.reset();
447 } // namespace extensions