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
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.
GetDownloadFilePath(const base::FilePath & download_directory,const std::string & id,const base::Callback<void (const base::FilePath &)> & callback)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
MaybeAppendAuthUserParameter(const std::string & authuser,GURL * url)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
GetWebstoreInstallURL(const std::string & extension_id,InstallSource source)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 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
OnExtensionDownloadStarted(const std::string & id,content::DownloadItem * item)212 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
213 const std::string& id,
214 content::DownloadItem* item) {
215 }
216
OnExtensionDownloadProgress(const std::string & id,content::DownloadItem * item)217 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
218 const std::string& id,
219 content::DownloadItem* item) {
220 }
221
Approval()222 WebstoreInstaller::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
232 scoped_ptr<WebstoreInstaller::Approval>
CreateWithInstallPrompt(Profile * profile)233 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
234 scoped_ptr<Approval> result(new Approval());
235 result->profile = profile;
236 return result.Pass();
237 }
238
239 scoped_ptr<WebstoreInstaller::Approval>
CreateForSharedModule(Profile * profile)240 WebstoreInstaller::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
249 scoped_ptr<WebstoreInstaller::Approval>
CreateWithNoInstallPrompt(Profile * profile,const std::string & extension_id,scoped_ptr<base::DictionaryValue> parsed_manifest,bool strict_manifest_check)250 WebstoreInstaller::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
~Approval()268 WebstoreInstaller::Approval::~Approval() {}
269
GetAssociatedApproval(const DownloadItem & download)270 const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
271 const DownloadItem& download) {
272 return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
273 }
274
WebstoreInstaller(Profile * profile,Delegate * delegate,content::WebContents * web_contents,const std::string & id,scoped_ptr<Approval> approval,InstallSource source)275 WebstoreInstaller::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
Start()300 void 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
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)355 void 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
OnExtensionInstalled(content::BrowserContext * browser_context,const Extension * extension,bool is_update)382 void 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
InvalidateDelegate()424 void WebstoreInstaller::InvalidateDelegate() {
425 delegate_ = NULL;
426 }
427
SetDownloadDirectoryForTests(base::FilePath * directory)428 void WebstoreInstaller::SetDownloadDirectoryForTests(
429 base::FilePath* directory) {
430 g_download_directory_for_tests = directory;
431 }
432
~WebstoreInstaller()433 WebstoreInstaller::~WebstoreInstaller() {
434 if (download_item_) {
435 download_item_->RemoveObserver(this);
436 download_item_ = NULL;
437 }
438 }
439
OnDownloadStarted(DownloadItem * item,content::DownloadInterruptReason interrupt_reason)440 void 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
OnDownloadUpdated(DownloadItem * download)480 void 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
OnDownloadDestroyed(DownloadItem * download)526 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
527 CHECK_EQ(download_item_, download);
528 download_item_->RemoveObserver(this);
529 download_item_ = NULL;
530 }
531
DownloadNextPendingModule()532 void 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
DownloadCrx(const std::string & extension_id,InstallSource source)542 void 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.
StartDownload(const base::FilePath & file)577 void 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
UpdateDownloadProgress()639 void 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
StartCrxInstaller(const DownloadItem & download)678 void 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
ReportFailure(const std::string & error,FailureReason reason)698 void 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
ReportSuccess()712 void WebstoreInstaller::ReportSuccess() {
713 if (delegate_) {
714 delegate_->OnExtensionInstallSuccess(id_);
715 delegate_ = NULL;
716 }
717
718 Release(); // Balanced in Start().
719 }
720
RecordInterrupt(const DownloadItem * download) const721 void 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