NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / extensions / webstore_installer.cc
blobf9caccad4e009a2ddf73fafdcc52cf18ca92a8eb
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_installer.h"
7 #include <vector>
9 #include "base/basictypes.h"
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/file_util.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/histogram.h"
15 #include "base/metrics/sparse_histogram.h"
16 #include "base/path_service.h"
17 #include "base/rand_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "base/strings/stringprintf.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "chrome/browser/chrome_notification_types.h"
23 #include "chrome/browser/download/download_crx_util.h"
24 #include "chrome/browser/download/download_prefs.h"
25 #include "chrome/browser/download/download_stats.h"
26 #include "chrome/browser/extensions/crx_installer.h"
27 #include "chrome/browser/extensions/install_tracker.h"
28 #include "chrome/browser/extensions/install_tracker_factory.h"
29 #include "chrome/browser/extensions/install_verifier.h"
30 #include "chrome/browser/omaha_query_params/omaha_query_params.h"
31 #include "chrome/browser/profiles/profile.h"
32 #include "chrome/browser/ui/browser_list.h"
33 #include "chrome/browser/ui/tabs/tab_strip_model.h"
34 #include "chrome/common/chrome_paths.h"
35 #include "chrome/common/chrome_switches.h"
36 #include "chrome/common/extensions/extension_constants.h"
37 #include "content/public/browser/browser_thread.h"
38 #include "content/public/browser/download_manager.h"
39 #include "content/public/browser/download_save_info.h"
40 #include "content/public/browser/download_url_parameters.h"
41 #include "content/public/browser/navigation_controller.h"
42 #include "content/public/browser/navigation_entry.h"
43 #include "content/public/browser/notification_details.h"
44 #include "content/public/browser/notification_service.h"
45 #include "content/public/browser/notification_source.h"
46 #include "content/public/browser/render_process_host.h"
47 #include "content/public/browser/render_view_host.h"
48 #include "content/public/browser/web_contents.h"
49 #include "extensions/browser/extension_system.h"
50 #include "extensions/common/extension.h"
51 #include "extensions/common/manifest_constants.h"
52 #include "extensions/common/manifest_handlers/shared_module_info.h"
53 #include "net/base/escape.h"
54 #include "url/gurl.h"
56 #if defined(OS_CHROMEOS)
57 #include "chrome/browser/chromeos/drive/file_system_util.h"
58 #endif
60 using chrome::OmahaQueryParams;
61 using content::BrowserContext;
62 using content::BrowserThread;
63 using content::DownloadItem;
64 using content::DownloadManager;
65 using content::NavigationController;
66 using content::DownloadUrlParameters;
68 namespace {
70 // Key used to attach the Approval to the DownloadItem.
71 const char kApprovalKey[] = "extensions.webstore_installer";
73 const char kInvalidIdError[] = "Invalid id";
74 const char kDownloadDirectoryError[] = "Could not create download directory";
75 const char kDownloadCanceledError[] = "Download canceled";
76 const char kInstallCanceledError[] = "Install canceled";
77 const char kDownloadInterruptedError[] = "Download interrupted";
78 const char kInvalidDownloadError[] =
79 "Download was not a valid extension or user script";
80 const char kDependencyNotFoundError[] = "Dependency not found";
81 const char kDependencyNotSharedModuleError[] =
82 "Dependency is not shared module";
83 const char kInlineInstallSource[] = "inline";
84 const char kDefaultInstallSource[] = "ondemand";
85 const char kAppLauncherInstallSource[] = "applauncher";
87 // Folder for downloading crx files from the webstore. This is used so that the
88 // crx files don't go via the usual downloads folder.
89 const base::FilePath::CharType kWebstoreDownloadFolder[] =
90 FILE_PATH_LITERAL("Webstore Downloads");
92 base::FilePath* g_download_directory_for_tests = NULL;
94 // Must be executed on the FILE thread.
95 void GetDownloadFilePath(
96 const base::FilePath& download_directory,
97 const std::string& id,
98 const base::Callback<void(const base::FilePath&)>& callback) {
99 // Ensure the download directory exists. TODO(asargent) - make this use
100 // common code from the downloads system.
101 if (!base::DirectoryExists(download_directory)) {
102 if (!base::CreateDirectory(download_directory)) {
103 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
104 base::Bind(callback, base::FilePath()));
105 return;
109 // This is to help avoid a race condition between when we generate this
110 // filename and when the download starts writing to it (think concurrently
111 // running sharded browser tests installing the same test file, for
112 // instance).
113 std::string random_number =
114 base::Uint64ToString(base::RandGenerator(kuint16max));
116 base::FilePath file =
117 download_directory.AppendASCII(id + "_" + random_number + ".crx");
119 int uniquifier =
120 file_util::GetUniquePathNumber(file, base::FilePath::StringType());
121 if (uniquifier > 0) {
122 file = file.InsertBeforeExtensionASCII(
123 base::StringPrintf(" (%d)", uniquifier));
126 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
127 base::Bind(callback, file));
130 bool UseSeparateWebstoreDownloadDirectory() {
131 const char kFieldTrial[] = "WebstoreDownloadDirectory";
132 const char kSeparateDirectoryUnderUDD[] = "SeparateDirectoryUnderUDD";
134 std::string field_trial_group =
135 base::FieldTrialList::FindFullName(kFieldTrial);
136 return field_trial_group == kSeparateDirectoryUnderUDD;
139 } // namespace
141 namespace extensions {
143 // static
144 GURL WebstoreInstaller::GetWebstoreInstallURL(
145 const std::string& extension_id,
146 InstallSource source) {
147 std::string install_source;
148 switch (source) {
149 case INSTALL_SOURCE_INLINE:
150 install_source = kInlineInstallSource;
151 break;
152 case INSTALL_SOURCE_APP_LAUNCHER:
153 install_source = kAppLauncherInstallSource;
154 break;
155 case INSTALL_SOURCE_OTHER:
156 install_source = kDefaultInstallSource;
159 CommandLine* cmd_line = CommandLine::ForCurrentProcess();
160 if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
161 std::string download_url =
162 cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
163 return GURL(base::StringPrintf(download_url.c_str(),
164 extension_id.c_str()));
166 std::vector<std::string> params;
167 params.push_back("id=" + extension_id);
168 if (!install_source.empty())
169 params.push_back("installsource=" + install_source);
170 params.push_back("uc");
171 std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
173 GURL url(url_string + "?response=redirect&" +
174 OmahaQueryParams::Get(OmahaQueryParams::CRX) + "&x=" +
175 net::EscapeQueryParamValue(JoinString(params, '&'), true));
176 DCHECK(url.is_valid());
178 return url;
181 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
182 const std::string& id,
183 content::DownloadItem* item) {
186 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
187 const std::string& id,
188 content::DownloadItem* item) {
191 WebstoreInstaller::Approval::Approval()
192 : profile(NULL),
193 use_app_installed_bubble(false),
194 skip_post_install_ui(false),
195 skip_install_dialog(false),
196 enable_launcher(false),
197 manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT),
198 is_ephemeral(false) {
201 scoped_ptr<WebstoreInstaller::Approval>
202 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
203 scoped_ptr<Approval> result(new Approval());
204 result->profile = profile;
205 return result.Pass();
208 scoped_ptr<WebstoreInstaller::Approval>
209 WebstoreInstaller::Approval::CreateForSharedModule(Profile* profile) {
210 scoped_ptr<Approval> result(new Approval());
211 result->profile = profile;
212 result->skip_install_dialog = true;
213 result->manifest_check_level = MANIFEST_CHECK_LEVEL_NONE;
214 return result.Pass();
217 scoped_ptr<WebstoreInstaller::Approval>
218 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
219 Profile* profile,
220 const std::string& extension_id,
221 scoped_ptr<base::DictionaryValue> parsed_manifest,
222 bool strict_manifest_check) {
223 scoped_ptr<Approval> result(new Approval());
224 result->extension_id = extension_id;
225 result->profile = profile;
226 result->manifest = scoped_ptr<Manifest>(
227 new Manifest(Manifest::INVALID_LOCATION,
228 scoped_ptr<base::DictionaryValue>(
229 parsed_manifest->DeepCopy())));
230 result->skip_install_dialog = true;
231 result->manifest_check_level = strict_manifest_check ?
232 MANIFEST_CHECK_LEVEL_STRICT : MANIFEST_CHECK_LEVEL_LOOSE;
233 return result.Pass();
236 WebstoreInstaller::Approval::~Approval() {}
238 const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
239 const DownloadItem& download) {
240 return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
243 WebstoreInstaller::WebstoreInstaller(Profile* profile,
244 Delegate* delegate,
245 content::WebContents* web_contents,
246 const std::string& id,
247 scoped_ptr<Approval> approval,
248 InstallSource source)
249 : content::WebContentsObserver(web_contents),
250 profile_(profile),
251 delegate_(delegate),
252 id_(id),
253 install_source_(source),
254 download_item_(NULL),
255 approval_(approval.release()),
256 total_modules_(0),
257 download_started_(false) {
258 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
259 DCHECK(web_contents);
261 registrar_.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE,
262 content::NotificationService::AllSources());
263 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
264 content::Source<Profile>(profile->GetOriginalProfile()));
265 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
266 content::Source<CrxInstaller>(NULL));
269 void WebstoreInstaller::Start() {
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
271 AddRef(); // Balanced in ReportSuccess and ReportFailure.
273 if (!Extension::IdIsValid(id_)) {
274 ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
275 return;
278 ExtensionService* extension_service =
279 ExtensionSystem::Get(profile_)->extension_service();
280 if (approval_.get() && approval_->dummy_extension) {
281 ExtensionService::ImportStatus status =
282 extension_service->CheckImports(approval_->dummy_extension,
283 &pending_modules_, &pending_modules_);
284 // For this case, it is because some imports are not shared modules.
285 if (status == ExtensionService::IMPORT_STATUS_UNRECOVERABLE) {
286 ReportFailure(kDependencyNotSharedModuleError,
287 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
288 return;
292 // Add the extension main module into the list.
293 SharedModuleInfo::ImportInfo info;
294 info.extension_id = id_;
295 pending_modules_.push_back(info);
297 total_modules_ = pending_modules_.size();
299 std::set<std::string> ids;
300 std::list<SharedModuleInfo::ImportInfo>::const_iterator i;
301 for (i = pending_modules_.begin(); i != pending_modules_.end(); ++i) {
302 ids.insert(i->extension_id);
304 ExtensionSystem::Get(profile_)->install_verifier()->AddProvisional(ids);
306 // TODO(crbug.com/305343): Query manifest of dependencises before
307 // downloading & installing those dependencies.
308 DownloadNextPendingModule();
310 std::string name;
311 if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) {
312 NOTREACHED();
314 extensions::InstallTracker* tracker =
315 extensions::InstallTrackerFactory::GetForProfile(profile_);
316 extensions::InstallObserver::ExtensionInstallParams params(
317 id_,
318 name,
319 approval_->installing_icon,
320 approval_->manifest->is_app(),
321 approval_->manifest->is_platform_app());
322 params.is_ephemeral = approval_->is_ephemeral;
323 tracker->OnBeginExtensionInstall(params);
326 void WebstoreInstaller::Observe(int type,
327 const content::NotificationSource& source,
328 const content::NotificationDetails& details) {
329 switch (type) {
330 case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
331 const Extension* extension =
332 content::Details<const Extension>(details).ptr();
333 CrxInstaller* installer = content::Source<CrxInstaller>(source).ptr();
334 if (extension == NULL && download_item_ != NULL &&
335 installer->download_url() == download_item_->GetURL() &&
336 installer->profile()->IsSameProfile(profile_)) {
337 ReportFailure(kInstallCanceledError, FAILURE_REASON_CANCELLED);
339 break;
342 case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
343 CHECK(profile_->IsSameProfile(content::Source<Profile>(source).ptr()));
344 const Extension* extension =
345 content::Details<const InstalledExtensionInfo>(details)->extension;
346 if (pending_modules_.empty())
347 return;
348 SharedModuleInfo::ImportInfo info = pending_modules_.front();
349 if (extension->id() != info.extension_id)
350 return;
351 pending_modules_.pop_front();
353 if (pending_modules_.empty()) {
354 CHECK_EQ(extension->id(), id_);
355 ReportSuccess();
356 } else {
357 const Version version_required(info.minimum_version);
358 if (version_required.IsValid() &&
359 extension->version()->CompareTo(version_required) < 0) {
360 // It should not happen, CrxInstaller will make sure the version is
361 // equal or newer than version_required.
362 ReportFailure(kDependencyNotFoundError,
363 FAILURE_REASON_DEPENDENCY_NOT_FOUND);
364 } else if (!SharedModuleInfo::IsSharedModule(extension)) {
365 // It should not happen, CrxInstaller will make sure it is a shared
366 // module.
367 ReportFailure(kDependencyNotSharedModuleError,
368 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
369 } else {
370 DownloadNextPendingModule();
373 break;
376 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
377 CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
378 CHECK(crx_installer);
379 if (!profile_->IsSameProfile(crx_installer->profile()))
380 return;
382 // TODO(rdevlin.cronin): Continue removing std::string errors and
383 // replacing with base::string16. See crbug.com/71980.
384 const base::string16* error =
385 content::Details<const base::string16>(details).ptr();
386 const std::string utf8_error = base::UTF16ToUTF8(*error);
387 if (download_url_ == crx_installer->original_download_url())
388 ReportFailure(utf8_error, FAILURE_REASON_OTHER);
389 break;
392 default:
393 NOTREACHED();
397 void WebstoreInstaller::InvalidateDelegate() {
398 delegate_ = NULL;
401 void WebstoreInstaller::SetDownloadDirectoryForTests(
402 base::FilePath* directory) {
403 g_download_directory_for_tests = directory;
406 WebstoreInstaller::~WebstoreInstaller() {
407 if (download_item_) {
408 download_item_->RemoveObserver(this);
409 download_item_ = NULL;
413 void WebstoreInstaller::OnDownloadStarted(
414 DownloadItem* item,
415 content::DownloadInterruptReason interrupt_reason) {
416 if (!item) {
417 DCHECK_NE(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
418 ReportFailure(content::DownloadInterruptReasonToString(interrupt_reason),
419 FAILURE_REASON_OTHER);
420 return;
423 DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
424 DCHECK(!pending_modules_.empty());
425 download_item_ = item;
426 download_item_->AddObserver(this);
427 if (pending_modules_.size() > 1) {
428 // We are downloading a shared module. We need create an approval for it.
429 scoped_ptr<Approval> approval = Approval::CreateForSharedModule(profile_);
430 const SharedModuleInfo::ImportInfo& info = pending_modules_.front();
431 approval->extension_id = info.extension_id;
432 const Version version_required(info.minimum_version);
434 if (version_required.IsValid()) {
435 approval->minimum_version.reset(
436 new Version(version_required));
438 download_item_->SetUserData(kApprovalKey, approval.release());
439 } else {
440 // It is for the main module of the extension. We should use the provided
441 // |approval_|.
442 if (approval_)
443 download_item_->SetUserData(kApprovalKey, approval_.release());
446 if (!download_started_) {
447 if (delegate_)
448 delegate_->OnExtensionDownloadStarted(id_, download_item_);
449 download_started_ = true;
453 void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
454 CHECK_EQ(download_item_, download);
456 switch (download->GetState()) {
457 case DownloadItem::CANCELLED:
458 ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
459 break;
460 case DownloadItem::INTERRUPTED:
461 RecordInterrupt(download);
462 ReportFailure(kDownloadInterruptedError, FAILURE_REASON_OTHER);
463 break;
464 case DownloadItem::COMPLETE:
465 // Wait for other notifications if the download is really an extension.
466 if (!download_crx_util::IsExtensionDownload(*download)) {
467 ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
468 } else if (pending_modules_.empty()) {
469 // The download is the last module - the extension main module.
470 if (delegate_)
471 delegate_->OnExtensionDownloadProgress(id_, download);
472 extensions::InstallTracker* tracker =
473 extensions::InstallTrackerFactory::GetForProfile(profile_);
474 tracker->OnDownloadProgress(id_, 100);
476 break;
477 case DownloadItem::IN_PROGRESS: {
478 if (delegate_ && pending_modules_.size() == 1) {
479 // Only report download progress for the main module to |delegrate_|.
480 delegate_->OnExtensionDownloadProgress(id_, download);
482 int percent = download->PercentComplete();
483 // Only report progress if precent is more than 0
484 if (percent >= 0) {
485 int finished_modules = total_modules_ - pending_modules_.size();
486 percent = (percent + finished_modules * 100) / total_modules_;
487 extensions::InstallTracker* tracker =
488 extensions::InstallTrackerFactory::GetForProfile(profile_);
489 tracker->OnDownloadProgress(id_, percent);
491 break;
493 default:
494 // Continue listening if the download is not in one of the above states.
495 break;
499 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
500 CHECK_EQ(download_item_, download);
501 download_item_->RemoveObserver(this);
502 download_item_ = NULL;
505 void WebstoreInstaller::DownloadNextPendingModule() {
506 CHECK(!pending_modules_.empty());
507 if (pending_modules_.size() == 1) {
508 DCHECK_EQ(id_, pending_modules_.front().extension_id);
509 DownloadCrx(id_, install_source_);
510 } else {
511 DownloadCrx(pending_modules_.front().extension_id, INSTALL_SOURCE_OTHER);
515 void WebstoreInstaller::DownloadCrx(
516 const std::string& extension_id,
517 InstallSource source) {
518 download_url_ = GetWebstoreInstallURL(extension_id, source);
520 base::FilePath download_path;
521 if (UseSeparateWebstoreDownloadDirectory()) {
522 base::FilePath user_data_dir;
523 PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
524 download_path = user_data_dir.Append(kWebstoreDownloadFolder);
525 } else {
526 download_path = DownloadPrefs::FromDownloadManager(
527 BrowserContext::GetDownloadManager(profile_))->DownloadPath();
530 base::FilePath download_directory(g_download_directory_for_tests ?
531 *g_download_directory_for_tests : download_path);
533 #if defined(OS_CHROMEOS)
534 // Do not use drive for extension downloads.
535 if (drive::util::IsUnderDriveMountPoint(download_directory)) {
536 download_directory = DownloadPrefs::FromBrowserContext(
537 profile_)->GetDefaultDownloadDirectoryForProfile();
539 #endif
541 BrowserThread::PostTask(
542 BrowserThread::FILE, FROM_HERE,
543 base::Bind(&GetDownloadFilePath, download_directory, id_,
544 base::Bind(&WebstoreInstaller::StartDownload, this)));
547 // http://crbug.com/165634
548 // http://crbug.com/126013
549 // The current working theory is that one of the many pointers dereferenced in
550 // here is occasionally deleted before all of its referers are nullified,
551 // probably in a callback race. After this comment is released, the crash
552 // reports should narrow down exactly which pointer it is. Collapsing all the
553 // early-returns into a single branch makes it hard to see exactly which pointer
554 // it is.
555 void WebstoreInstaller::StartDownload(const base::FilePath& file) {
556 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
558 if (file.empty()) {
559 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
560 return;
563 DownloadManager* download_manager =
564 BrowserContext::GetDownloadManager(profile_);
565 if (!download_manager) {
566 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
567 return;
570 content::WebContents* contents = web_contents();
571 if (!contents) {
572 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
573 return;
575 if (!contents->GetRenderProcessHost()) {
576 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
577 return;
579 if (!contents->GetRenderViewHost()) {
580 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
581 return;
584 content::NavigationController& controller = contents->GetController();
585 if (!controller.GetBrowserContext()) {
586 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
587 return;
589 if (!controller.GetBrowserContext()->GetResourceContext()) {
590 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
591 return;
594 // The download url for the given extension is contained in |download_url_|.
595 // We will navigate the current tab to this url to start the download. The
596 // download system will then pass the crx to the CrxInstaller.
597 RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER);
598 int render_process_host_id = contents->GetRenderProcessHost()->GetID();
599 int render_view_host_routing_id =
600 contents->GetRenderViewHost()->GetRoutingID();
601 content::ResourceContext* resource_context =
602 controller.GetBrowserContext()->GetResourceContext();
603 scoped_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
604 download_url_,
605 render_process_host_id,
606 render_view_host_routing_id ,
607 resource_context));
608 params->set_file_path(file);
609 if (controller.GetVisibleEntry())
610 params->set_referrer(
611 content::Referrer(controller.GetVisibleEntry()->GetURL(),
612 blink::WebReferrerPolicyDefault));
613 params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted, this));
614 download_manager->DownloadUrl(params.Pass());
617 void WebstoreInstaller::ReportFailure(const std::string& error,
618 FailureReason reason) {
619 if (delegate_) {
620 delegate_->OnExtensionInstallFailure(id_, error, reason);
621 delegate_ = NULL;
624 extensions::InstallTracker* tracker =
625 extensions::InstallTrackerFactory::GetForProfile(profile_);
626 tracker->OnInstallFailure(id_);
628 Release(); // Balanced in Start().
631 void WebstoreInstaller::ReportSuccess() {
632 if (delegate_) {
633 delegate_->OnExtensionInstallSuccess(id_);
634 delegate_ = NULL;
637 Release(); // Balanced in Start().
640 void WebstoreInstaller::RecordInterrupt(const DownloadItem* download) const {
641 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.WebstoreDownload.InterruptReason",
642 download->GetLastReason());
644 // Use logarithmic bin sizes up to 1 TB.
645 const int kNumBuckets = 30;
646 const int64 kMaxSizeKb = 1 << kNumBuckets;
647 UMA_HISTOGRAM_CUSTOM_COUNTS(
648 "Extensions.WebstoreDownload.InterruptReceivedKBytes",
649 download->GetReceivedBytes() / 1024,
651 kMaxSizeKb,
652 kNumBuckets);
653 int64 total_bytes = download->GetTotalBytes();
654 if (total_bytes >= 0) {
655 UMA_HISTOGRAM_CUSTOM_COUNTS(
656 "Extensions.WebstoreDownload.InterruptTotalKBytes",
657 total_bytes / 1024,
659 kMaxSizeKb,
660 kNumBuckets);
662 UMA_HISTOGRAM_BOOLEAN(
663 "Extensions.WebstoreDownload.InterruptTotalSizeUnknown",
664 total_bytes <= 0);
667 } // namespace extensions