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