This source file includes following definitions.
- SanitizeDays
- CalculatePingDays
- CalculateActivePingDays
- request_ids
- download_url
- extension_cache_
- DetermineFirstCheckDelay
- Start
- Stop
- ScheduleNextCheck
- TimerFired
- CheckSoon
- WillCheckSoon
- DoCheckSoon
- AddToDownloader
- CheckNow
- CheckExtensionSoon
- ExtensionCheckFinished
- OnExtensionDownloadFailed
- OnExtensionDownloadFinished
- GetPingDataForExtension
- GetUpdateUrlData
- IsExtensionPending
- GetExtensionExistingVersion
- UpdatePingData
- MaybeInstallCRXFile
- Observe
- NotifyStarted
- NotifyIfFinished
#include "chrome/browser/extensions/updater/extension_updater.h"
#include <algorithm>
#include <set>
#include <vector>
#include "base/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/module/module.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/pending_extension_manager.h"
#include "chrome/browser/extensions/updater/extension_downloader.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "crypto/sha2.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest.h"
using base::RandDouble;
using base::RandInt;
using base::Time;
using base::TimeDelta;
using content::BrowserThread;
typedef extensions::ExtensionDownloaderDelegate::Error Error;
typedef extensions::ExtensionDownloaderDelegate::PingResult PingResult;
namespace {
const int kStartupWaitSeconds = 60 * 5;
#if defined(NDEBUG)
const int kMinUpdateFrequencySeconds = 30;
#endif
const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7;
const int kMinUpdateThrottleTime = 5;
int SanitizeDays(int days) {
if (days < 0)
return 0;
return days;
}
int CalculatePingDays(const Time& last_ping_day) {
int days = extensions::ManifestFetchData::kNeverPinged;
if (!last_ping_day.is_null()) {
days = SanitizeDays((Time::Now() - last_ping_day).InDays());
}
return days;
}
int CalculateActivePingDays(const Time& last_active_ping_day,
bool hasActiveBit) {
if (!hasActiveBit)
return 0;
if (last_active_ping_day.is_null())
return extensions::ManifestFetchData::kNeverPinged;
return SanitizeDays((Time::Now() - last_active_ping_day).InDays());
}
}
namespace extensions {
ExtensionUpdater::CheckParams::CheckParams()
: install_immediately(false) {}
ExtensionUpdater::CheckParams::~CheckParams() {}
ExtensionUpdater::FetchedCRXFile::FetchedCRXFile(
const std::string& i,
const base::FilePath& p,
bool file_ownership_passed,
const GURL& u,
const std::set<int>& request_ids)
: extension_id(i),
path(p),
file_ownership_passed(file_ownership_passed),
download_url(u),
request_ids(request_ids) {}
ExtensionUpdater::FetchedCRXFile::FetchedCRXFile()
: path(), file_ownership_passed(true), download_url() {}
ExtensionUpdater::FetchedCRXFile::~FetchedCRXFile() {}
ExtensionUpdater::InProgressCheck::InProgressCheck()
: install_immediately(false) {}
ExtensionUpdater::InProgressCheck::~InProgressCheck() {}
struct ExtensionUpdater::ThrottleInfo {
ThrottleInfo()
: in_progress(true),
throttle_delay(kMinUpdateThrottleTime),
check_start(Time::Now()) {}
bool in_progress;
int throttle_delay;
Time check_start;
};
ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service,
ExtensionPrefs* extension_prefs,
PrefService* prefs,
Profile* profile,
int frequency_seconds,
ExtensionCache* cache)
: alive_(false),
weak_ptr_factory_(this),
service_(service), frequency_seconds_(frequency_seconds),
will_check_soon_(false), extension_prefs_(extension_prefs),
prefs_(prefs), profile_(profile),
next_request_id_(0),
crx_install_is_running_(false),
extension_cache_(cache) {
DCHECK_GE(frequency_seconds_, 5);
DCHECK_LE(frequency_seconds_, kMaxUpdateFrequencySeconds);
#if defined(NDEBUG)
frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds);
#endif
frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds);
registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
content::NotificationService::AllBrowserContextsAndSources());
}
ExtensionUpdater::~ExtensionUpdater() {
Stop();
}
TimeDelta ExtensionUpdater::DetermineFirstCheckDelay() {
DCHECK(alive_);
if (frequency_seconds_ < kStartupWaitSeconds)
return TimeDelta::FromSeconds(frequency_seconds_);
if (!prefs_->HasPrefPath(pref_names::kNextUpdateCheck))
return TimeDelta::FromSeconds(frequency_seconds_);
Time now = Time::Now();
Time last = Time::FromInternalValue(prefs_->GetInt64(
pref_names::kLastUpdateCheck));
int days = (now - last).InDays();
if (days >= 30) {
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
kStartupWaitSeconds * 2));
} else if (days >= 14) {
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 2,
kStartupWaitSeconds * 4));
} else if (days >= 3) {
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 4,
kStartupWaitSeconds * 8));
}
Time saved_next = Time::FromInternalValue(prefs_->GetInt64(
pref_names::kNextUpdateCheck));
Time earliest = now + TimeDelta::FromSeconds(kStartupWaitSeconds);
if (saved_next >= earliest) {
return saved_next - now;
} else {
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
frequency_seconds_));
}
}
void ExtensionUpdater::Start() {
DCHECK(!alive_);
DCHECK(service_);
DCHECK(extension_prefs_);
DCHECK(prefs_);
DCHECK(profile_);
DCHECK(!weak_ptr_factory_.HasWeakPtrs());
alive_ = true;
ScheduleNextCheck(DetermineFirstCheckDelay());
}
void ExtensionUpdater::Stop() {
weak_ptr_factory_.InvalidateWeakPtrs();
alive_ = false;
service_ = NULL;
extension_prefs_ = NULL;
prefs_ = NULL;
profile_ = NULL;
timer_.Stop();
will_check_soon_ = false;
downloader_.reset();
}
void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) {
DCHECK(alive_);
DCHECK(!timer_.IsRunning());
DCHECK(target_delay >= TimeDelta::FromSeconds(1));
double delay_ms = target_delay.InMillisecondsF();
double jitter_factor = (RandDouble() * .2) - 0.1;
delay_ms += delay_ms * jitter_factor;
TimeDelta actual_delay = TimeDelta::FromMilliseconds(
static_cast<int64>(delay_ms));
Time next = Time::Now() + actual_delay;
prefs_->SetInt64(pref_names::kNextUpdateCheck, next.ToInternalValue());
timer_.Start(FROM_HERE, actual_delay, this, &ExtensionUpdater::TimerFired);
}
void ExtensionUpdater::TimerFired() {
DCHECK(alive_);
CheckNow(default_params_);
if (frequency_seconds_ == extensions::kDefaultUpdateFrequencySeconds) {
Time last = Time::FromInternalValue(prefs_->GetInt64(
pref_names::kLastUpdateCheck));
if (last.ToInternalValue() != 0) {
UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.UpdateCheckGap",
(Time::Now() - last).InMinutes(),
TimeDelta::FromSeconds(kStartupWaitSeconds).InMinutes(),
TimeDelta::FromDays(40).InMinutes(),
50);
}
}
int64 now = Time::Now().ToInternalValue();
prefs_->SetInt64(pref_names::kLastUpdateCheck, now);
ScheduleNextCheck(TimeDelta::FromSeconds(frequency_seconds_));
}
void ExtensionUpdater::CheckSoon() {
DCHECK(alive_);
if (will_check_soon_)
return;
if (BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&ExtensionUpdater::DoCheckSoon,
weak_ptr_factory_.GetWeakPtr()))) {
will_check_soon_ = true;
} else {
NOTREACHED();
}
}
bool ExtensionUpdater::WillCheckSoon() const {
return will_check_soon_;
}
void ExtensionUpdater::DoCheckSoon() {
DCHECK(will_check_soon_);
CheckNow(default_params_);
will_check_soon_ = false;
}
void ExtensionUpdater::AddToDownloader(
const ExtensionSet* extensions,
const std::list<std::string>& pending_ids,
int request_id) {
InProgressCheck& request = requests_in_progress_[request_id];
for (ExtensionSet::const_iterator extension_iter = extensions->begin();
extension_iter != extensions->end(); ++extension_iter) {
const Extension& extension = *extension_iter->get();
if (!Manifest::IsAutoUpdateableLocation(extension.location())) {
VLOG(2) << "Extension " << extension.id() << " is not auto updateable";
continue;
}
std::list<std::string>::const_iterator pending_id_iter = std::find(
pending_ids.begin(), pending_ids.end(), extension.id());
if (pending_id_iter == pending_ids.end()) {
if (downloader_->AddExtension(extension, request_id))
request.in_progress_ids_.push_back(extension.id());
}
}
}
void ExtensionUpdater::CheckNow(const CheckParams& params) {
int request_id = next_request_id_++;
VLOG(2) << "Starting update check " << request_id;
if (params.ids.empty())
NotifyStarted();
DCHECK(alive_);
InProgressCheck& request = requests_in_progress_[request_id];
request.callback = params.callback;
request.install_immediately = params.install_immediately;
if (!downloader_.get()) {
downloader_.reset(
new ExtensionDownloader(this, profile_->GetRequestContext()));
}
const PendingExtensionManager* pending_extension_manager =
service_->pending_extension_manager();
std::list<std::string> pending_ids;
if (params.ids.empty()) {
pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_ids);
std::list<std::string>::const_iterator iter;
for (iter = pending_ids.begin(); iter != pending_ids.end(); ++iter) {
const PendingExtensionInfo* info = pending_extension_manager->GetById(
*iter);
if (!Manifest::IsAutoUpdateableLocation(info->install_source())) {
VLOG(2) << "Extension " << *iter << " is not auto updateable";
continue;
}
if (downloader_->AddPendingExtension(*iter, info->update_url(),
request_id))
request.in_progress_ids_.push_back(*iter);
}
ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
AddToDownloader(®istry->enabled_extensions(), pending_ids, request_id);
AddToDownloader(®istry->disabled_extensions(), pending_ids, request_id);
} else {
for (std::list<std::string>::const_iterator it = params.ids.begin();
it != params.ids.end(); ++it) {
const Extension* extension = service_->GetExtensionById(*it, true);
DCHECK(extension);
if (downloader_->AddExtension(*extension, request_id))
request.in_progress_ids_.push_back(extension->id());
}
}
bool noChecks = request.in_progress_ids_.empty();
downloader_->StartAllPending(extension_cache_);
if (noChecks)
NotifyIfFinished(request_id);
}
bool ExtensionUpdater::CheckExtensionSoon(const std::string& extension_id,
const FinishedCallback& callback) {
bool have_throttle_info = ContainsKey(throttle_info_, extension_id);
ThrottleInfo& info = throttle_info_[extension_id];
if (have_throttle_info) {
if (info.in_progress)
return false;
Time now = Time::Now();
Time last = info.check_start;
if (now < last) {
last = now;
info.check_start = now;
}
Time earliest = last + TimeDelta::FromSeconds(info.throttle_delay);
if (now < earliest)
return false;
info.check_start = now;
info.in_progress = true;
}
CheckParams params;
params.ids.push_back(extension_id);
params.callback = base::Bind(&ExtensionUpdater::ExtensionCheckFinished,
weak_ptr_factory_.GetWeakPtr(),
extension_id, callback);
CheckNow(params);
return true;
}
void ExtensionUpdater::ExtensionCheckFinished(
const std::string& extension_id,
const FinishedCallback& callback) {
std::map<std::string, ThrottleInfo>::iterator it =
throttle_info_.find(extension_id);
if (it != throttle_info_.end()) {
it->second.in_progress = false;
}
callback.Run();
}
void ExtensionUpdater::OnExtensionDownloadFailed(
const std::string& id,
Error error,
const PingResult& ping,
const std::set<int>& request_ids) {
DCHECK(alive_);
UpdatePingData(id, ping);
bool install_immediately = false;
for (std::set<int>::const_iterator it = request_ids.begin();
it != request_ids.end(); ++it) {
InProgressCheck& request = requests_in_progress_[*it];
install_immediately |= request.install_immediately;
request.in_progress_ids_.remove(id);
NotifyIfFinished(*it);
}
if (install_immediately && service_->GetPendingExtensionUpdate(id))
service_->FinishDelayedInstallation(id);
}
void ExtensionUpdater::OnExtensionDownloadFinished(
const std::string& id,
const base::FilePath& path,
bool file_ownership_passed,
const GURL& download_url,
const std::string& version,
const PingResult& ping,
const std::set<int>& request_ids) {
DCHECK(alive_);
UpdatePingData(id, ping);
VLOG(2) << download_url << " written to " << path.value();
FetchedCRXFile fetched(id, path, file_ownership_passed, download_url,
request_ids);
fetched_crx_files_.push(fetched);
MaybeInstallCRXFile();
}
bool ExtensionUpdater::GetPingDataForExtension(
const std::string& id,
ManifestFetchData::PingData* ping_data) {
DCHECK(alive_);
ping_data->rollcall_days = CalculatePingDays(
extension_prefs_->LastPingDay(id));
ping_data->is_enabled = service_->IsExtensionEnabled(id);
ping_data->active_days =
CalculateActivePingDays(extension_prefs_->LastActivePingDay(id),
extension_prefs_->GetActiveBit(id));
return true;
}
std::string ExtensionUpdater::GetUpdateUrlData(const std::string& id) {
DCHECK(alive_);
return extension::GetUpdateURLData(extension_prefs_, id);
}
bool ExtensionUpdater::IsExtensionPending(const std::string& id) {
DCHECK(alive_);
return service_->pending_extension_manager()->IsIdPending(id);
}
bool ExtensionUpdater::GetExtensionExistingVersion(const std::string& id,
std::string* version) {
DCHECK(alive_);
const Extension* extension = service_->GetExtensionById(id, true);
if (!extension)
return false;
const Extension* update = service_->GetPendingExtensionUpdate(id);
if (update)
*version = update->VersionString();
else
*version = extension->VersionString();
return true;
}
void ExtensionUpdater::UpdatePingData(const std::string& id,
const PingResult& ping_result) {
DCHECK(alive_);
if (ping_result.did_ping)
extension_prefs_->SetLastPingDay(id, ping_result.day_start);
if (extension_prefs_->GetActiveBit(id)) {
extension_prefs_->SetActiveBit(id, false);
extension_prefs_->SetLastActivePingDay(id, ping_result.day_start);
}
}
void ExtensionUpdater::MaybeInstallCRXFile() {
if (crx_install_is_running_ || fetched_crx_files_.empty())
return;
std::set<int> request_ids;
while (!fetched_crx_files_.empty() && !crx_install_is_running_) {
const FetchedCRXFile& crx_file = fetched_crx_files_.top();
VLOG(2) << "updating " << crx_file.extension_id
<< " with " << crx_file.path.value();
CrxInstaller* installer = NULL;
if (service_->UpdateExtension(crx_file.extension_id,
crx_file.path,
crx_file.file_ownership_passed,
crx_file.download_url,
&installer)) {
crx_install_is_running_ = true;
current_crx_file_ = crx_file;
for (std::set<int>::const_iterator it = crx_file.request_ids.begin();
it != crx_file.request_ids.end(); ++it) {
InProgressCheck& request = requests_in_progress_[*it];
if (request.install_immediately) {
installer->set_install_wait_for_idle(false);
break;
}
}
registrar_.Add(this,
chrome::NOTIFICATION_CRX_INSTALLER_DONE,
content::Source<CrxInstaller>(installer));
} else {
for (std::set<int>::const_iterator it = crx_file.request_ids.begin();
it != crx_file.request_ids.end(); ++it) {
InProgressCheck& request = requests_in_progress_[*it];
request.in_progress_ids_.remove(crx_file.extension_id);
}
request_ids.insert(crx_file.request_ids.begin(),
crx_file.request_ids.end());
}
fetched_crx_files_.pop();
}
for (std::set<int>::const_iterator it = request_ids.begin();
it != request_ids.end(); ++it) {
NotifyIfFinished(*it);
}
}
void ExtensionUpdater::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
registrar_.Remove(this,
chrome::NOTIFICATION_CRX_INSTALLER_DONE,
source);
crx_install_is_running_ = false;
const FetchedCRXFile& crx_file = current_crx_file_;
for (std::set<int>::const_iterator it = crx_file.request_ids.begin();
it != crx_file.request_ids.end(); ++it) {
InProgressCheck& request = requests_in_progress_[*it];
request.in_progress_ids_.remove(crx_file.extension_id);
NotifyIfFinished(*it);
}
MaybeInstallCRXFile();
break;
}
case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
const Extension* extension =
content::Details<const InstalledExtensionInfo>(details)->extension;
if (extension)
throttle_info_.erase(extension->id());
break;
}
default:
NOTREACHED();
}
}
void ExtensionUpdater::NotifyStarted() {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_UPDATING_STARTED,
content::Source<Profile>(profile_),
content::NotificationService::NoDetails());
}
void ExtensionUpdater::NotifyIfFinished(int request_id) {
DCHECK(ContainsKey(requests_in_progress_, request_id));
const InProgressCheck& request = requests_in_progress_[request_id];
if (request.in_progress_ids_.empty()) {
VLOG(2) << "Finished update check " << request_id;
if (!request.callback.is_null())
request.callback.Run();
requests_in_progress_.erase(request_id);
}
}
}