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/files/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 "base/time/time.h"
23#include "chrome/browser/chrome_notification_types.h"
24#include "chrome/browser/download/download_crx_util.h"
25#include "chrome/browser/download/download_prefs.h"
26#include "chrome/browser/download/download_stats.h"
27#include "chrome/browser/extensions/crx_installer.h"
28#include "chrome/browser/extensions/install_tracker.h"
29#include "chrome/browser/extensions/install_tracker_factory.h"
30#include "chrome/browser/extensions/install_verifier.h"
31#include "chrome/browser/extensions/shared_module_service.h"
32#include "chrome/browser/profiles/profile.h"
33#include "chrome/browser/ui/browser_list.h"
34#include "chrome/browser/ui/tabs/tab_strip_model.h"
35#include "chrome/common/chrome_paths.h"
36#include "chrome/common/chrome_switches.h"
37#include "components/crx_file/id_util.h"
38#include "components/omaha_query_params/omaha_query_params.h"
39#include "content/public/browser/browser_thread.h"
40#include "content/public/browser/download_manager.h"
41#include "content/public/browser/download_save_info.h"
42#include "content/public/browser/download_url_parameters.h"
43#include "content/public/browser/navigation_controller.h"
44#include "content/public/browser/navigation_entry.h"
45#include "content/public/browser/notification_details.h"
46#include "content/public/browser/notification_service.h"
47#include "content/public/browser/notification_source.h"
48#include "content/public/browser/render_process_host.h"
49#include "content/public/browser/render_view_host.h"
50#include "content/public/browser/web_contents.h"
51#include "extensions/browser/extension_registry.h"
52#include "extensions/browser/extension_system.h"
53#include "extensions/common/extension.h"
54#include "extensions/common/extension_urls.h"
55#include "extensions/common/manifest_constants.h"
56#include "extensions/common/manifest_handlers/shared_module_info.h"
57#include "net/base/escape.h"
58#include "url/gurl.h"
59
60#if defined(OS_CHROMEOS)
61#include "chrome/browser/chromeos/drive/file_system_util.h"
62#endif
63
64using content::BrowserContext;
65using content::BrowserThread;
66using content::DownloadItem;
67using content::DownloadManager;
68using content::NavigationController;
69using content::DownloadUrlParameters;
70
71namespace {
72
73// Key used to attach the Approval to the DownloadItem.
74const char kApprovalKey[] = "extensions.webstore_installer";
75
76const char kInvalidIdError[] = "Invalid id";
77const char kDownloadDirectoryError[] = "Could not create download directory";
78const char kDownloadCanceledError[] = "Download canceled";
79const char kDownloadInterruptedError[] = "Download interrupted";
80const char kInvalidDownloadError[] =
81    "Download was not a valid extension or user script";
82const char kDependencyNotFoundError[] = "Dependency not found";
83const char kDependencyNotSharedModuleError[] =
84    "Dependency is not shared module";
85const char kInlineInstallSource[] = "inline";
86const char kDefaultInstallSource[] = "ondemand";
87const char kAppLauncherInstallSource[] = "applauncher";
88
89// TODO(rockot): Share this duplicated constant with the extension updater.
90// See http://crbug.com/371398.
91const char kAuthUserQueryKey[] = "authuser";
92
93const size_t kTimeRemainingMinutesThreshold = 1u;
94
95// Folder for downloading crx files from the webstore. This is used so that the
96// crx files don't go via the usual downloads folder.
97const base::FilePath::CharType kWebstoreDownloadFolder[] =
98    FILE_PATH_LITERAL("Webstore Downloads");
99
100base::FilePath* g_download_directory_for_tests = NULL;
101
102// Must be executed on the FILE thread.
103void GetDownloadFilePath(
104    const base::FilePath& download_directory,
105    const std::string& id,
106    const base::Callback<void(const base::FilePath&)>& callback) {
107  // Ensure the download directory exists. TODO(asargent) - make this use
108  // common code from the downloads system.
109  if (!base::DirectoryExists(download_directory)) {
110    if (!base::CreateDirectory(download_directory)) {
111      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
112                              base::Bind(callback, base::FilePath()));
113      return;
114    }
115  }
116
117  // This is to help avoid a race condition between when we generate this
118  // filename and when the download starts writing to it (think concurrently
119  // running sharded browser tests installing the same test file, for
120  // instance).
121  std::string random_number =
122      base::Uint64ToString(base::RandGenerator(kuint16max));
123
124  base::FilePath file =
125      download_directory.AppendASCII(id + "_" + random_number + ".crx");
126
127  int uniquifier =
128      base::GetUniquePathNumber(file, base::FilePath::StringType());
129  if (uniquifier > 0) {
130    file = file.InsertBeforeExtensionASCII(
131        base::StringPrintf(" (%d)", uniquifier));
132  }
133
134  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
135                          base::Bind(callback, file));
136}
137
138void MaybeAppendAuthUserParameter(const std::string& authuser, GURL* url) {
139  if (authuser.empty())
140    return;
141  std::string old_query = url->query();
142  url::Component query(0, old_query.length());
143  url::Component key, value;
144  // Ensure that the URL doesn't already specify an authuser parameter.
145  while (url::ExtractQueryKeyValue(
146             old_query.c_str(), &query, &key, &value)) {
147    std::string key_string = old_query.substr(key.begin, key.len);
148    if (key_string == kAuthUserQueryKey) {
149      return;
150    }
151  }
152  if (!old_query.empty()) {
153    old_query += "&";
154  }
155  std::string authuser_param = base::StringPrintf(
156      "%s=%s",
157      kAuthUserQueryKey,
158      authuser.c_str());
159
160  // TODO(rockot): Share this duplicated code with the extension updater.
161  // See http://crbug.com/371398.
162  std::string new_query_string = old_query + authuser_param;
163  url::Component new_query(0, new_query_string.length());
164  url::Replacements<char> replacements;
165  replacements.SetQuery(new_query_string.c_str(), new_query);
166  *url = url->ReplaceComponents(replacements);
167}
168
169}  // namespace
170
171namespace extensions {
172
173// static
174GURL WebstoreInstaller::GetWebstoreInstallURL(
175    const std::string& extension_id,
176    InstallSource source) {
177  std::string install_source;
178  switch (source) {
179    case INSTALL_SOURCE_INLINE:
180      install_source = kInlineInstallSource;
181      break;
182    case INSTALL_SOURCE_APP_LAUNCHER:
183      install_source = kAppLauncherInstallSource;
184      break;
185    case INSTALL_SOURCE_OTHER:
186      install_source = kDefaultInstallSource;
187  }
188
189  CommandLine* cmd_line = CommandLine::ForCurrentProcess();
190  if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
191    std::string download_url =
192        cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
193    return GURL(base::StringPrintf(download_url.c_str(),
194                                   extension_id.c_str()));
195  }
196  std::vector<std::string> params;
197  params.push_back("id=" + extension_id);
198  if (!install_source.empty())
199    params.push_back("installsource=" + install_source);
200  params.push_back("uc");
201  std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
202
203  GURL url(url_string + "?response=redirect&" +
204           omaha_query_params::OmahaQueryParams::Get(
205               omaha_query_params::OmahaQueryParams::CRX) +
206           "&x=" + net::EscapeQueryParamValue(JoinString(params, '&'), true));
207  DCHECK(url.is_valid());
208
209  return url;
210}
211
212void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
213    const std::string& id,
214    content::DownloadItem* item) {
215}
216
217void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
218    const std::string& id,
219    content::DownloadItem* item) {
220}
221
222WebstoreInstaller::Approval::Approval()
223    : profile(NULL),
224      use_app_installed_bubble(false),
225      skip_post_install_ui(false),
226      skip_install_dialog(false),
227      enable_launcher(false),
228      manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT),
229      is_ephemeral(false) {
230}
231
232scoped_ptr<WebstoreInstaller::Approval>
233WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
234  scoped_ptr<Approval> result(new Approval());
235  result->profile = profile;
236  return result.Pass();
237}
238
239scoped_ptr<WebstoreInstaller::Approval>
240WebstoreInstaller::Approval::CreateForSharedModule(Profile* profile) {
241  scoped_ptr<Approval> result(new Approval());
242  result->profile = profile;
243  result->skip_install_dialog = true;
244  result->skip_post_install_ui = true;
245  result->manifest_check_level = MANIFEST_CHECK_LEVEL_NONE;
246  return result.Pass();
247}
248
249scoped_ptr<WebstoreInstaller::Approval>
250WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
251    Profile* profile,
252    const std::string& extension_id,
253    scoped_ptr<base::DictionaryValue> parsed_manifest,
254    bool strict_manifest_check) {
255  scoped_ptr<Approval> result(new Approval());
256  result->extension_id = extension_id;
257  result->profile = profile;
258  result->manifest = scoped_ptr<Manifest>(
259      new Manifest(Manifest::INVALID_LOCATION,
260                   scoped_ptr<base::DictionaryValue>(
261                       parsed_manifest->DeepCopy())));
262  result->skip_install_dialog = true;
263  result->manifest_check_level = strict_manifest_check ?
264    MANIFEST_CHECK_LEVEL_STRICT : MANIFEST_CHECK_LEVEL_LOOSE;
265  return result.Pass();
266}
267
268WebstoreInstaller::Approval::~Approval() {}
269
270const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
271    const DownloadItem& download) {
272  return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
273}
274
275WebstoreInstaller::WebstoreInstaller(Profile* profile,
276                                     Delegate* delegate,
277                                     content::WebContents* web_contents,
278                                     const std::string& id,
279                                     scoped_ptr<Approval> approval,
280                                     InstallSource source)
281    : content::WebContentsObserver(web_contents),
282      extension_registry_observer_(this),
283      profile_(profile),
284      delegate_(delegate),
285      id_(id),
286      install_source_(source),
287      download_item_(NULL),
288      approval_(approval.release()),
289      total_modules_(0),
290      download_started_(false) {
291  DCHECK_CURRENTLY_ON(BrowserThread::UI);
292  DCHECK(web_contents);
293
294  registrar_.Add(this,
295                 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
296                 content::Source<CrxInstaller>(NULL));
297  extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
298}
299
300void WebstoreInstaller::Start() {
301  DCHECK_CURRENTLY_ON(BrowserThread::UI);
302  AddRef();  // Balanced in ReportSuccess and ReportFailure.
303
304  if (!crx_file::id_util::IdIsValid(id_)) {
305    ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
306    return;
307  }
308
309  ExtensionService* extension_service =
310    ExtensionSystem::Get(profile_)->extension_service();
311  if (approval_.get() && approval_->dummy_extension.get()) {
312    extension_service->shared_module_service()->CheckImports(
313        approval_->dummy_extension.get(), &pending_modules_, &pending_modules_);
314    // Do not check the return value of CheckImports, the CRX installer
315    // will report appropriate error messages and fail to install if there
316    // is an import error.
317  }
318
319  // Add the extension main module into the list.
320  SharedModuleInfo::ImportInfo info;
321  info.extension_id = id_;
322  pending_modules_.push_back(info);
323
324  total_modules_ = pending_modules_.size();
325
326  std::set<std::string> ids;
327  std::list<SharedModuleInfo::ImportInfo>::const_iterator i;
328  for (i = pending_modules_.begin(); i != pending_modules_.end(); ++i) {
329    ids.insert(i->extension_id);
330  }
331  ExtensionSystem::Get(profile_)->install_verifier()->AddProvisional(ids);
332
333  std::string name;
334  if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) {
335    NOTREACHED();
336  }
337  extensions::InstallTracker* tracker =
338      extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
339  extensions::InstallObserver::ExtensionInstallParams params(
340      id_,
341      name,
342      approval_->installing_icon,
343      approval_->manifest->is_app(),
344      approval_->manifest->is_platform_app());
345  params.is_ephemeral = approval_->is_ephemeral;
346  tracker->OnBeginExtensionInstall(params);
347
348  tracker->OnBeginExtensionDownload(id_);
349
350  // TODO(crbug.com/305343): Query manifest of dependencies before
351  // downloading & installing those dependencies.
352  DownloadNextPendingModule();
353}
354
355void WebstoreInstaller::Observe(int type,
356                                const content::NotificationSource& source,
357                                const content::NotificationDetails& details) {
358  switch (type) {
359    case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
360      CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
361      CHECK(crx_installer);
362      if (crx_installer != crx_installer_.get())
363        return;
364
365      // TODO(rdevlin.cronin): Continue removing std::string errors and
366      // replacing with base::string16. See crbug.com/71980.
367      const base::string16* error =
368          content::Details<const base::string16>(details).ptr();
369      const std::string utf8_error = base::UTF16ToUTF8(*error);
370      crx_installer_ = NULL;
371      // ReportFailure releases a reference to this object so it must be the
372      // last operation in this method.
373      ReportFailure(utf8_error, FAILURE_REASON_OTHER);
374      break;
375    }
376
377    default:
378      NOTREACHED();
379  }
380}
381
382void WebstoreInstaller::OnExtensionInstalled(
383    content::BrowserContext* browser_context,
384    const Extension* extension,
385    bool is_update) {
386  CHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context)));
387  if (pending_modules_.empty())
388    return;
389  SharedModuleInfo::ImportInfo info = pending_modules_.front();
390  if (extension->id() != info.extension_id)
391    return;
392  pending_modules_.pop_front();
393
394  // Clean up local state from the current download.
395  if (download_item_) {
396    download_item_->RemoveObserver(this);
397    download_item_->Remove();
398    download_item_ = NULL;
399  }
400  crx_installer_ = NULL;
401
402  if (pending_modules_.empty()) {
403    CHECK_EQ(extension->id(), id_);
404    ReportSuccess();
405  } else {
406    const Version version_required(info.minimum_version);
407    if (version_required.IsValid() &&
408        extension->version()->CompareTo(version_required) < 0) {
409      // It should not happen, CrxInstaller will make sure the version is
410      // equal or newer than version_required.
411      ReportFailure(kDependencyNotFoundError,
412                    FAILURE_REASON_DEPENDENCY_NOT_FOUND);
413    } else if (!SharedModuleInfo::IsSharedModule(extension)) {
414      // It should not happen, CrxInstaller will make sure it is a shared
415      // module.
416      ReportFailure(kDependencyNotSharedModuleError,
417                    FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
418    } else {
419      DownloadNextPendingModule();
420    }
421  }
422}
423
424void WebstoreInstaller::InvalidateDelegate() {
425  delegate_ = NULL;
426}
427
428void WebstoreInstaller::SetDownloadDirectoryForTests(
429    base::FilePath* directory) {
430  g_download_directory_for_tests = directory;
431}
432
433WebstoreInstaller::~WebstoreInstaller() {
434  if (download_item_) {
435    download_item_->RemoveObserver(this);
436    download_item_ = NULL;
437  }
438}
439
440void WebstoreInstaller::OnDownloadStarted(
441    DownloadItem* item,
442    content::DownloadInterruptReason interrupt_reason) {
443  if (!item) {
444    DCHECK_NE(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
445    ReportFailure(content::DownloadInterruptReasonToString(interrupt_reason),
446                  FAILURE_REASON_OTHER);
447    return;
448  }
449
450  DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
451  DCHECK(!pending_modules_.empty());
452  download_item_ = item;
453  download_item_->AddObserver(this);
454  if (pending_modules_.size() > 1) {
455    // We are downloading a shared module. We need create an approval for it.
456    scoped_ptr<Approval> approval = Approval::CreateForSharedModule(profile_);
457    const SharedModuleInfo::ImportInfo& info = pending_modules_.front();
458    approval->extension_id = info.extension_id;
459    const Version version_required(info.minimum_version);
460
461    if (version_required.IsValid()) {
462      approval->minimum_version.reset(
463          new Version(version_required));
464    }
465    download_item_->SetUserData(kApprovalKey, approval.release());
466  } else {
467    // It is for the main module of the extension. We should use the provided
468    // |approval_|.
469    if (approval_)
470      download_item_->SetUserData(kApprovalKey, approval_.release());
471  }
472
473  if (!download_started_) {
474    if (delegate_)
475      delegate_->OnExtensionDownloadStarted(id_, download_item_);
476    download_started_ = true;
477  }
478}
479
480void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
481  CHECK_EQ(download_item_, download);
482
483  switch (download->GetState()) {
484    case DownloadItem::CANCELLED:
485      ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
486      break;
487    case DownloadItem::INTERRUPTED:
488      RecordInterrupt(download);
489      ReportFailure(kDownloadInterruptedError, FAILURE_REASON_OTHER);
490      break;
491    case DownloadItem::COMPLETE:
492      // Wait for other notifications if the download is really an extension.
493      if (!download_crx_util::IsExtensionDownload(*download)) {
494        ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
495      } else {
496        if (crx_installer_.get())
497          return;  // DownloadItemImpl calls the observer twice, ignore it.
498        StartCrxInstaller(*download);
499
500        if (pending_modules_.size() == 1) {
501          // The download is the last module - the extension main module.
502          if (delegate_)
503            delegate_->OnExtensionDownloadProgress(id_, download);
504          extensions::InstallTracker* tracker =
505              extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
506          tracker->OnDownloadProgress(id_, 100);
507        }
508      }
509      // Stop the progress timer if it's running.
510      download_progress_timer_.Stop();
511      break;
512    case DownloadItem::IN_PROGRESS: {
513      if (delegate_ && pending_modules_.size() == 1) {
514        // Only report download progress for the main module to |delegrate_|.
515        delegate_->OnExtensionDownloadProgress(id_, download);
516      }
517      UpdateDownloadProgress();
518      break;
519    }
520    default:
521      // Continue listening if the download is not in one of the above states.
522      break;
523  }
524}
525
526void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
527  CHECK_EQ(download_item_, download);
528  download_item_->RemoveObserver(this);
529  download_item_ = NULL;
530}
531
532void WebstoreInstaller::DownloadNextPendingModule() {
533  CHECK(!pending_modules_.empty());
534  if (pending_modules_.size() == 1) {
535    DCHECK_EQ(id_, pending_modules_.front().extension_id);
536    DownloadCrx(id_, install_source_);
537  } else {
538    DownloadCrx(pending_modules_.front().extension_id, INSTALL_SOURCE_OTHER);
539  }
540}
541
542void WebstoreInstaller::DownloadCrx(
543    const std::string& extension_id,
544    InstallSource source) {
545  download_url_ = GetWebstoreInstallURL(extension_id, source);
546  MaybeAppendAuthUserParameter(approval_->authuser, &download_url_);
547
548  base::FilePath user_data_dir;
549  PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
550  base::FilePath download_path = user_data_dir.Append(kWebstoreDownloadFolder);
551
552  base::FilePath download_directory(g_download_directory_for_tests ?
553      *g_download_directory_for_tests : download_path);
554
555#if defined(OS_CHROMEOS)
556  // Do not use drive for extension downloads.
557  if (drive::util::IsUnderDriveMountPoint(download_directory)) {
558    download_directory = DownloadPrefs::FromBrowserContext(
559        profile_)->GetDefaultDownloadDirectoryForProfile();
560  }
561#endif
562
563  BrowserThread::PostTask(
564      BrowserThread::FILE, FROM_HERE,
565      base::Bind(&GetDownloadFilePath, download_directory, extension_id,
566        base::Bind(&WebstoreInstaller::StartDownload, this)));
567}
568
569// http://crbug.com/165634
570// http://crbug.com/126013
571// The current working theory is that one of the many pointers dereferenced in
572// here is occasionally deleted before all of its referers are nullified,
573// probably in a callback race. After this comment is released, the crash
574// reports should narrow down exactly which pointer it is.  Collapsing all the
575// early-returns into a single branch makes it hard to see exactly which pointer
576// it is.
577void WebstoreInstaller::StartDownload(const base::FilePath& file) {
578  DCHECK_CURRENTLY_ON(BrowserThread::UI);
579
580  if (file.empty()) {
581    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
582    return;
583  }
584
585  DownloadManager* download_manager =
586      BrowserContext::GetDownloadManager(profile_);
587  if (!download_manager) {
588    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
589    return;
590  }
591
592  content::WebContents* contents = web_contents();
593  if (!contents) {
594    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
595    return;
596  }
597  if (!contents->GetRenderProcessHost()) {
598    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
599    return;
600  }
601  if (!contents->GetRenderViewHost()) {
602    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
603    return;
604  }
605
606  content::NavigationController& controller = contents->GetController();
607  if (!controller.GetBrowserContext()) {
608    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
609    return;
610  }
611  if (!controller.GetBrowserContext()->GetResourceContext()) {
612    ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
613    return;
614  }
615
616  // The download url for the given extension is contained in |download_url_|.
617  // We will navigate the current tab to this url to start the download. The
618  // download system will then pass the crx to the CrxInstaller.
619  RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER);
620  int render_process_host_id = contents->GetRenderProcessHost()->GetID();
621  int render_view_host_routing_id =
622      contents->GetRenderViewHost()->GetRoutingID();
623  content::ResourceContext* resource_context =
624      controller.GetBrowserContext()->GetResourceContext();
625  scoped_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
626      download_url_,
627      render_process_host_id,
628      render_view_host_routing_id ,
629      resource_context));
630  params->set_file_path(file);
631  if (controller.GetVisibleEntry())
632    params->set_referrer(
633        content::Referrer(controller.GetVisibleEntry()->GetURL(),
634                          blink::WebReferrerPolicyDefault));
635  params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted, this));
636  download_manager->DownloadUrl(params.Pass());
637}
638
639void WebstoreInstaller::UpdateDownloadProgress() {
640  // If the download has gone away, or isn't in progress (in which case we can't
641  // give a good progress estimate), stop any running timers and return.
642  if (!download_item_ ||
643      download_item_->GetState() != DownloadItem::IN_PROGRESS) {
644    download_progress_timer_.Stop();
645    return;
646  }
647
648  int percent = download_item_->PercentComplete();
649  // Only report progress if percent is more than 0 or we have finished
650  // downloading at least one of the pending modules.
651  int finished_modules = total_modules_ - pending_modules_.size();
652  if (finished_modules > 0 && percent < 0)
653    percent = 0;
654  if (percent >= 0) {
655    percent = (percent + (finished_modules * 100)) / total_modules_;
656    extensions::InstallTracker* tracker =
657        extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
658    tracker->OnDownloadProgress(id_, percent);
659  }
660
661  // If there's enough time remaining on the download to warrant an update,
662  // set the timer (overwriting any current timers). Otherwise, stop the
663  // timer.
664  base::TimeDelta time_remaining;
665  if (download_item_->TimeRemaining(&time_remaining) &&
666      time_remaining >
667          base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold)) {
668    download_progress_timer_.Start(
669        FROM_HERE,
670        base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold),
671        this,
672        &WebstoreInstaller::UpdateDownloadProgress);
673  } else {
674    download_progress_timer_.Stop();
675  }
676}
677
678void WebstoreInstaller::StartCrxInstaller(const DownloadItem& download) {
679  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
680  DCHECK(!crx_installer_.get());
681
682  ExtensionService* service = ExtensionSystem::Get(profile_)->
683      extension_service();
684  CHECK(service);
685
686  const Approval* approval = GetAssociatedApproval(download);
687  DCHECK(approval);
688
689  crx_installer_ = download_crx_util::CreateCrxInstaller(profile_, download);
690
691  crx_installer_->set_expected_id(approval->extension_id);
692  crx_installer_->set_is_gallery_install(true);
693  crx_installer_->set_allow_silent_install(true);
694
695  crx_installer_->InstallCrx(download.GetFullPath());
696}
697
698void WebstoreInstaller::ReportFailure(const std::string& error,
699                                      FailureReason reason) {
700  if (delegate_) {
701    delegate_->OnExtensionInstallFailure(id_, error, reason);
702    delegate_ = NULL;
703  }
704
705  extensions::InstallTracker* tracker =
706      extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
707  tracker->OnInstallFailure(id_);
708
709  Release();  // Balanced in Start().
710}
711
712void WebstoreInstaller::ReportSuccess() {
713  if (delegate_) {
714    delegate_->OnExtensionInstallSuccess(id_);
715    delegate_ = NULL;
716  }
717
718  Release();  // Balanced in Start().
719}
720
721void WebstoreInstaller::RecordInterrupt(const DownloadItem* download) const {
722  UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.WebstoreDownload.InterruptReason",
723                              download->GetLastReason());
724
725  // Use logarithmic bin sizes up to 1 TB.
726  const int kNumBuckets = 30;
727  const int64 kMaxSizeKb = 1 << kNumBuckets;
728  UMA_HISTOGRAM_CUSTOM_COUNTS(
729      "Extensions.WebstoreDownload.InterruptReceivedKBytes",
730      download->GetReceivedBytes() / 1024,
731      1,
732      kMaxSizeKb,
733      kNumBuckets);
734  int64 total_bytes = download->GetTotalBytes();
735  if (total_bytes >= 0) {
736    UMA_HISTOGRAM_CUSTOM_COUNTS(
737        "Extensions.WebstoreDownload.InterruptTotalKBytes",
738        total_bytes / 1024,
739        1,
740        kMaxSizeKb,
741        kNumBuckets);
742  }
743  UMA_HISTOGRAM_BOOLEAN(
744      "Extensions.WebstoreDownload.InterruptTotalSizeUnknown",
745      total_bytes <= 0);
746}
747
748}  // namespace extensions
749