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.
5 #include "chrome/browser/extensions/webstore_installer.h"
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/files/file_util.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/metrics/histogram.h"
16 #include "base/metrics/sparse_histogram.h"
17 #include "base/path_service.h"
18 #include "base/rand_util.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/time/time.h"
24 #include "chrome/browser/chrome_notification_types.h"
25 #include "chrome/browser/download/download_crx_util.h"
26 #include "chrome/browser/download/download_prefs.h"
27 #include "chrome/browser/download/download_stats.h"
28 #include "chrome/browser/extensions/crx_installer.h"
29 #include "chrome/browser/extensions/install_tracker.h"
30 #include "chrome/browser/extensions/install_tracker_factory.h"
31 #include "chrome/browser/extensions/install_verifier.h"
32 #include "chrome/browser/extensions/shared_module_service.h"
33 #include "chrome/browser/profiles/profile.h"
34 #include "chrome/common/chrome_paths.h"
35 #include "chrome/common/chrome_switches.h"
36 #include "chrome/grit/generated_resources.h"
37 #include "components/crx_file/id_util.h"
38 #include "components/update_client/update_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/browser/install/crx_install_error.h"
54 #include "extensions/common/extension.h"
55 #include "extensions/common/extension_urls.h"
56 #include "extensions/common/manifest_constants.h"
57 #include "extensions/common/manifest_handlers/shared_module_info.h"
58 #include "net/base/escape.h"
59 #include "ui/base/l10n/l10n_util.h"
62 #if defined(OS_CHROMEOS)
63 #include "chrome/browser/chromeos/drive/file_system_core_util.h"
66 using content::BrowserContext
;
67 using content::BrowserThread
;
68 using content::DownloadItem
;
69 using content::DownloadManager
;
70 using content::NavigationController
;
71 using content::DownloadUrlParameters
;
75 // Key used to attach the Approval to the DownloadItem.
76 const char kApprovalKey
[] = "extensions.webstore_installer";
78 const char kInvalidIdError
[] = "Invalid id";
79 const char kDownloadDirectoryError
[] = "Could not create download directory";
80 const char kDownloadCanceledError
[] = "Download canceled";
81 const char kDownloadInterruptedError
[] = "Download interrupted";
82 const char kInvalidDownloadError
[] =
83 "Download was not a valid extension or user script";
84 const char kDependencyNotFoundError
[] = "Dependency not found";
85 const char kDependencyNotSharedModuleError
[] =
86 "Dependency is not shared module";
87 const char kInlineInstallSource
[] = "inline";
88 const char kDefaultInstallSource
[] = "ondemand";
89 const char kAppLauncherInstallSource
[] = "applauncher";
91 // TODO(rockot): Share this duplicated constant with the extension updater.
92 // See http://crbug.com/371398.
93 const char kAuthUserQueryKey
[] = "authuser";
95 const size_t kTimeRemainingMinutesThreshold
= 1u;
97 // Folder for downloading crx files from the webstore. This is used so that the
98 // crx files don't go via the usual downloads folder.
99 const base::FilePath::CharType kWebstoreDownloadFolder
[] =
100 FILE_PATH_LITERAL("Webstore Downloads");
102 base::FilePath
* g_download_directory_for_tests
= NULL
;
104 // Must be executed on the FILE thread.
105 void GetDownloadFilePath(
106 const base::FilePath
& download_directory
,
107 const std::string
& id
,
108 const base::Callback
<void(const base::FilePath
&)>& callback
) {
109 // Ensure the download directory exists. TODO(asargent) - make this use
110 // common code from the downloads system.
111 if (!base::DirectoryExists(download_directory
)) {
112 if (!base::CreateDirectory(download_directory
)) {
113 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
114 base::Bind(callback
, base::FilePath()));
119 // This is to help avoid a race condition between when we generate this
120 // filename and when the download starts writing to it (think concurrently
121 // running sharded browser tests installing the same test file, for
123 std::string random_number
=
124 base::Uint64ToString(base::RandGenerator(kuint16max
));
126 base::FilePath file
=
127 download_directory
.AppendASCII(id
+ "_" + random_number
+ ".crx");
130 base::GetUniquePathNumber(file
, base::FilePath::StringType());
131 if (uniquifier
> 0) {
132 file
= file
.InsertBeforeExtensionASCII(
133 base::StringPrintf(" (%d)", uniquifier
));
136 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
137 base::Bind(callback
, file
));
140 void MaybeAppendAuthUserParameter(const std::string
& authuser
, GURL
* url
) {
141 if (authuser
.empty())
143 std::string old_query
= url
->query();
144 url::Component
query(0, old_query
.length());
145 url::Component key
, value
;
146 // Ensure that the URL doesn't already specify an authuser parameter.
147 while (url::ExtractQueryKeyValue(
148 old_query
.c_str(), &query
, &key
, &value
)) {
149 std::string key_string
= old_query
.substr(key
.begin
, key
.len
);
150 if (key_string
== kAuthUserQueryKey
) {
154 if (!old_query
.empty()) {
157 std::string authuser_param
= base::StringPrintf(
162 // TODO(rockot): Share this duplicated code with the extension updater.
163 // See http://crbug.com/371398.
164 std::string new_query_string
= old_query
+ authuser_param
;
165 url::Component
new_query(0, new_query_string
.length());
166 url::Replacements
<char> replacements
;
167 replacements
.SetQuery(new_query_string
.c_str(), new_query
);
168 *url
= url
->ReplaceComponents(replacements
);
171 std::string
GetErrorMessageForDownloadInterrupt(
172 content::DownloadInterruptReason reason
) {
174 case content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED
:
175 case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN
:
176 return l10n_util::GetStringUTF8(IDS_WEBSTORE_DOWNLOAD_ACCESS_DENIED
);
180 return kDownloadInterruptedError
;
185 namespace extensions
{
188 GURL
WebstoreInstaller::GetWebstoreInstallURL(
189 const std::string
& extension_id
,
190 InstallSource source
) {
191 std::string install_source
;
193 case INSTALL_SOURCE_INLINE
:
194 install_source
= kInlineInstallSource
;
196 case INSTALL_SOURCE_APP_LAUNCHER
:
197 install_source
= kAppLauncherInstallSource
;
199 case INSTALL_SOURCE_OTHER
:
200 install_source
= kDefaultInstallSource
;
203 base::CommandLine
* cmd_line
= base::CommandLine::ForCurrentProcess();
204 if (cmd_line
->HasSwitch(switches::kAppsGalleryDownloadURL
)) {
205 std::string download_url
=
206 cmd_line
->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL
);
207 return GURL(base::StringPrintf(download_url
.c_str(),
208 extension_id
.c_str()));
210 std::vector
<std::string
> params
;
211 params
.push_back("id=" + extension_id
);
212 if (!install_source
.empty())
213 params
.push_back("installsource=" + install_source
);
214 params
.push_back("uc");
215 std::string url_string
= extension_urls::GetWebstoreUpdateUrl().spec();
217 GURL
url(url_string
+ "?response=redirect&" +
218 update_client::UpdateQueryParams::Get(
219 update_client::UpdateQueryParams::CRX
) +
220 "&x=" + net::EscapeQueryParamValue(base::JoinString(params
, "&"),
222 DCHECK(url
.is_valid());
227 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
228 const std::string
& id
,
229 content::DownloadItem
* item
) {
232 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
233 const std::string
& id
,
234 content::DownloadItem
* item
) {
237 WebstoreInstaller::Approval::Approval()
239 use_app_installed_bubble(false),
240 skip_post_install_ui(false),
241 skip_install_dialog(false),
242 enable_launcher(false),
243 manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT
),
244 is_ephemeral(false) {
247 scoped_ptr
<WebstoreInstaller::Approval
>
248 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile
* profile
) {
249 scoped_ptr
<Approval
> result(new Approval());
250 result
->profile
= profile
;
251 return result
.Pass();
254 scoped_ptr
<WebstoreInstaller::Approval
>
255 WebstoreInstaller::Approval::CreateForSharedModule(Profile
* profile
) {
256 scoped_ptr
<Approval
> result(new Approval());
257 result
->profile
= profile
;
258 result
->skip_install_dialog
= true;
259 result
->skip_post_install_ui
= true;
260 result
->manifest_check_level
= MANIFEST_CHECK_LEVEL_NONE
;
261 return result
.Pass();
264 scoped_ptr
<WebstoreInstaller::Approval
>
265 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
267 const std::string
& extension_id
,
268 scoped_ptr
<base::DictionaryValue
> parsed_manifest
,
269 bool strict_manifest_check
) {
270 scoped_ptr
<Approval
> result(new Approval());
271 result
->extension_id
= extension_id
;
272 result
->profile
= profile
;
273 result
->manifest
= scoped_ptr
<Manifest
>(
274 new Manifest(Manifest::INVALID_LOCATION
,
275 scoped_ptr
<base::DictionaryValue
>(
276 parsed_manifest
->DeepCopy())));
277 result
->skip_install_dialog
= true;
278 result
->manifest_check_level
= strict_manifest_check
?
279 MANIFEST_CHECK_LEVEL_STRICT
: MANIFEST_CHECK_LEVEL_LOOSE
;
280 return result
.Pass();
283 WebstoreInstaller::Approval::~Approval() {}
285 const WebstoreInstaller::Approval
* WebstoreInstaller::GetAssociatedApproval(
286 const DownloadItem
& download
) {
287 return static_cast<const Approval
*>(download
.GetUserData(kApprovalKey
));
290 WebstoreInstaller::WebstoreInstaller(Profile
* profile
,
292 content::WebContents
* web_contents
,
293 const std::string
& id
,
294 scoped_ptr
<Approval
> approval
,
295 InstallSource source
)
296 : content::WebContentsObserver(web_contents
),
297 extension_registry_observer_(this),
301 install_source_(source
),
302 download_item_(NULL
),
303 approval_(approval
.release()),
305 download_started_(false) {
306 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
307 DCHECK(web_contents
);
310 extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
,
311 content::Source
<CrxInstaller
>(NULL
));
312 extension_registry_observer_
.Add(ExtensionRegistry::Get(profile
));
315 void WebstoreInstaller::Start() {
316 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
317 AddRef(); // Balanced in ReportSuccess and ReportFailure.
319 if (!crx_file::id_util::IdIsValid(id_
)) {
320 ReportFailure(kInvalidIdError
, FAILURE_REASON_OTHER
);
324 ExtensionService
* extension_service
=
325 ExtensionSystem::Get(profile_
)->extension_service();
326 if (approval_
.get() && approval_
->dummy_extension
.get()) {
327 extension_service
->shared_module_service()->CheckImports(
328 approval_
->dummy_extension
.get(), &pending_modules_
, &pending_modules_
);
329 // Do not check the return value of CheckImports, the CRX installer
330 // will report appropriate error messages and fail to install if there
331 // is an import error.
334 // Add the extension main module into the list.
335 SharedModuleInfo::ImportInfo info
;
336 info
.extension_id
= id_
;
337 pending_modules_
.push_back(info
);
339 total_modules_
= pending_modules_
.size();
341 std::set
<std::string
> ids
;
342 std::list
<SharedModuleInfo::ImportInfo
>::const_iterator i
;
343 for (i
= pending_modules_
.begin(); i
!= pending_modules_
.end(); ++i
) {
344 ids
.insert(i
->extension_id
);
346 InstallVerifier::Get(profile_
)->AddProvisional(ids
);
349 if (!approval_
->manifest
->value()->GetString(manifest_keys::kName
, &name
)) {
352 extensions::InstallTracker
* tracker
=
353 extensions::InstallTrackerFactory::GetForBrowserContext(profile_
);
354 extensions::InstallObserver::ExtensionInstallParams
params(
357 approval_
->installing_icon
,
358 approval_
->manifest
->is_app(),
359 approval_
->manifest
->is_platform_app());
360 params
.is_ephemeral
= approval_
->is_ephemeral
;
361 tracker
->OnBeginExtensionInstall(params
);
363 tracker
->OnBeginExtensionDownload(id_
);
365 // TODO(crbug.com/305343): Query manifest of dependencies before
366 // downloading & installing those dependencies.
367 DownloadNextPendingModule();
370 void WebstoreInstaller::Observe(int type
,
371 const content::NotificationSource
& source
,
372 const content::NotificationDetails
& details
) {
374 case extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR
: {
375 CrxInstaller
* crx_installer
= content::Source
<CrxInstaller
>(source
).ptr();
376 CHECK(crx_installer
);
377 if (crx_installer
!= crx_installer_
.get())
380 // TODO(rdevlin.cronin): Continue removing std::string errors and
381 // replacing with base::string16. See crbug.com/71980.
382 const extensions::CrxInstallError
* error
=
383 content::Details
<const extensions::CrxInstallError
>(details
).ptr();
384 const std::string utf8_error
= base::UTF16ToUTF8(error
->message());
385 crx_installer_
= NULL
;
386 // ReportFailure releases a reference to this object so it must be the
387 // last operation in this method.
388 ReportFailure(utf8_error
, FAILURE_REASON_OTHER
);
397 void WebstoreInstaller::OnExtensionInstalled(
398 content::BrowserContext
* browser_context
,
399 const Extension
* extension
,
401 CHECK(profile_
->IsSameProfile(Profile::FromBrowserContext(browser_context
)));
402 if (pending_modules_
.empty())
404 SharedModuleInfo::ImportInfo info
= pending_modules_
.front();
405 if (extension
->id() != info
.extension_id
)
407 pending_modules_
.pop_front();
409 // Clean up local state from the current download.
410 if (download_item_
) {
411 download_item_
->RemoveObserver(this);
412 download_item_
->Remove();
413 download_item_
= NULL
;
415 crx_installer_
= NULL
;
417 if (pending_modules_
.empty()) {
418 CHECK_EQ(extension
->id(), id_
);
421 const Version
version_required(info
.minimum_version
);
422 if (version_required
.IsValid() &&
423 extension
->version()->CompareTo(version_required
) < 0) {
424 // It should not happen, CrxInstaller will make sure the version is
425 // equal or newer than version_required.
426 ReportFailure(kDependencyNotFoundError
,
427 FAILURE_REASON_DEPENDENCY_NOT_FOUND
);
428 } else if (!SharedModuleInfo::IsSharedModule(extension
)) {
429 // It should not happen, CrxInstaller will make sure it is a shared
431 ReportFailure(kDependencyNotSharedModuleError
,
432 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE
);
434 DownloadNextPendingModule();
439 void WebstoreInstaller::InvalidateDelegate() {
443 void WebstoreInstaller::SetDownloadDirectoryForTests(
444 base::FilePath
* directory
) {
445 g_download_directory_for_tests
= directory
;
448 WebstoreInstaller::~WebstoreInstaller() {
449 if (download_item_
) {
450 download_item_
->RemoveObserver(this);
451 download_item_
= NULL
;
455 void WebstoreInstaller::OnDownloadStarted(
456 const std::string
& extension_id
,
458 content::DownloadInterruptReason interrupt_reason
) {
460 DCHECK_NE(content::DOWNLOAD_INTERRUPT_REASON_NONE
, interrupt_reason
);
461 ReportFailure(content::DownloadInterruptReasonToString(interrupt_reason
),
462 FAILURE_REASON_OTHER
);
467 for (const auto& module
: pending_modules_
) {
468 if (extension_id
== module
.extension_id
) {
474 // If this extension is not pending, it means another installer has
475 // installed this extension and triggered OnExtensionInstalled(). In this
476 // case, either it was the main module and success has already been
477 // reported, or it was a dependency and either failed (ie. wrong version) or
478 // the next download was triggered. In any case, the only thing that needs
479 // to be done is to stop this download.
484 DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE
, interrupt_reason
);
485 DCHECK(!pending_modules_
.empty());
486 download_item_
= item
;
487 download_item_
->AddObserver(this);
488 if (pending_modules_
.size() > 1) {
489 // We are downloading a shared module. We need create an approval for it.
490 scoped_ptr
<Approval
> approval
= Approval::CreateForSharedModule(profile_
);
491 const SharedModuleInfo::ImportInfo
& info
= pending_modules_
.front();
492 approval
->extension_id
= info
.extension_id
;
493 const Version
version_required(info
.minimum_version
);
495 if (version_required
.IsValid()) {
496 approval
->minimum_version
.reset(
497 new Version(version_required
));
499 download_item_
->SetUserData(kApprovalKey
, approval
.release());
501 // It is for the main module of the extension. We should use the provided
504 download_item_
->SetUserData(kApprovalKey
, approval_
.release());
507 if (!download_started_
) {
509 delegate_
->OnExtensionDownloadStarted(id_
, download_item_
);
510 download_started_
= true;
514 void WebstoreInstaller::OnDownloadUpdated(DownloadItem
* download
) {
515 CHECK_EQ(download_item_
, download
);
517 switch (download
->GetState()) {
518 case DownloadItem::CANCELLED
:
519 ReportFailure(kDownloadCanceledError
, FAILURE_REASON_CANCELLED
);
521 case DownloadItem::INTERRUPTED
:
522 RecordInterrupt(download
);
524 GetErrorMessageForDownloadInterrupt(download
->GetLastReason()),
525 FAILURE_REASON_OTHER
);
527 case DownloadItem::COMPLETE
:
528 // Wait for other notifications if the download is really an extension.
529 if (!download_crx_util::IsExtensionDownload(*download
)) {
530 ReportFailure(kInvalidDownloadError
, FAILURE_REASON_OTHER
);
532 if (crx_installer_
.get())
533 return; // DownloadItemImpl calls the observer twice, ignore it.
534 StartCrxInstaller(*download
);
536 if (pending_modules_
.size() == 1) {
537 // The download is the last module - the extension main module.
539 delegate_
->OnExtensionDownloadProgress(id_
, download
);
540 extensions::InstallTracker
* tracker
=
541 extensions::InstallTrackerFactory::GetForBrowserContext(profile_
);
542 tracker
->OnDownloadProgress(id_
, 100);
545 // Stop the progress timer if it's running.
546 download_progress_timer_
.Stop();
548 case DownloadItem::IN_PROGRESS
: {
549 if (delegate_
&& pending_modules_
.size() == 1) {
550 // Only report download progress for the main module to |delegrate_|.
551 delegate_
->OnExtensionDownloadProgress(id_
, download
);
553 UpdateDownloadProgress();
557 // Continue listening if the download is not in one of the above states.
562 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem
* download
) {
563 CHECK_EQ(download_item_
, download
);
564 download_item_
->RemoveObserver(this);
565 download_item_
= NULL
;
568 void WebstoreInstaller::DownloadNextPendingModule() {
569 CHECK(!pending_modules_
.empty());
570 if (pending_modules_
.size() == 1) {
571 DCHECK_EQ(id_
, pending_modules_
.front().extension_id
);
572 DownloadCrx(id_
, install_source_
);
574 DownloadCrx(pending_modules_
.front().extension_id
, INSTALL_SOURCE_OTHER
);
578 void WebstoreInstaller::DownloadCrx(
579 const std::string
& extension_id
,
580 InstallSource source
) {
581 download_url_
= GetWebstoreInstallURL(extension_id
, source
);
582 MaybeAppendAuthUserParameter(approval_
->authuser
, &download_url_
);
584 base::FilePath user_data_dir
;
585 PathService::Get(chrome::DIR_USER_DATA
, &user_data_dir
);
586 base::FilePath download_path
= user_data_dir
.Append(kWebstoreDownloadFolder
);
588 base::FilePath
download_directory(g_download_directory_for_tests
?
589 *g_download_directory_for_tests
: download_path
);
591 #if defined(OS_CHROMEOS)
592 // Do not use drive for extension downloads.
593 if (drive::util::IsUnderDriveMountPoint(download_directory
)) {
594 download_directory
= DownloadPrefs::FromBrowserContext(
595 profile_
)->GetDefaultDownloadDirectoryForProfile();
599 BrowserThread::PostTask(
600 BrowserThread::FILE, FROM_HERE
,
601 base::Bind(&GetDownloadFilePath
, download_directory
, extension_id
,
602 base::Bind(&WebstoreInstaller::StartDownload
, this, extension_id
)));
605 // http://crbug.com/165634
606 // http://crbug.com/126013
607 // The current working theory is that one of the many pointers dereferenced in
608 // here is occasionally deleted before all of its referers are nullified,
609 // probably in a callback race. After this comment is released, the crash
610 // reports should narrow down exactly which pointer it is. Collapsing all the
611 // early-returns into a single branch makes it hard to see exactly which pointer
613 void WebstoreInstaller::StartDownload(const std::string
& extension_id
,
614 const base::FilePath
& file
) {
615 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
618 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
622 DownloadManager
* download_manager
=
623 BrowserContext::GetDownloadManager(profile_
);
624 if (!download_manager
) {
625 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
629 content::WebContents
* contents
= web_contents();
631 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
634 if (!contents
->GetRenderProcessHost()) {
635 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
638 if (!contents
->GetRenderViewHost()) {
639 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
643 content::NavigationController
& controller
= contents
->GetController();
644 if (!controller
.GetBrowserContext()) {
645 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
648 if (!controller
.GetBrowserContext()->GetResourceContext()) {
649 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
653 // The download url for the given extension is contained in |download_url_|.
654 // We will navigate the current tab to this url to start the download. The
655 // download system will then pass the crx to the CrxInstaller.
656 RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER
);
657 int render_process_host_id
= contents
->GetRenderProcessHost()->GetID();
658 int render_view_host_routing_id
=
659 contents
->GetRenderViewHost()->GetRoutingID();
660 content::ResourceContext
* resource_context
=
661 controller
.GetBrowserContext()->GetResourceContext();
662 scoped_ptr
<DownloadUrlParameters
> params(new DownloadUrlParameters(
664 render_process_host_id
,
665 render_view_host_routing_id
,
667 params
->set_file_path(file
);
668 if (controller
.GetVisibleEntry())
669 params
->set_referrer(content::Referrer::SanitizeForRequest(
670 download_url_
, content::Referrer(controller
.GetVisibleEntry()->GetURL(),
671 blink::WebReferrerPolicyDefault
)));
672 params
->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted
,
675 download_manager
->DownloadUrl(params
.Pass());
678 void WebstoreInstaller::UpdateDownloadProgress() {
679 // If the download has gone away, or isn't in progress (in which case we can't
680 // give a good progress estimate), stop any running timers and return.
681 if (!download_item_
||
682 download_item_
->GetState() != DownloadItem::IN_PROGRESS
) {
683 download_progress_timer_
.Stop();
687 int percent
= download_item_
->PercentComplete();
688 // Only report progress if percent is more than 0 or we have finished
689 // downloading at least one of the pending modules.
690 int finished_modules
= total_modules_
- pending_modules_
.size();
691 if (finished_modules
> 0 && percent
< 0)
694 percent
= (percent
+ (finished_modules
* 100)) / total_modules_
;
695 extensions::InstallTracker
* tracker
=
696 extensions::InstallTrackerFactory::GetForBrowserContext(profile_
);
697 tracker
->OnDownloadProgress(id_
, percent
);
700 // If there's enough time remaining on the download to warrant an update,
701 // set the timer (overwriting any current timers). Otherwise, stop the
703 base::TimeDelta time_remaining
;
704 if (download_item_
->TimeRemaining(&time_remaining
) &&
706 base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold
)) {
707 download_progress_timer_
.Start(
709 base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold
),
711 &WebstoreInstaller::UpdateDownloadProgress
);
713 download_progress_timer_
.Stop();
717 void WebstoreInstaller::StartCrxInstaller(const DownloadItem
& download
) {
718 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
719 DCHECK(!crx_installer_
.get());
721 // The clock may be backward, e.g. daylight savings time just happenned.
722 if (download
.GetEndTime() >= download
.GetStartTime()) {
723 UMA_HISTOGRAM_TIMES("Extensions.WebstoreDownload.FileDownload",
724 download
.GetEndTime() - download
.GetStartTime());
726 ExtensionService
* service
= ExtensionSystem::Get(profile_
)->
730 const Approval
* approval
= GetAssociatedApproval(download
);
733 crx_installer_
= download_crx_util::CreateCrxInstaller(profile_
, download
);
735 crx_installer_
->set_expected_id(approval
->extension_id
);
736 crx_installer_
->set_is_gallery_install(true);
737 crx_installer_
->set_allow_silent_install(true);
739 crx_installer_
->InstallCrx(download
.GetFullPath());
742 void WebstoreInstaller::ReportFailure(const std::string
& error
,
743 FailureReason reason
) {
745 delegate_
->OnExtensionInstallFailure(id_
, error
, reason
);
749 extensions::InstallTracker
* tracker
=
750 extensions::InstallTrackerFactory::GetForBrowserContext(profile_
);
751 tracker
->OnInstallFailure(id_
);
753 Release(); // Balanced in Start().
756 void WebstoreInstaller::ReportSuccess() {
758 delegate_
->OnExtensionInstallSuccess(id_
);
762 Release(); // Balanced in Start().
765 void WebstoreInstaller::RecordInterrupt(const DownloadItem
* download
) const {
766 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.WebstoreDownload.InterruptReason",
767 download
->GetLastReason());
769 // Use logarithmic bin sizes up to 1 TB.
770 const int kNumBuckets
= 30;
771 const int64 kMaxSizeKb
= 1 << kNumBuckets
;
772 UMA_HISTOGRAM_CUSTOM_COUNTS(
773 "Extensions.WebstoreDownload.InterruptReceivedKBytes",
774 download
->GetReceivedBytes() / 1024,
778 int64 total_bytes
= download
->GetTotalBytes();
779 if (total_bytes
>= 0) {
780 UMA_HISTOGRAM_CUSTOM_COUNTS(
781 "Extensions.WebstoreDownload.InterruptTotalKBytes",
787 UMA_HISTOGRAM_BOOLEAN(
788 "Extensions.WebstoreDownload.InterruptTotalSizeUnknown",
792 } // namespace extensions