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_util.h"
12 #include "chrome/browser/extensions/extension_service.h"
13 #include "chrome/browser/extensions/install_tracker.h"
14 #include "chrome/browser/extensions/webstore_data_fetcher.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "components/crx_file/id_util.h"
17 #include "content/public/browser/web_contents.h"
18 #include "extensions/browser/extension_prefs.h"
19 #include "extensions/browser/extension_registry.h"
20 #include "extensions/browser/extension_system.h"
21 #include "extensions/browser/extension_util.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_urls.h"
26 using content::WebContents
;
28 namespace extensions
{
30 const char kInvalidWebstoreItemId
[] = "Invalid Chrome Web Store item ID";
31 const char kWebstoreRequestError
[] =
32 "Could not fetch data from the Chrome Web Store";
33 const char kInvalidWebstoreResponseError
[] = "Invalid Chrome Web Store reponse";
34 const char kInvalidManifestError
[] = "Invalid manifest";
35 const char kUserCancelledError
[] = "User cancelled install";
36 const char kExtensionIsBlacklisted
[] = "Extension is blacklisted";
37 const char kInstallInProgressError
[] = "An install is already in progress";
38 const char kLaunchInProgressError
[] = "A launch is already in progress";
40 WebstoreStandaloneInstaller::WebstoreStandaloneInstaller(
41 const std::string
& webstore_item_id
,
43 const Callback
& callback
)
44 : id_(webstore_item_id
),
47 install_source_(WebstoreInstaller::INSTALL_SOURCE_INLINE
),
48 show_user_count_(true),
53 void WebstoreStandaloneInstaller::BeginInstall() {
54 // Add a ref to keep this alive for WebstoreDataFetcher.
55 // All code paths from here eventually lead to either CompleteInstall or
56 // AbortInstall, which both release this ref.
59 if (!crx_file::id_util::IdIsValid(id_
)) {
60 CompleteInstall(webstore_install::INVALID_ID
, kInvalidWebstoreItemId
);
64 webstore_install::Result result
= webstore_install::OTHER_ERROR
;
66 if (!EnsureUniqueInstall(&result
, &error
)) {
67 CompleteInstall(result
, error
);
71 // Use the requesting page as the referrer both since that is more correct
72 // (it is the page that caused this request to happen) and so that we can
73 // track top sites that trigger inline install requests.
74 webstore_data_fetcher_
.reset(new WebstoreDataFetcher(
76 profile_
->GetRequestContext(),
79 webstore_data_fetcher_
->Start();
83 // Private interface implementation.
86 WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
89 void WebstoreStandaloneInstaller::RunCallback(bool success
,
90 const std::string
& error
,
91 webstore_install::Result result
) {
92 callback_
.Run(success
, error
, result
);
95 void WebstoreStandaloneInstaller::AbortInstall() {
97 // Abort any in-progress fetches.
98 if (webstore_data_fetcher_
) {
99 webstore_data_fetcher_
.reset();
100 scoped_active_install_
.reset();
101 Release(); // Matches the AddRef in BeginInstall.
105 bool WebstoreStandaloneInstaller::EnsureUniqueInstall(
106 webstore_install::Result
* reason
,
107 std::string
* error
) {
108 InstallTracker
* tracker
= InstallTracker::Get(profile_
);
111 const ActiveInstallData
* existing_install_data
=
112 tracker
->GetActiveInstall(id_
);
113 if (existing_install_data
) {
114 if (existing_install_data
->is_ephemeral
) {
115 *reason
= webstore_install::LAUNCH_IN_PROGRESS
;
116 *error
= kLaunchInProgressError
;
118 *reason
= webstore_install::INSTALL_IN_PROGRESS
;
119 *error
= kInstallInProgressError
;
124 ActiveInstallData
install_data(id_
);
125 InitInstallData(&install_data
);
126 scoped_active_install_
.reset(new ScopedActiveInstall(tracker
, install_data
));
130 void WebstoreStandaloneInstaller::CompleteInstall(
131 webstore_install::Result result
,
132 const std::string
& error
) {
133 scoped_active_install_
.reset();
134 if (!callback_
.is_null())
135 callback_
.Run(result
== webstore_install::SUCCESS
, error
, result
);
136 Release(); // Matches the AddRef in BeginInstall.
139 void WebstoreStandaloneInstaller::ProceedWithInstallPrompt() {
140 install_prompt_
= CreateInstallPrompt();
141 if (install_prompt_
.get()) {
143 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
149 scoped_refptr
<const Extension
>
150 WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() {
151 if (!localized_extension_for_display_
.get()) {
152 DCHECK(manifest_
.get());
153 if (!manifest_
.get())
157 localized_extension_for_display_
=
158 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
160 Extension::REQUIRE_KEY
| Extension::FROM_WEBSTORE
,
163 localized_description_
,
166 return localized_extension_for_display_
.get();
169 void WebstoreStandaloneInstaller::InitInstallData(
170 ActiveInstallData
* install_data
) const {
171 // Default implementation sets no properties.
174 void WebstoreStandaloneInstaller::OnManifestParsed() {
175 ProceedWithInstallPrompt();
178 scoped_ptr
<ExtensionInstallPrompt
>
179 WebstoreStandaloneInstaller::CreateInstallUI() {
180 return make_scoped_ptr(new ExtensionInstallPrompt(GetWebContents()));
183 scoped_ptr
<WebstoreInstaller::Approval
>
184 WebstoreStandaloneInstaller::CreateApproval() const {
185 scoped_ptr
<WebstoreInstaller::Approval
> approval(
186 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
189 scoped_ptr
<base::DictionaryValue
>(manifest_
.get()->DeepCopy()),
191 approval
->skip_post_install_ui
= !ShouldShowPostInstallUI();
192 approval
->use_app_installed_bubble
= ShouldShowAppInstalledBubble();
193 approval
->installing_icon
= gfx::ImageSkia::CreateFrom1xBitmap(icon_
);
194 return approval
.Pass();
197 void WebstoreStandaloneInstaller::InstallUIProceed() {
198 if (!CheckRequestorAlive()) {
199 CompleteInstall(webstore_install::ABORTED
, std::string());
203 scoped_ptr
<WebstoreInstaller::Approval
> approval
= CreateApproval();
205 ExtensionService
* extension_service
=
206 ExtensionSystem::Get(profile_
)->extension_service();
207 const Extension
* installed_extension
=
208 extension_service
->GetExtensionById(id_
, true /* include disabled */);
209 if (installed_extension
) {
210 std::string install_message
;
211 webstore_install::Result install_result
= webstore_install::SUCCESS
;
214 if (ExtensionPrefs::Get(profile_
)->IsExtensionBlacklisted(id_
)) {
215 // Don't install a blacklisted extension.
216 install_result
= webstore_install::BLACKLISTED
;
217 install_message
= kExtensionIsBlacklisted
;
218 } else if (util::IsEphemeralApp(installed_extension
->id(), profile_
) &&
219 !approval
->is_ephemeral
) {
220 // If the target extension has already been installed ephemerally and is
221 // up to date, it can be promoted to a regular installed extension and
222 // downloading from the Web Store is not necessary.
223 scoped_refptr
<const Extension
> extension_to_install
=
224 GetLocalizedExtensionForDisplay();
225 if (!extension_to_install
.get()) {
226 CompleteInstall(webstore_install::INVALID_MANIFEST
,
227 kInvalidManifestError
);
231 if (installed_extension
->version()->CompareTo(
232 *extension_to_install
->version()) < 0) {
233 // If the existing extension is out of date, proceed with the install
234 // to update the extension.
237 install_ui::ShowPostInstallUIForApproval(
238 profile_
, *approval
, installed_extension
);
239 extension_service
->PromoteEphemeralApp(installed_extension
, false);
241 } else if (!extension_service
->IsExtensionEnabled(id_
)) {
242 // If the extension is installed but disabled, and not blacklisted,
244 extension_service
->EnableExtension(id_
);
245 } // else extension is installed and enabled; no work to be done.
248 CompleteInstall(install_result
, install_message
);
253 scoped_refptr
<WebstoreInstaller
> installer
= new WebstoreInstaller(
263 void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated
) {
264 CompleteInstall(webstore_install::USER_CANCELLED
, kUserCancelledError
);
267 void WebstoreStandaloneInstaller::OnWebstoreRequestFailure() {
268 OnWebStoreDataFetcherDone();
269 CompleteInstall(webstore_install::WEBSTORE_REQUEST_ERROR
,
270 kWebstoreRequestError
);
273 void WebstoreStandaloneInstaller::OnWebstoreResponseParseSuccess(
274 scoped_ptr
<base::DictionaryValue
> webstore_data
) {
275 OnWebStoreDataFetcherDone();
277 if (!CheckRequestorAlive()) {
278 CompleteInstall(webstore_install::ABORTED
, std::string());
284 if (!CheckInlineInstallPermitted(*webstore_data
, &error
)) {
285 CompleteInstall(webstore_install::NOT_PERMITTED
, error
);
289 if (!CheckRequestorPermitted(*webstore_data
, &error
)) {
290 CompleteInstall(webstore_install::NOT_PERMITTED
, error
);
294 // Manifest, number of users, average rating and rating count are required.
295 std::string manifest
;
296 if (!webstore_data
->GetString(kManifestKey
, &manifest
) ||
297 !webstore_data
->GetString(kUsersKey
, &localized_user_count_
) ||
298 !webstore_data
->GetDouble(kAverageRatingKey
, &average_rating_
) ||
299 !webstore_data
->GetInteger(kRatingCountKey
, &rating_count_
)) {
300 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
301 kInvalidWebstoreResponseError
);
306 show_user_count_
= true;
307 webstore_data
->GetBoolean(kShowUserCountKey
, &show_user_count_
);
309 if (average_rating_
< ExtensionInstallPrompt::kMinExtensionRating
||
310 average_rating_
> ExtensionInstallPrompt::kMaxExtensionRating
) {
311 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
312 kInvalidWebstoreResponseError
);
316 // Localized name and description are optional.
317 if ((webstore_data
->HasKey(kLocalizedNameKey
) &&
318 !webstore_data
->GetString(kLocalizedNameKey
, &localized_name_
)) ||
319 (webstore_data
->HasKey(kLocalizedDescriptionKey
) &&
320 !webstore_data
->GetString(
321 kLocalizedDescriptionKey
, &localized_description_
))) {
322 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
323 kInvalidWebstoreResponseError
);
327 // Icon URL is optional.
329 if (webstore_data
->HasKey(kIconUrlKey
)) {
330 std::string icon_url_string
;
331 if (!webstore_data
->GetString(kIconUrlKey
, &icon_url_string
)) {
332 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
333 kInvalidWebstoreResponseError
);
336 icon_url
= GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
338 if (!icon_url
.is_valid()) {
339 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
,
340 kInvalidWebstoreResponseError
);
345 // Assume ownership of webstore_data.
346 webstore_data_
= webstore_data
.Pass();
348 scoped_refptr
<WebstoreInstallHelper
> helper
=
349 new WebstoreInstallHelper(this,
353 profile_
->GetRequestContext());
354 // The helper will call us back via OnWebstoreParseSucces or
355 // OnWebstoreParseFailure.
359 void WebstoreStandaloneInstaller::OnWebstoreResponseParseFailure(
360 const std::string
& error
) {
361 OnWebStoreDataFetcherDone();
362 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE
, error
);
365 void WebstoreStandaloneInstaller::OnWebstoreParseSuccess(
366 const std::string
& id
,
367 const SkBitmap
& icon
,
368 base::DictionaryValue
* manifest
) {
371 if (!CheckRequestorAlive()) {
372 CompleteInstall(webstore_install::ABORTED
, std::string());
376 manifest_
.reset(manifest
);
382 void WebstoreStandaloneInstaller::OnWebstoreParseFailure(
383 const std::string
& id
,
384 InstallHelperResultCode result_code
,
385 const std::string
& error_message
) {
386 webstore_install::Result install_result
= webstore_install::OTHER_ERROR
;
387 switch (result_code
) {
388 case WebstoreInstallHelper::Delegate::MANIFEST_ERROR
:
389 install_result
= webstore_install::INVALID_MANIFEST
;
391 case WebstoreInstallHelper::Delegate::ICON_ERROR
:
392 install_result
= webstore_install::ICON_ERROR
;
398 CompleteInstall(install_result
, error_message
);
401 void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
402 const std::string
& id
) {
404 CompleteInstall(webstore_install::SUCCESS
, std::string());
407 void WebstoreStandaloneInstaller::OnExtensionInstallFailure(
408 const std::string
& id
,
409 const std::string
& error
,
410 WebstoreInstaller::FailureReason reason
) {
413 webstore_install::Result install_result
= webstore_install::OTHER_ERROR
;
415 case WebstoreInstaller::FAILURE_REASON_CANCELLED
:
416 install_result
= webstore_install::USER_CANCELLED
;
418 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_FOUND
:
419 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE
:
420 install_result
= webstore_install::MISSING_DEPENDENCIES
;
426 CompleteInstall(install_result
, error
);
429 void WebstoreStandaloneInstaller::ShowInstallUI() {
430 scoped_refptr
<const Extension
> localized_extension
=
431 GetLocalizedExtensionForDisplay();
432 if (!localized_extension
.get()) {
433 CompleteInstall(webstore_install::INVALID_MANIFEST
, kInvalidManifestError
);
437 install_ui_
= CreateInstallUI();
438 install_ui_
->ConfirmStandaloneInstall(
439 this, localized_extension
.get(), &icon_
, install_prompt_
);
442 void WebstoreStandaloneInstaller::OnWebStoreDataFetcherDone() {
443 // An instance of this class is passed in as a delegate for the
444 // WebstoreInstallHelper, ExtensionInstallPrompt and WebstoreInstaller, and
445 // therefore needs to remain alive until they are done. Clear the webstore
446 // data fetcher to avoid calling Release in AbortInstall while any of these
447 // operations are in progress.
448 webstore_data_fetcher_
.reset();
451 } // namespace extensions