blob: a8d9de7f20eb6388befd956fe514f2707188a90b [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/webstore_installer.h"
#include <stddef.h>
#include <stdint.h>
#include <limits>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/path_service.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/download/download_crx_util.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_stats.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/install_tracker.h"
#include "chrome/browser/extensions/install_tracker_factory.h"
#include "chrome/browser/extensions/install_verifier.h"
#include "chrome/browser/extensions/shared_module_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/generated_resources.h"
#include "components/crx_file/id_util.h"
#include "components/update_client/update_query_params.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/download_save_info.h"
#include "content/public/browser/download_url_parameters.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install/crx_install_error.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/shared_module_info.h"
#include "net/base/escape.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/drive/file_system_util.h"
#endif
using content::BrowserContext;
using content::BrowserThread;
using content::DownloadItem;
using content::DownloadManager;
using content::NavigationController;
using content::DownloadUrlParameters;
namespace {
// Key used to attach the Approval to the DownloadItem.
const char kApprovalKey[] = "extensions.webstore_installer";
const char kInvalidIdError[] = "Invalid id";
const char kDownloadDirectoryError[] = "Could not create download directory";
const char kDownloadCanceledError[] = "Download canceled";
const char kDownloadInterruptedError[] = "Download interrupted";
const char kInvalidDownloadError[] =
"Download was not a valid extension or user script";
const char kDependencyNotFoundError[] = "Dependency not found";
const char kDependencyNotSharedModuleError[] =
"Dependency is not shared module";
const char kInlineInstallSource[] = "inline";
const char kDefaultInstallSource[] = "ondemand";
const char kAppLauncherInstallSource[] = "applauncher";
// TODO(rockot): Share this duplicated constant with the extension updater.
// See http://crbug.com/371398.
const char kAuthUserQueryKey[] = "authuser";
const size_t kTimeRemainingMinutesThreshold = 1u;
// Folder for downloading crx files from the webstore. This is used so that the
// crx files don't go via the usual downloads folder.
const base::FilePath::CharType kWebstoreDownloadFolder[] =
FILE_PATH_LITERAL("Webstore Downloads");
base::FilePath* g_download_directory_for_tests = NULL;
// Must be executed on the FILE thread.
void GetDownloadFilePath(
const base::FilePath& download_directory,
const std::string& id,
const base::Callback<void(const base::FilePath&)>& callback) {
// Ensure the download directory exists. TODO(asargent) - make this use
// common code from the downloads system.
if (!base::DirectoryExists(download_directory)) {
if (!base::CreateDirectory(download_directory)) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(callback, base::FilePath()));
return;
}
}
// This is to help avoid a race condition between when we generate this
// filename and when the download starts writing to it (think concurrently
// running sharded browser tests installing the same test file, for
// instance).
std::string random_number = base::Uint64ToString(
base::RandGenerator(std::numeric_limits<uint16_t>::max()));
base::FilePath file =
download_directory.AppendASCII(id + "_" + random_number + ".crx");
int uniquifier =
base::GetUniquePathNumber(file, base::FilePath::StringType());
if (uniquifier > 0) {
file = file.InsertBeforeExtensionASCII(
base::StringPrintf(" (%d)", uniquifier));
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::BindOnce(callback, file));
}
void MaybeAppendAuthUserParameter(const std::string& authuser, GURL* url) {
if (authuser.empty())
return;
std::string old_query = url->query();
url::Component query(0, old_query.length());
url::Component key, value;
// Ensure that the URL doesn't already specify an authuser parameter.
while (url::ExtractQueryKeyValue(
old_query.c_str(), &query, &key, &value)) {
std::string key_string = old_query.substr(key.begin, key.len);
if (key_string == kAuthUserQueryKey) {
return;
}
}
if (!old_query.empty()) {
old_query += "&";
}
std::string authuser_param = base::StringPrintf(
"%s=%s",
kAuthUserQueryKey,
authuser.c_str());
// TODO(rockot): Share this duplicated code with the extension updater.
// See http://crbug.com/371398.
std::string new_query_string = old_query + authuser_param;
url::Component new_query(0, new_query_string.length());
url::Replacements<char> replacements;
replacements.SetQuery(new_query_string.c_str(), new_query);
*url = url->ReplaceComponents(replacements);
}
std::string GetErrorMessageForDownloadInterrupt(
content::DownloadInterruptReason reason) {
switch (reason) {
case content::DOWNLOAD_INTERRUPT_REASON_SERVER_UNAUTHORIZED:
case content::DOWNLOAD_INTERRUPT_REASON_SERVER_FORBIDDEN:
return l10n_util::GetStringUTF8(IDS_WEBSTORE_DOWNLOAD_ACCESS_DENIED);
default:
break;
}
return kDownloadInterruptedError;
}
} // namespace
namespace extensions {
// static
GURL WebstoreInstaller::GetWebstoreInstallURL(
const std::string& extension_id,
InstallSource source) {
std::string install_source;
switch (source) {
case INSTALL_SOURCE_INLINE:
install_source = kInlineInstallSource;
break;
case INSTALL_SOURCE_APP_LAUNCHER:
install_source = kAppLauncherInstallSource;
break;
case INSTALL_SOURCE_OTHER:
install_source = kDefaultInstallSource;
}
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (cmd_line->HasSwitch(switches::kAppsGalleryDownloadURL)) {
std::string download_url =
cmd_line->GetSwitchValueASCII(switches::kAppsGalleryDownloadURL);
return GURL(base::StringPrintf(download_url.c_str(),
extension_id.c_str()));
}
std::vector<base::StringPiece> params;
std::string extension_param = "id=" + extension_id;
std::string installsource_param = "installsource=" + install_source;
params.push_back(extension_param);
if (!install_source.empty())
params.push_back(installsource_param);
params.push_back("uc");
std::string url_string = extension_urls::GetWebstoreUpdateUrl().spec();
GURL url(url_string + "?response=redirect&" +
update_client::UpdateQueryParams::Get(
update_client::UpdateQueryParams::CRX) +
"&x=" + net::EscapeQueryParamValue(base::JoinString(params, "&"),
true));
DCHECK(url.is_valid());
return url;
}
void WebstoreInstaller::Delegate::OnExtensionDownloadStarted(
const std::string& id,
content::DownloadItem* item) {
}
void WebstoreInstaller::Delegate::OnExtensionDownloadProgress(
const std::string& id,
content::DownloadItem* item) {
}
WebstoreInstaller::Approval::Approval()
: profile(NULL),
use_app_installed_bubble(false),
skip_post_install_ui(false),
skip_install_dialog(false),
manifest_check_level(MANIFEST_CHECK_LEVEL_STRICT) {
}
std::unique_ptr<WebstoreInstaller::Approval>
WebstoreInstaller::Approval::CreateWithInstallPrompt(Profile* profile) {
std::unique_ptr<Approval> result(new Approval());
result->profile = profile;
return result;
}
std::unique_ptr<WebstoreInstaller::Approval>
WebstoreInstaller::Approval::CreateForSharedModule(Profile* profile) {
std::unique_ptr<Approval> result(new Approval());
result->profile = profile;
result->skip_install_dialog = true;
result->skip_post_install_ui = true;
result->manifest_check_level = MANIFEST_CHECK_LEVEL_NONE;
return result;
}
std::unique_ptr<WebstoreInstaller::Approval>
WebstoreInstaller::Approval::CreateWithNoInstallPrompt(
Profile* profile,
const std::string& extension_id,
std::unique_ptr<base::DictionaryValue> parsed_manifest,
bool strict_manifest_check) {
std::unique_ptr<Approval> result(new Approval());
result->extension_id = extension_id;
result->profile = profile;
result->manifest = std::unique_ptr<Manifest>(new Manifest(
Manifest::INVALID_LOCATION,
std::unique_ptr<base::DictionaryValue>(parsed_manifest->DeepCopy())));
result->skip_install_dialog = true;
result->manifest_check_level = strict_manifest_check ?
MANIFEST_CHECK_LEVEL_STRICT : MANIFEST_CHECK_LEVEL_LOOSE;
return result;
}
WebstoreInstaller::Approval::~Approval() {}
const WebstoreInstaller::Approval* WebstoreInstaller::GetAssociatedApproval(
const DownloadItem& download) {
return static_cast<const Approval*>(download.GetUserData(kApprovalKey));
}
WebstoreInstaller::WebstoreInstaller(Profile* profile,
Delegate* delegate,
content::WebContents* web_contents,
const std::string& id,
std::unique_ptr<Approval> approval,
InstallSource source)
: content::WebContentsObserver(web_contents),
extension_registry_observer_(this),
profile_(profile),
delegate_(delegate),
id_(id),
install_source_(source),
download_item_(NULL),
approval_(approval.release()),
total_modules_(0),
download_started_(false) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(web_contents);
registrar_.Add(this,
extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR,
content::Source<CrxInstaller>(NULL));
extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
}
void WebstoreInstaller::Start() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
AddRef(); // Balanced in ReportSuccess and ReportFailure.
if (!crx_file::id_util::IdIsValid(id_)) {
ReportFailure(kInvalidIdError, FAILURE_REASON_OTHER);
return;
}
ExtensionService* extension_service =
ExtensionSystem::Get(profile_)->extension_service();
if (approval_.get() && approval_->dummy_extension.get()) {
extension_service->shared_module_service()->CheckImports(
approval_->dummy_extension.get(), &pending_modules_, &pending_modules_);
// Do not check the return value of CheckImports, the CRX installer
// will report appropriate error messages and fail to install if there
// is an import error.
}
// Add the extension main module into the list.
SharedModuleInfo::ImportInfo info;
info.extension_id = id_;
pending_modules_.push_back(info);
total_modules_ = pending_modules_.size();
std::set<std::string> ids;
std::list<SharedModuleInfo::ImportInfo>::const_iterator i;
for (i = pending_modules_.begin(); i != pending_modules_.end(); ++i) {
ids.insert(i->extension_id);
}
InstallVerifier::Get(profile_)->AddProvisional(ids);
std::string name;
if (!approval_->manifest->value()->GetString(manifest_keys::kName, &name)) {
NOTREACHED();
}
extensions::InstallTracker* tracker =
extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
extensions::InstallObserver::ExtensionInstallParams params(
id_,
name,
approval_->installing_icon,
approval_->manifest->is_app(),
approval_->manifest->is_platform_app());
tracker->OnBeginExtensionInstall(params);
tracker->OnBeginExtensionDownload(id_);
// TODO(crbug.com/305343): Query manifest of dependencies before
// downloading & installing those dependencies.
DownloadNextPendingModule();
}
void WebstoreInstaller::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_INSTALL_ERROR, type);
CrxInstaller* crx_installer = content::Source<CrxInstaller>(source).ptr();
CHECK(crx_installer);
if (crx_installer != crx_installer_.get())
return;
// TODO(rdevlin.cronin): Continue removing std::string errors and
// replacing with base::string16. See crbug.com/71980.
const extensions::CrxInstallError* error =
content::Details<const extensions::CrxInstallError>(details).ptr();
const std::string utf8_error = base::UTF16ToUTF8(error->message());
crx_installer_ = nullptr;
// ReportFailure releases a reference to this object so it must be the
// last operation in this method.
ReportFailure(utf8_error, FAILURE_REASON_OTHER);
}
void WebstoreInstaller::OnExtensionInstalled(
content::BrowserContext* browser_context,
const Extension* extension,
bool is_update) {
CHECK(profile_->IsSameProfile(Profile::FromBrowserContext(browser_context)));
if (pending_modules_.empty())
return;
SharedModuleInfo::ImportInfo info = pending_modules_.front();
if (extension->id() != info.extension_id)
return;
pending_modules_.pop_front();
// Clean up local state from the current download.
if (download_item_) {
download_item_->RemoveObserver(this);
download_item_->Remove();
download_item_ = NULL;
}
crx_installer_ = NULL;
if (pending_modules_.empty()) {
CHECK_EQ(extension->id(), id_);
ReportSuccess();
} else {
const base::Version version_required(info.minimum_version);
if (version_required.IsValid() &&
extension->version()->CompareTo(version_required) < 0) {
// It should not happen, CrxInstaller will make sure the version is
// equal or newer than version_required.
ReportFailure(kDependencyNotFoundError,
FAILURE_REASON_DEPENDENCY_NOT_FOUND);
} else if (!SharedModuleInfo::IsSharedModule(extension)) {
// It should not happen, CrxInstaller will make sure it is a shared
// module.
ReportFailure(kDependencyNotSharedModuleError,
FAILURE_REASON_DEPENDENCY_NOT_SHARED_MODULE);
} else {
DownloadNextPendingModule();
}
}
}
void WebstoreInstaller::InvalidateDelegate() {
delegate_ = NULL;
}
void WebstoreInstaller::SetDownloadDirectoryForTests(
base::FilePath* directory) {
g_download_directory_for_tests = directory;
}
WebstoreInstaller::~WebstoreInstaller() {
if (download_item_) {
download_item_->RemoveObserver(this);
download_item_ = NULL;
}
}
void WebstoreInstaller::OnDownloadStarted(
const std::string& extension_id,
DownloadItem* item,
content::DownloadInterruptReason interrupt_reason) {
if (!item || interrupt_reason != content::DOWNLOAD_INTERRUPT_REASON_NONE) {
if (item)
item->Remove();
ReportFailure(content::DownloadInterruptReasonToString(interrupt_reason),
FAILURE_REASON_OTHER);
return;
}
bool found = false;
for (const auto& module : pending_modules_) {
if (extension_id == module.extension_id) {
found = true;
break;
}
}
if (!found) {
// If this extension is not pending, it means another installer has
// installed this extension and triggered OnExtensionInstalled(). In this
// case, either it was the main module and success has already been
// reported, or it was a dependency and either failed (ie. wrong version) or
// the next download was triggered. In any case, the only thing that needs
// to be done is to stop this download.
item->Remove();
return;
}
DCHECK_EQ(content::DOWNLOAD_INTERRUPT_REASON_NONE, interrupt_reason);
DCHECK(!pending_modules_.empty());
download_item_ = item;
download_item_->AddObserver(this);
if (pending_modules_.size() > 1) {
// We are downloading a shared module. We need create an approval for it.
std::unique_ptr<Approval> approval =
Approval::CreateForSharedModule(profile_);
const SharedModuleInfo::ImportInfo& info = pending_modules_.front();
approval->extension_id = info.extension_id;
const base::Version version_required(info.minimum_version);
if (version_required.IsValid()) {
approval->minimum_version.reset(new base::Version(version_required));
}
download_item_->SetUserData(kApprovalKey, std::move(approval));
} else {
// It is for the main module of the extension. We should use the provided
// |approval_|.
if (approval_)
download_item_->SetUserData(kApprovalKey, std::move(approval_));
}
if (!download_started_) {
if (delegate_)
delegate_->OnExtensionDownloadStarted(id_, download_item_);
download_started_ = true;
}
}
void WebstoreInstaller::OnDownloadUpdated(DownloadItem* download) {
CHECK_EQ(download_item_, download);
switch (download->GetState()) {
case DownloadItem::CANCELLED:
ReportFailure(kDownloadCanceledError, FAILURE_REASON_CANCELLED);
break;
case DownloadItem::INTERRUPTED:
RecordInterrupt(download);
ReportFailure(
GetErrorMessageForDownloadInterrupt(download->GetLastReason()),
FAILURE_REASON_OTHER);
break;
case DownloadItem::COMPLETE:
// Stop the progress timer if it's running.
download_progress_timer_.Stop();
// Only wait for other notifications if the download is really
// an extension.
if (!download_crx_util::IsExtensionDownload(*download)) {
ReportFailure(kInvalidDownloadError, FAILURE_REASON_OTHER);
return;
}
if (crx_installer_.get())
return; // DownloadItemImpl calls the observer twice, ignore it.
StartCrxInstaller(*download);
if (pending_modules_.size() == 1) {
// The download is the last module - the extension main module.
if (delegate_)
delegate_->OnExtensionDownloadProgress(id_, download);
extensions::InstallTracker* tracker =
extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
tracker->OnDownloadProgress(id_, 100);
}
break;
case DownloadItem::IN_PROGRESS: {
if (delegate_ && pending_modules_.size() == 1) {
// Only report download progress for the main module to |delegrate_|.
delegate_->OnExtensionDownloadProgress(id_, download);
}
UpdateDownloadProgress();
break;
}
default:
// Continue listening if the download is not in one of the above states.
break;
}
}
void WebstoreInstaller::OnDownloadDestroyed(DownloadItem* download) {
CHECK_EQ(download_item_, download);
download_item_->RemoveObserver(this);
download_item_ = NULL;
}
void WebstoreInstaller::DownloadNextPendingModule() {
CHECK(!pending_modules_.empty());
if (pending_modules_.size() == 1) {
DCHECK_EQ(id_, pending_modules_.front().extension_id);
DownloadCrx(id_, install_source_);
} else {
DownloadCrx(pending_modules_.front().extension_id, INSTALL_SOURCE_OTHER);
}
}
void WebstoreInstaller::DownloadCrx(
const std::string& extension_id,
InstallSource source) {
download_url_ = GetWebstoreInstallURL(extension_id, source);
MaybeAppendAuthUserParameter(approval_->authuser, &download_url_);
base::FilePath user_data_dir;
PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
base::FilePath download_path = user_data_dir.Append(kWebstoreDownloadFolder);
base::FilePath download_directory(g_download_directory_for_tests ?
*g_download_directory_for_tests : download_path);
#if defined(OS_CHROMEOS)
// Do not use drive for extension downloads.
if (drive::util::IsUnderDriveMountPoint(download_directory)) {
download_directory = DownloadPrefs::FromBrowserContext(
profile_)->GetDefaultDownloadDirectoryForProfile();
}
#endif
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::BindOnce(
&GetDownloadFilePath, download_directory, extension_id,
base::Bind(&WebstoreInstaller::StartDownload, this, extension_id)));
}
// http://crbug.com/165634
// http://crbug.com/126013
// The current working theory is that one of the many pointers dereferenced in
// here is occasionally deleted before all of its referers are nullified,
// probably in a callback race. After this comment is released, the crash
// reports should narrow down exactly which pointer it is. Collapsing all the
// early-returns into a single branch makes it hard to see exactly which pointer
// it is.
void WebstoreInstaller::StartDownload(const std::string& extension_id,
const base::FilePath& file) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (file.empty()) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
DownloadManager* download_manager =
BrowserContext::GetDownloadManager(profile_);
if (!download_manager) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
content::WebContents* contents = web_contents();
if (!contents) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
if (!contents->GetRenderProcessHost()) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
if (!contents->GetRenderViewHost()) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
content::NavigationController& controller = contents->GetController();
if (!controller.GetBrowserContext()) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
if (!controller.GetBrowserContext()->GetResourceContext()) {
ReportFailure(kDownloadDirectoryError, FAILURE_REASON_OTHER);
return;
}
// The download url for the given extension is contained in |download_url_|.
// We will navigate the current tab to this url to start the download. The
// download system will then pass the crx to the CrxInstaller.
RecordDownloadSource(DOWNLOAD_INITIATED_BY_WEBSTORE_INSTALLER);
int render_process_host_id = contents->GetRenderProcessHost()->GetID();
int render_view_host_routing_id =
contents->GetRenderViewHost()->GetRoutingID();
content::RenderFrameHost* render_frame_host = contents->GetMainFrame();
content::StoragePartition* storage_partition =
BrowserContext::GetStoragePartition(profile_,
render_frame_host->GetSiteInstance());
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("webstore_installer", R"(
semantics {
sender: "Webstore Installer"
description: "Downloads an extension for installation."
trigger:
"User initiates a webstore extension installation flow, including "
"installing from the webstore, inline installation from a site, "
"re-installing a corrupted extension, and others."
data:
"The id of the extension to be installed and information about the "
"user's installation, including version, language, distribution "
"(Chrome vs Chromium), NaCl architecture, installation source (as "
"an enum), and accepted crx formats."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled. It is only activated if the user "
"triggers an extension installation."
chrome_policy {
ExtensionInstallBlacklist {
ExtensionInstallBlacklist: {
entries: '*'
}
}
}
})");
std::unique_ptr<DownloadUrlParameters> params(new DownloadUrlParameters(
download_url_, render_process_host_id, render_view_host_routing_id,
render_frame_host->GetRoutingID(),
storage_partition->GetURLRequestContext(), traffic_annotation));
params->set_file_path(file);
if (controller.GetVisibleEntry())
params->set_referrer(content::Referrer::SanitizeForRequest(
download_url_, content::Referrer(controller.GetVisibleEntry()->GetURL(),
blink::kWebReferrerPolicyDefault)));
params->set_callback(base::Bind(&WebstoreInstaller::OnDownloadStarted,
this,
extension_id));
download_manager->DownloadUrl(std::move(params));
}
void WebstoreInstaller::UpdateDownloadProgress() {
// If the download has gone away, or isn't in progress (in which case we can't
// give a good progress estimate), stop any running timers and return.
if (!download_item_ ||
download_item_->GetState() != DownloadItem::IN_PROGRESS) {
download_progress_timer_.Stop();
return;
}
int percent = download_item_->PercentComplete();
// Only report progress if percent is more than 0 or we have finished
// downloading at least one of the pending modules.
int finished_modules = total_modules_ - pending_modules_.size();
if (finished_modules > 0 && percent < 0)
percent = 0;
if (percent >= 0) {
percent = (percent + (finished_modules * 100)) / total_modules_;
extensions::InstallTracker* tracker =
extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
tracker->OnDownloadProgress(id_, percent);
}
// If there's enough time remaining on the download to warrant an update,
// set the timer (overwriting any current timers). Otherwise, stop the
// timer.
base::TimeDelta time_remaining;
if (download_item_->TimeRemaining(&time_remaining) &&
time_remaining >
base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold)) {
download_progress_timer_.Start(
FROM_HERE,
base::TimeDelta::FromSeconds(kTimeRemainingMinutesThreshold),
this,
&WebstoreInstaller::UpdateDownloadProgress);
} else {
download_progress_timer_.Stop();
}
}
void WebstoreInstaller::StartCrxInstaller(const DownloadItem& download) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!crx_installer_.get());
// The clock may be backward, e.g. daylight savings time just happenned.
if (download.GetEndTime() >= download.GetStartTime()) {
UMA_HISTOGRAM_TIMES("Extensions.WebstoreDownload.FileDownload",
download.GetEndTime() - download.GetStartTime());
}
ExtensionService* service = ExtensionSystem::Get(profile_)->
extension_service();
CHECK(service);
const Approval* approval = GetAssociatedApproval(download);
DCHECK(approval);
crx_installer_ = download_crx_util::CreateCrxInstaller(profile_, download);
crx_installer_->set_expected_id(approval->extension_id);
crx_installer_->set_is_gallery_install(true);
crx_installer_->set_allow_silent_install(true);
crx_installer_->InstallCrx(download.GetFullPath());
}
void WebstoreInstaller::ReportFailure(const std::string& error,
FailureReason reason) {
if (delegate_) {
delegate_->OnExtensionInstallFailure(id_, error, reason);
delegate_ = NULL;
}
extensions::InstallTracker* tracker =
extensions::InstallTrackerFactory::GetForBrowserContext(profile_);
tracker->OnInstallFailure(id_);
Release(); // Balanced in Start().
}
void WebstoreInstaller::ReportSuccess() {
if (delegate_) {
delegate_->OnExtensionInstallSuccess(id_);
delegate_ = NULL;
}
Release(); // Balanced in Start().
}
void WebstoreInstaller::RecordInterrupt(const DownloadItem* download) const {
UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.WebstoreDownload.InterruptReason",
download->GetLastReason());
// Use logarithmic bin sizes up to 1 TB.
const int kNumBuckets = 30;
const int64_t kMaxSizeKb = 1 << kNumBuckets;
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Extensions.WebstoreDownload.InterruptReceivedKBytes",
download->GetReceivedBytes() / 1024,
1,
kMaxSizeKb,
kNumBuckets);
int64_t total_bytes = download->GetTotalBytes();
if (total_bytes >= 0) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Extensions.WebstoreDownload.InterruptTotalKBytes",
total_bytes / 1024,
1,
kMaxSizeKb,
kNumBuckets);
}
UMA_HISTOGRAM_BOOLEAN(
"Extensions.WebstoreDownload.InterruptTotalSizeUnknown",
total_bytes <= 0);
}
} // namespace extensions