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"
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 "chrome/browser/browser_process.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/extension_system.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/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 "chrome/common/extensions/extension_constants.h"
38 #include "chrome/common/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/common/extension.h"
52 #include "extensions/common/manifest_constants.h"
53 #include "extensions/common/manifest_handlers/shared_module_info.h"
54 #include "net/base/escape.h"
57 #if defined(OS_CHROMEOS)
58 #include "chrome/browser/chromeos/drive/file_system_util.h"
61 using chrome::OmahaQueryParams
;
62 using content::BrowserContext
;
63 using content::BrowserThread
;
64 using content::DownloadItem
;
65 using content::DownloadManager
;
66 using content::NavigationController
;
67 using content::DownloadUrlParameters
;
71 // Key used to attach the Approval to the DownloadItem.
72 const char kApprovalKey
[] = "extensions.webstore_installer";
74 const char kInvalidIdError
[] = "Invalid id";
75 const char kDownloadDirectoryError
[] = "Could not create download directory";
76 const char kDownloadCanceledError
[] = "Download canceled";
77 const char kInstallCanceledError
[] = "Install canceled";
78 const char kDownloadInterruptedError
[] = "Download interrupted";
79 const char kInvalidDownloadError
[] =
80 "Download was not a valid extension or user script";
81 const char kDependencyNotFoundError
[] = "Dependency not found";
82 const char kDependencyNotSharedModuleError
[] =
83 "Dependency is not shared module";
84 const char kInlineInstallSource
[] = "inline";
85 const char kDefaultInstallSource
[] = "ondemand";
86 const char kAppLauncherInstallSource
[] = "applauncher";
88 // Folder for downloading crx files from the webstore. This is used so that the
89 // crx files don't go via the usual downloads folder.
90 const base::FilePath::CharType kWebstoreDownloadFolder
[] =
91 FILE_PATH_LITERAL("Webstore Downloads");
93 base::FilePath
* g_download_directory_for_tests
= NULL
;
95 // Must be executed on the FILE thread.
96 void GetDownloadFilePath(
97 const base::FilePath
& download_directory
,
98 const std::string
& id
,
99 const base::Callback
<void(const base::FilePath
&)>& callback
) {
100 // Ensure the download directory exists. TODO(asargent) - make this use
101 // common code from the downloads system.
102 if (!base::DirectoryExists(download_directory
)) {
103 if (!base::CreateDirectory(download_directory
)) {
104 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
105 base::Bind(callback
, base::FilePath()));
110 // This is to help avoid a race condition between when we generate this
111 // filename and when the download starts writing to it (think concurrently
112 // running sharded browser tests installing the same test file, for
114 std::string random_number
=
115 base::Uint64ToString(base::RandGenerator(kuint16max
));
117 base::FilePath file
=
118 download_directory
.AppendASCII(id
+ "_" + random_number
+ ".crx");
121 file_util::GetUniquePathNumber(file
, base::FilePath::StringType());
122 if (uniquifier
> 0) {
123 file
= file
.InsertBeforeExtensionASCII(
124 base::StringPrintf(" (%d)", uniquifier
));
127 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
128 base::Bind(callback
, file
));
131 bool UseSeparateWebstoreDownloadDirectory() {
132 const char kFieldTrial
[] = "WebstoreDownloadDirectory";
133 const char kSeparateDirectoryUnderUDD
[] = "SeparateDirectoryUnderUDD";
135 std::string field_trial_group
=
136 base::FieldTrialList::FindFullName(kFieldTrial
);
137 return field_trial_group
== kSeparateDirectoryUnderUDD
;
142 namespace extensions
{
145 GURL
WebstoreInstaller::GetWebstoreInstallURL(
146 const std::string
& extension_id
,
147 InstallSource source
) {
148 std::string install_source
;
150 case INSTALL_SOURCE_INLINE
:
151 install_source
= kInlineInstallSource
;
153 case INSTALL_SOURCE_APP_LAUNCHER
:
154 install_source
= kAppLauncherInstallSource
;
156 case INSTALL_SOURCE_OTHER
:
157 install_source
= kDefaultInstallSource
;
160 CommandLine
* cmd_line
= CommandLine::ForCurrentProcess();
161 if (cmd_line
->HasSwitch(switches::kAppsGalleryDownloadURL
)) {
162 std::string download_url
=
163 cmd_line
->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL
);
164 return GURL(base::StringPrintf(download_url
.c_str(),
165 extension_id
.c_str()));
167 std::vector
<std::string
> params
;
168 params
.push_back("id=" + extension_id
);
169 if (!install_source
.empty())
170 params
.push_back("installsource=" + install_source
);
171 params
.push_back("lang=" + g_browser_process
->GetApplicationLocale());
172 params
.push_back("uc");
173 std::string url_string
= extension_urls::GetWebstoreUpdateUrl().spec();
175 GURL
url(url_string
+ "?response=redirect&" +
176 OmahaQueryParams::Get(OmahaQueryParams::CRX
) + "&x=" +
177 net::EscapeQueryParamValue(JoinString(params
, '&'), true));
178 DCHECK(url
.is_valid());
183 void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
184 const std::string
& id
,
185 content::DownloadItem
* item
) {
188 void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
189 const std::string
& id
,
190 content::DownloadItem
* item
) {
193 WebstoreInstaller::Approval::Approval()
195 use_app_installed_bubble(false),
196 skip_post_install_ui(false),
197 skip_install_dialog(false),
198 enable_launcher(false),
199 manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT
),
200 is_ephemeral(false) {
203 scoped_ptr
<WebstoreInstaller::Approval
>
204 WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile
* profile
) {
205 scoped_ptr
<Approval
> result(new Approval());
206 result
->profile
= profile
;
207 return result
.Pass();
210 scoped_ptr
<WebstoreInstaller::Approval
>
211 WebstoreInstaller::Approval::CreateForSharedModule(Profile
* profile
) {
212 scoped_ptr
<Approval
> result(new Approval());
213 result
->profile
= profile
;
214 result
->skip_install_dialog
= true;
215 result
->manifest_check_level
= MANIFEST_CHECK_LEVEL_NONE
;
216 return result
.Pass();
219 scoped_ptr
<WebstoreInstaller::Approval
>
220 WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
222 const std::string
& extension_id
,
223 scoped_ptr
<base::DictionaryValue
> parsed_manifest
,
224 bool strict_manifest_check
) {
225 scoped_ptr
<Approval
> result(new Approval());
226 result
->extension_id
= extension_id
;
227 result
->profile
= profile
;
228 result
->manifest
= scoped_ptr
<Manifest
>(
229 new Manifest(Manifest::INVALID_LOCATION
,
230 scoped_ptr
<base::DictionaryValue
>(
231 parsed_manifest
->DeepCopy())));
232 result
->skip_install_dialog
= true;
233 result
->manifest_check_level
= strict_manifest_check
?
234 MANIFEST_CHECK_LEVEL_STRICT
: MANIFEST_CHECK_LEVEL_LOOSE
;
235 return result
.Pass();
238 WebstoreInstaller::Approval::~Approval() {}
240 const WebstoreInstaller::Approval
* WebstoreInstaller::GetAssociatedApproval(
241 const DownloadItem
& download
) {
242 return static_cast<const Approval
*>(download
.GetUserData(kApprovalKey
));
245 WebstoreInstaller::WebstoreInstaller(Profile
* profile
,
247 NavigationController
* controller
,
248 const std::string
& id
,
249 scoped_ptr
<Approval
> approval
,
250 InstallSource source
)
253 controller_(controller
),
255 install_source_(source
),
256 download_item_(NULL
),
257 approval_(approval
.release()),
259 download_started_(false) {
260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
263 registrar_
.Add(this, chrome::NOTIFICATION_CRX_INSTALLER_DONE
,
264 content::NotificationService::AllSources());
265 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED
,
266 content::Source
<Profile
>(profile
->GetOriginalProfile()));
267 registrar_
.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR
,
268 content::Source
<CrxInstaller
>(NULL
));
271 void WebstoreInstaller::Start() {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
273 AddRef(); // Balanced in ReportSuccess and ReportFailure.
275 if (!Extension::IdIsValid(id_
)) {
276 ReportFailure(kInvalidIdError
, FAILURE_REASON_OTHER
);
280 ExtensionService
* extension_service
=
281 ExtensionSystem::Get(profile_
)->extension_service();
282 if (approval_
.get() && approval_
->dummy_extension
) {
283 ExtensionService::ImportStatus status
=
284 extension_service
->CheckImports(approval_
->dummy_extension
,
285 &pending_modules_
, &pending_modules_
);
286 // For this case, it is because some imports are not shared modules.
287 if (status
== ExtensionService::IMPORT_STATUS_UNRECOVERABLE
) {
288 ReportFailure(kDependencyNotSharedModuleError
,
289 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE
);
294 // Add the extension main module into the list.
295 SharedModuleInfo::ImportInfo info
;
296 info
.extension_id
= id_
;
297 pending_modules_
.push_back(info
);
299 total_modules_
= pending_modules_
.size();
301 std::set
<std::string
> ids
;
302 std::list
<SharedModuleInfo::ImportInfo
>::const_iterator i
;
303 for (i
= pending_modules_
.begin(); i
!= pending_modules_
.end(); ++i
) {
304 ids
.insert(i
->extension_id
);
306 ExtensionSystem::Get(profile_
)->install_verifier()->AddProvisional(ids
);
308 // TODO(crbug.com/305343): Query manifest of dependencises before
309 // downloading & installing those dependencies.
310 DownloadNextPendingModule();
313 if (!approval_
->manifest
->value()->GetString(manifest_keys::kName
, &name
)) {
316 extensions::InstallTracker
* tracker
=
317 extensions::InstallTrackerFactory::GetForProfile(profile_
);
318 extensions::InstallObserver::ExtensionInstallParams
params(
321 approval_
->installing_icon
,
322 approval_
->manifest
->is_app(),
323 approval_
->manifest
->is_platform_app());
324 params
.is_ephemeral
= approval_
->is_ephemeral
;
325 tracker
->OnBeginExtensionInstall(params
);
328 void WebstoreInstaller::Observe(int type
,
329 const content::NotificationSource
& source
,
330 const content::NotificationDetails
& details
) {
332 case chrome::NOTIFICATION_CRX_INSTALLER_DONE
: {
333 const Extension
* extension
=
334 content::Details
<const Extension
>(details
).ptr();
335 CrxInstaller
* installer
= content::Source
<CrxInstaller
>(source
).ptr();
336 if (extension
== NULL
&& download_item_
!= NULL
&&
337 installer
->download_url() == download_item_
->GetURL() &&
338 installer
->profile()->IsSameProfile(profile_
)) {
339 ReportFailure(kInstallCanceledError
, FAILURE_REASON_CANCELLED
);
344 case chrome::NOTIFICATION_EXTENSION_INSTALLED
: {
345 CHECK(profile_
->IsSameProfile(content::Source
<Profile
>(source
).ptr()));
346 const Extension
* extension
=
347 content::Details
<const InstalledExtensionInfo
>(details
)->extension
;
348 if (pending_modules_
.empty())
350 SharedModuleInfo::ImportInfo info
= pending_modules_
.front();
351 if (extension
->id() != info
.extension_id
)
353 pending_modules_
.pop_front();
355 if (pending_modules_
.empty()) {
356 CHECK_EQ(extension
->id(), id_
);
359 const Version
version_required(info
.minimum_version
);
360 if (version_required
.IsValid() &&
361 extension
->version()->CompareTo(version_required
) < 0) {
362 // It should not happen, CrxInstaller will make sure the version is
363 // equal or newer than version_required.
364 ReportFailure(kDependencyNotFoundError
,
365 FAILURE_REASON_DEPENDENCY_NOT_FOUND
);
366 } else if (!SharedModuleInfo::IsSharedModule(extension
)) {
367 // It should not happen, CrxInstaller will make sure it is a shared
369 ReportFailure(kDependencyNotSharedModuleError
,
370 FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE
);
372 DownloadNextPendingModule();
378 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR
: {
379 CrxInstaller
* crx_installer
= content::Source
<CrxInstaller
>(source
).ptr();
380 CHECK(crx_installer
);
381 if (!profile_
->IsSameProfile(crx_installer
->profile()))
384 // TODO(rdevlin.cronin): Continue removing std::string errors and
385 // replacing with base::string16. See crbug.com/71980.
386 const base::string16
* error
=
387 content::Details
<const base::string16
>(details
).ptr();
388 const std::string utf8_error
= base::UTF16ToUTF8(*error
);
389 if (download_url_
== crx_installer
->original_download_url())
390 ReportFailure(utf8_error
, FAILURE_REASON_OTHER
);
399 void WebstoreInstaller::InvalidateDelegate() {
403 void WebstoreInstaller::SetDownloadDirectoryForTests(
404 base::FilePath
* directory
) {
405 g_download_directory_for_tests
= directory
;
408 WebstoreInstaller::~WebstoreInstaller() {
410 if (download_item_
) {
411 download_item_
->RemoveObserver(this);
412 download_item_
= NULL
;
416 void WebstoreInstaller::OnDownloadStarted(
418 content::DownloadInterruptReason interrupt_reason
) {
420 DCHECK_NE(content::DOWNLOAD_INTERRUPT_REASON_NONE
, interrupt_reason
);
421 ReportFailure(content::DownloadInterruptReasonToString(interrupt_reason
),
422 FAILURE_REASON_OTHER
);
426 DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE
, interrupt_reason
);
427 DCHECK(!pending_modules_
.empty());
428 download_item_
= item
;
429 download_item_
->AddObserver(this);
430 if (pending_modules_
.size() > 1) {
431 // We are downloading a shared module. We need create an approval for it.
432 scoped_ptr
<Approval
> approval
= Approval::CreateForSharedModule(profile_
);
433 const SharedModuleInfo::ImportInfo
& info
= pending_modules_
.front();
434 approval
->extension_id
= info
.extension_id
;
435 const Version
version_required(info
.minimum_version
);
437 if (version_required
.IsValid()) {
438 approval
->minimum_version
.reset(
439 new Version(version_required
));
441 download_item_
->SetUserData(kApprovalKey
, approval
.release());
443 // It is for the main module of the extension. We should use the provided
446 download_item_
->SetUserData(kApprovalKey
, approval_
.release());
449 if (!download_started_
) {
451 delegate_
->OnExtensionDownloadStarted(id_
, download_item_
);
452 download_started_
= true;
456 void WebstoreInstaller::OnDownloadUpdated(DownloadItem
* download
) {
457 CHECK_EQ(download_item_
, download
);
459 switch (download
->GetState()) {
460 case DownloadItem::CANCELLED
:
461 ReportFailure(kDownloadCanceledError
, FAILURE_REASON_CANCELLED
);
463 case DownloadItem::INTERRUPTED
:
464 RecordInterrupt(download
);
465 ReportFailure(kDownloadInterruptedError
, FAILURE_REASON_OTHER
);
467 case DownloadItem::COMPLETE
:
468 // Wait for other notifications if the download is really an extension.
469 if (!download_crx_util::IsExtensionDownload(*download
)) {
470 ReportFailure(kInvalidDownloadError
, FAILURE_REASON_OTHER
);
471 } else if (pending_modules_
.empty()) {
472 // The download is the last module - the extension main module.
474 delegate_
->OnExtensionDownloadProgress(id_
, download
);
475 extensions::InstallTracker
* tracker
=
476 extensions::InstallTrackerFactory::GetForProfile(profile_
);
477 tracker
->OnDownloadProgress(id_
, 100);
480 case DownloadItem::IN_PROGRESS
: {
481 if (delegate_
&& pending_modules_
.size() == 1) {
482 // Only report download progress for the main module to |delegrate_|.
483 delegate_
->OnExtensionDownloadProgress(id_
, download
);
485 int percent
= download
->PercentComplete();
486 // Only report progress if precent is more than 0
488 int finished_modules
= total_modules_
- pending_modules_
.size();
489 percent
= (percent
+ finished_modules
* 100) / total_modules_
;
490 extensions::InstallTracker
* tracker
=
491 extensions::InstallTrackerFactory::GetForProfile(profile_
);
492 tracker
->OnDownloadProgress(id_
, percent
);
497 // Continue listening if the download is not in one of the above states.
502 void WebstoreInstaller::OnDownloadDestroyed(DownloadItem
* download
) {
503 CHECK_EQ(download_item_
, download
);
504 download_item_
->RemoveObserver(this);
505 download_item_
= NULL
;
508 void WebstoreInstaller::DownloadNextPendingModule() {
509 CHECK(!pending_modules_
.empty());
510 if (pending_modules_
.size() == 1) {
511 DCHECK_EQ(id_
, pending_modules_
.front().extension_id
);
512 DownloadCrx(id_
, install_source_
);
514 DownloadCrx(pending_modules_
.front().extension_id
, INSTALL_SOURCE_OTHER
);
518 void WebstoreInstaller::DownloadCrx(
519 const std::string
& extension_id
,
520 InstallSource source
) {
521 download_url_
= GetWebstoreInstallURL(extension_id
, source
);
523 base::FilePath download_path
;
524 if (UseSeparateWebstoreDownloadDirectory()) {
525 base::FilePath user_data_dir
;
526 PathService::Get(chrome::DIR_USER_DATA
, &user_data_dir
);
527 download_path
= user_data_dir
.Append(kWebstoreDownloadFolder
);
529 download_path
= DownloadPrefs::FromDownloadManager(
530 BrowserContext::GetDownloadManager(profile_
))->DownloadPath();
533 base::FilePath
download_directory(g_download_directory_for_tests
?
534 *g_download_directory_for_tests
: download_path
);
536 #if defined(OS_CHROMEOS)
537 // Do not use drive for extension downloads.
538 if (drive::util::IsUnderDriveMountPoint(download_directory
)) {
539 download_directory
= DownloadPrefs::FromBrowserContext(
540 profile_
)->GetDefaultDownloadDirectoryForProfile();
544 BrowserThread::PostTask(
545 BrowserThread::FILE, FROM_HERE
,
546 base::Bind(&GetDownloadFilePath
, download_directory
, id_
,
547 base::Bind(&WebstoreInstaller::StartDownload
, this)));
550 // http://crbug.com/165634
551 // http://crbug.com/126013
552 // The current working theory is that one of the many pointers dereferenced in
553 // here is occasionally deleted before all of its referers are nullified,
554 // probably in a callback race. After this comment is released, the crash
555 // reports should narrow down exactly which pointer it is. Collapsing all the
556 // early-returns into a single branch makes it hard to see exactly which pointer
558 void WebstoreInstaller::StartDownload(const base::FilePath
& file
) {
559 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
561 DownloadManager
* download_manager
=
562 BrowserContext::GetDownloadManager(profile_
);
564 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
567 if (!download_manager
) {
568 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
572 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
575 if (!controller_
->GetWebContents()) {
576 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
579 if (!controller_
->GetWebContents()->GetRenderProcessHost()) {
580 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
583 if (!controller_
->GetWebContents()->GetRenderViewHost()) {
584 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
587 if (!controller_
->GetBrowserContext()) {
588 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
591 if (!controller_
->GetBrowserContext()->GetResourceContext()) {
592 ReportFailure(kDownloadDirectoryError
, FAILURE_REASON_OTHER
);
596 // The download url for the given extension is contained in |download_url_|.
597 // We will navigate the current tab to this url to start the download. The
598 // download system will then pass the crx to the CrxInstaller.
599 RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER
);
600 int render_process_host_id
=
601 controller_
->GetWebContents()->GetRenderProcessHost()->GetID();
602 int render_view_host_routing_id
=
603 controller_
->GetWebContents()->GetRenderViewHost()->GetRoutingID();
604 content::ResourceContext
* resource_context
=
605 controller_
->GetBrowserContext()->GetResourceContext();
606 scoped_ptr
<DownloadUrlParameters
> params(new DownloadUrlParameters(
608 render_process_host_id
,
609 render_view_host_routing_id
,
611 params
->set_file_path(file
);
612 if (controller_
->GetVisibleEntry())
613 params
->set_referrer(
614 content::Referrer(controller_
->GetVisibleEntry()->GetURL(),
615 blink::WebReferrerPolicyDefault
));
616 params
->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted
, this));
617 download_manager
->DownloadUrl(params
.Pass());
620 void WebstoreInstaller::ReportFailure(const std::string
& error
,
621 FailureReason reason
) {
623 delegate_
->OnExtensionInstallFailure(id_
, error
, reason
);
627 extensions::InstallTracker
* tracker
=
628 extensions::InstallTrackerFactory::GetForProfile(profile_
);
629 tracker
->OnInstallFailure(id_
);
631 Release(); // Balanced in Start().
634 void WebstoreInstaller::ReportSuccess() {
636 delegate_
->OnExtensionInstallSuccess(id_
);
640 Release(); // Balanced in Start().
643 void WebstoreInstaller::RecordInterrupt(const DownloadItem
* download
) const {
644 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.WebstoreDownload.InterruptReason",
645 download
->GetLastReason());
647 // Use logarithmic bin sizes up to 1 TB.
648 const int kNumBuckets
= 30;
649 const int64 kMaxSizeKb
= 1 << kNumBuckets
;
650 UMA_HISTOGRAM_CUSTOM_COUNTS(
651 "Extensions.WebstoreDownload.InterruptReceivedKBytes",
652 download
->GetReceivedBytes() / 1024,
656 int64 total_bytes
= download
->GetTotalBytes();
657 if (total_bytes
>= 0) {
658 UMA_HISTOGRAM_CUSTOM_COUNTS(
659 "Extensions.WebstoreDownload.InterruptTotalKBytes",
665 UMA_HISTOGRAM_BOOLEAN(
666 "Extensions.WebstoreDownload.InterruptTotalSizeUnknown",
670 } // namespace extensions