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