Move Webstore URL concepts to //extensions and out
[chromium-blink-merge.git] / chrome / browser / extensions / webstore_standalone_installer.cc
blob617438d53ad84ec369aabd077a7ad9cfb9bc5df6
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"
25 #include "url/gurl.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,
43 Profile* profile,
44 const Callback& callback)
45 : id_(webstore_item_id),
46 callback_(callback),
47 profile_(profile),
48 install_source_(WebstoreInstaller::INSTALL_SOURCE_INLINE),
49 show_user_count_(true),
50 average_rating_(0.0),
51 rating_count_(0) {
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.
58 AddRef();
60 if (!crx_file::id_util::IdIsValid(id_)) {
61 CompleteInstall(webstore_install::INVALID_ID, kInvalidWebstoreItemId);
62 return;
65 webstore_install::Result result = webstore_install::OTHER_ERROR;
66 std::string error;
67 if (!EnsureUniqueInstall(&result, &error)) {
68 CompleteInstall(result, error);
69 return;
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(
76 this,
77 profile_->GetRequestContext(),
78 GetRequestorURL(),
79 id_));
80 webstore_data_fetcher_->Start();
84 // Private interface implementation.
87 WebstoreStandaloneInstaller::~WebstoreStandaloneInstaller() {
90 void WebstoreStandaloneInstaller::AbortInstall() {
91 callback_.Reset();
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_);
104 DCHECK(tracker);
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;
112 } else {
113 *reason = webstore_install::INSTALL_IN_PROGRESS;
114 *error = kInstallInProgressError;
116 return false;
119 ActiveInstallData install_data(id_);
120 InitInstallData(&install_data);
121 scoped_active_install_.reset(new ScopedActiveInstall(tracker, install_data));
122 return true;
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()) {
137 ShowInstallUI();
138 // Control flow finishes up in InstallUIProceed or InstallUIAbort.
139 } else {
140 InstallUIProceed();
144 scoped_refptr<const Extension>
145 WebstoreStandaloneInstaller::GetLocalizedExtensionForDisplay() {
146 if (!localized_extension_for_display_.get()) {
147 DCHECK(manifest_.get());
148 if (!manifest_.get())
149 return NULL;
151 std::string error;
152 localized_extension_for_display_ =
153 ExtensionInstallPrompt::GetLocalizedExtensionForDisplay(
154 manifest_.get(),
155 Extension::REQUIRE_KEY | Extension::FROM_WEBSTORE,
156 id_,
157 localized_name_,
158 localized_description_,
159 &error);
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(
182 profile_,
183 id_,
184 scoped_ptr<base::DictionaryValue>(manifest_.get()->DeepCopy()),
185 true));
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());
204 return;
207 std::string error;
209 if (!CheckInlineInstallPermitted(*webstore_data, &error)) {
210 CompleteInstall(webstore_install::NOT_PERMITTED, error);
211 return;
214 if (!CheckRequestorPermitted(*webstore_data, &error)) {
215 CompleteInstall(webstore_install::NOT_PERMITTED, error);
216 return;
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);
227 return;
230 // Optional.
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);
238 return;
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);
249 return;
252 // Icon URL is optional.
253 GURL icon_url;
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);
259 return;
261 icon_url = GURL(extension_urls::GetWebstoreLaunchURL()).Resolve(
262 icon_url_string);
263 if (!icon_url.is_valid()) {
264 CompleteInstall(webstore_install::INVALID_WEBSTORE_RESPONSE,
265 kInvalidWebstoreResponseError);
266 return;
270 // Assume ownership of webstore_data.
271 webstore_data_ = webstore_data.Pass();
273 scoped_refptr<WebstoreInstallHelper> helper =
274 new WebstoreInstallHelper(this,
275 id_,
276 manifest,
277 std::string(), // We don't have any icon data.
278 icon_url,
279 profile_->GetRequestContext());
280 // The helper will call us back via OnWebstoreParseSucces or
281 // OnWebstoreParseFailure.
282 helper->Start();
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) {
295 CHECK_EQ(id_, id);
297 if (!CheckRequestorAlive()) {
298 CompleteInstall(webstore_install::ABORTED, std::string());
299 return;
302 manifest_.reset(manifest);
303 icon_ = icon;
305 OnManifestParsed();
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;
316 break;
317 case WebstoreInstallHelper::Delegate::ICON_ERROR:
318 install_result = webstore_install::ICON_ERROR;
319 break;
320 default:
321 break;
324 CompleteInstall(install_result, error_message);
327 void WebstoreStandaloneInstaller::InstallUIProceed() {
328 if (!CheckRequestorAlive()) {
329 CompleteInstall(webstore_install::ABORTED, std::string());
330 return;
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;
342 bool done = true;
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);
358 return;
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.
365 done = false;
366 } else {
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,
373 // enable it.
374 extension_service->EnableExtension(id_);
375 } // else extension is installed and enabled; no work to be done.
377 if (done) {
378 CompleteInstall(install_result, install_message);
379 return;
383 scoped_refptr<WebstoreInstaller> installer = new WebstoreInstaller(
384 profile_,
385 this,
386 GetWebContents(),
387 id_,
388 approval.Pass(),
389 install_source_);
390 installer->Start();
393 void WebstoreStandaloneInstaller::InstallUIAbort(bool user_initiated) {
394 CompleteInstall(webstore_install::USER_CANCELLED, kUserCancelledError);
397 void WebstoreStandaloneInstaller::OnExtensionInstallSuccess(
398 const std::string& id) {
399 CHECK_EQ(id_, 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) {
407 CHECK_EQ(id_, id);
409 webstore_install::Result install_result = webstore_install::OTHER_ERROR;
410 switch (reason) {
411 case WebstoreInstaller::FAILURE_REASON_CANCELLED:
412 install_result = webstore_install::USER_CANCELLED;
413 break;
414 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_FOUND:
415 case WebstoreInstaller::FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE:
416 install_result = webstore_install::MISSING_DEPENDENCIES;
417 break;
418 default:
419 break;
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);
430 return;
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