webstore_installer.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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. 4 5#include "chrome/browser/extensions/webstore_installer.h" 6 7#include <vector> 8 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" 55 56#if defined(OS_CHROMEOS) 57#include "chrome/browser/chromeos/drive/file_system_util.h" 58#endif 59 60using chrome::OmahaQueryParams; 61using content::BrowserContext; 62using content::BrowserThread; 63using content::DownloadItem; 64using content::DownloadManager; 65using content::NavigationController; 66using content::DownloadUrlParameters; 67 68namespace { 69 70// Key used to attach the Approval to the DownloadItem. 71const char kApprovalKey[] = "extensions.webstore_installer"; 72 73const char kInvalidIdError[] = "Invalid id"; 74const char kDownloadDirectoryError[] = "Could not create download directory"; 75const char kDownloadCanceledError[] = "Download canceled"; 76const char kInstallCanceledError[] = "Install canceled"; 77const char kDownloadInterruptedError[] = "Download interrupted"; 78const char kInvalidDownloadError[] = 79 "Download was not a valid extension or user script"; 80const char kDependencyNotFoundError[] = "Dependency not found"; 81const char kDependencyNotSharedModuleError[] = 82 "Dependency is not shared module"; 83const char kInlineInstallSource[] = "inline"; 84const char kDefaultInstallSource[] = "ondemand"; 85const char kAppLauncherInstallSource[] = "applauncher"; 86 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. 89const base::FilePath::CharType kWebstoreDownloadFolder[] = 90 FILE_PATH_LITERAL("Webstore Downloads"); 91 92base::FilePath* g_download_directory_for_tests = NULL; 93 94// Must be executed on the FILE thread. 95void 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; 106 } 107 } 108 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)); 115 116 base::FilePath file = 117 download_directory.AppendASCII(id + "_" + random_number + ".crx"); 118 119 int uniquifier = 120 base::GetUniquePathNumber(file, base::FilePath::StringType()); 121 if (uniquifier > 0) { 122 file = file.InsertBeforeExtensionASCII( 123 base::StringPrintf(" (%d)", uniquifier)); 124 } 125 126 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 127 base::Bind(callback, file)); 128} 129 130bool UseSeparateWebstoreDownloadDirectory() { 131 const char kFieldTrial[] = "WebstoreDownloadDirectory"; 132 const char kSeparateDirectoryUnderUDD[] = "SeparateDirectoryUnderUDD"; 133 134 std::string field_trial_group = 135 base::FieldTrialList::FindFullName(kFieldTrial); 136 return field_trial_group == kSeparateDirectoryUnderUDD; 137} 138 139} // namespace 140 141namespace extensions { 142 143// static 144GURL 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; 157 } 158 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())); 165 } 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(); 172 173 GURL url(url_string + "?response=redirect&" + 174 OmahaQueryParams::Get(OmahaQueryParams::CRX) + "&x=" + 175 net::EscapeQueryParamValue(JoinString(params, '&'), true)); 176 DCHECK(url.is_valid()); 177 178 return url; 179} 180 181void WebstoreInstaller::Delegate::OnExtensionDownloadStarted( 182 const std::string& id, 183 content::DownloadItem* item) { 184} 185 186void WebstoreInstaller::Delegate::OnExtensionDownloadProgress( 187 const std::string& id, 188 content::DownloadItem* item) { 189} 190 191WebstoreInstaller::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) { 199} 200 201scoped_ptr<WebstoreInstaller::Approval> 202WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) { 203 scoped_ptr<Approval> result(new Approval()); 204 result->profile = profile; 205 return result.Pass(); 206} 207 208scoped_ptr<WebstoreInstaller::Approval> 209WebstoreInstaller::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(); 215} 216 217scoped_ptr<WebstoreInstaller::Approval> 218WebstoreInstaller::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(); 234} 235 236WebstoreInstaller::Approval::~Approval() {} 237 238const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval( 239 const DownloadItem& download) { 240 return static_cast<const Approval*>(download.GetUserData(kApprovalKey)); 241} 242 243WebstoreInstaller::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); 260 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)); 267} 268 269void WebstoreInstaller::Start() { 270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 271 AddRef(); // Balanced in ReportSuccess and ReportFailure. 272 273 if (!Extension::IdIsValid(id_)) { 274 ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER); 275 return; 276 } 277 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; 289 } 290 } 291 292 // Add the extension main module into the list. 293 SharedModuleInfo::ImportInfo info; 294 info.extension_id = id_; 295 pending_modules_.push_back(info); 296 297 total_modules_ = pending_modules_.size(); 298 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); 303 } 304 ExtensionSystem::Get(profile_)->install_verifier()->AddProvisional(ids); 305 306 // TODO(crbug.com/305343): Query manifest of dependencises before 307 // downloading & installing those dependencies. 308 DownloadNextPendingModule(); 309 310 std::string name; 311 if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) { 312 NOTREACHED(); 313 } 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); 324} 325 326void 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); 338 } 339 break; 340 } 341 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(); 352 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(); 371 } 372 } 373 break; 374 } 375 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; 381 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; 390 } 391 392 default: 393 NOTREACHED(); 394 } 395} 396 397void WebstoreInstaller::InvalidateDelegate() { 398 delegate_ = NULL; 399} 400 401void WebstoreInstaller::SetDownloadDirectoryForTests( 402 base::FilePath* directory) { 403 g_download_directory_for_tests = directory; 404} 405 406WebstoreInstaller::~WebstoreInstaller() { 407 if (download_item_) { 408 download_item_->RemoveObserver(this); 409 download_item_ = NULL; 410 } 411} 412 413void 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; 421 } 422 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); 433 434 if (version_required.IsValid()) { 435 approval->minimum_version.reset( 436 new Version(version_required)); 437 } 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()); 444 } 445 446 if (!download_started_) { 447 if (delegate_) 448 delegate_->OnExtensionDownloadStarted(id_, download_item_); 449 download_started_ = true; 450 } 451} 452 453void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) { 454 CHECK_EQ(download_item_, download); 455 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); 475 } 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); 481 } 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); 490 } 491 break; 492 } 493 default: 494 // Continue listening if the download is not in one of the above states. 495 break; 496 } 497} 498 499void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) { 500 CHECK_EQ(download_item_, download); 501 download_item_->RemoveObserver(this); 502 download_item_ = NULL; 503} 504 505void 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); 512 } 513} 514 515void WebstoreInstaller::DownloadCrx( 516 const std::string& extension_id, 517 InstallSource source) { 518 download_url_ = GetWebstoreInstallURL(extension_id, source); 519 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(); 528 } 529 530 base::FilePath download_directory(g_download_directory_for_tests ? 531 *g_download_directory_for_tests : download_path); 532 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(); 538 } 539#endif 540 541 BrowserThread::PostTask( 542 BrowserThread::FILE, FROM_HERE, 543 base::Bind(&GetDownloadFilePath, download_directory, id_, 544 base::Bind(&WebstoreInstaller::StartDownload, this))); 545} 546 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. 555void WebstoreInstaller::StartDownload(const base::FilePath& file) { 556 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 557 558 if (file.empty()) { 559 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 560 return; 561 } 562 563 DownloadManager* download_manager = 564 BrowserContext::GetDownloadManager(profile_); 565 if (!download_manager) { 566 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 567 return; 568 } 569 570 content::WebContents* contents = web_contents(); 571 if (!contents) { 572 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 573 return; 574 } 575 if (!contents->GetRenderProcessHost()) { 576 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 577 return; 578 } 579 if (!contents->GetRenderViewHost()) { 580 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 581 return; 582 } 583 584 content::NavigationController& controller = contents->GetController(); 585 if (!controller.GetBrowserContext()) { 586 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 587 return; 588 } 589 if (!controller.GetBrowserContext()->GetResourceContext()) { 590 ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER); 591 return; 592 } 593 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()); 615} 616 617void WebstoreInstaller::ReportFailure(const std::string& error, 618 FailureReason reason) { 619 if (delegate_) { 620 delegate_->OnExtensionInstallFailure(id_, error, reason); 621 delegate_ = NULL; 622 } 623 624 extensions::InstallTracker* tracker = 625 extensions::InstallTrackerFactory::GetForProfile(profile_); 626 tracker->OnInstallFailure(id_); 627 628 Release(); // Balanced in Start(). 629} 630 631void WebstoreInstaller::ReportSuccess() { 632 if (delegate_) { 633 delegate_->OnExtensionInstallSuccess(id_); 634 delegate_ = NULL; 635 } 636 637 Release(); // Balanced in Start(). 638} 639 640void WebstoreInstaller::RecordInterrupt(const DownloadItem* download) const { 641 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.WebstoreDownload.InterruptReason", 642 download->GetLastReason()); 643 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, 650 1, 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, 658 1, 659 kMaxSizeKb, 660 kNumBuckets); 661 } 662 UMA_HISTOGRAM_BOOLEAN( 663 "Extensions.WebstoreDownload.InterruptTotalSizeUnknown", 664 total_bytes <= 0); 665} 666 667} // namespace extensions 668