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