Home | History | Annotate | Download | only in updater
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/extensions/updater/extension_updater.h"
      6 
      7 #include <algorithm>
      8 #include <set>
      9 #include <vector>
     10 
     11 #include "base/bind.h"
     12 #include "base/logging.h"
     13 #include "base/metrics/histogram.h"
     14 #include "base/prefs/pref_service.h"
     15 #include "base/rand_util.h"
     16 #include "base/stl_util.h"
     17 #include "base/strings/string_number_conversions.h"
     18 #include "base/strings/string_split.h"
     19 #include "chrome/browser/chrome_notification_types.h"
     20 #include "chrome/browser/extensions/api/module/module.h"
     21 #include "chrome/browser/extensions/crx_installer.h"
     22 #include "chrome/browser/extensions/extension_service.h"
     23 #include "chrome/browser/extensions/pending_extension_manager.h"
     24 #include "chrome/browser/extensions/updater/extension_downloader.h"
     25 #include "chrome/browser/profiles/profile.h"
     26 #include "chrome/common/pref_names.h"
     27 #include "content/public/browser/browser_thread.h"
     28 #include "content/public/browser/notification_details.h"
     29 #include "content/public/browser/notification_service.h"
     30 #include "content/public/browser/notification_source.h"
     31 #include "crypto/sha2.h"
     32 #include "extensions/browser/extension_prefs.h"
     33 #include "extensions/browser/extension_registry.h"
     34 #include "extensions/browser/pref_names.h"
     35 #include "extensions/common/constants.h"
     36 #include "extensions/common/extension.h"
     37 #include "extensions/common/extension_set.h"
     38 #include "extensions/common/manifest.h"
     39 
     40 using base::RandDouble;
     41 using base::RandInt;
     42 using base::Time;
     43 using base::TimeDelta;
     44 using content::BrowserThread;
     45 
     46 typedef extensions::ExtensionDownloaderDelegate::Error Error;
     47 typedef extensions::ExtensionDownloaderDelegate::PingResult PingResult;
     48 
     49 namespace {
     50 
     51 // Wait at least 5 minutes after browser startup before we do any checks. If you
     52 // change this value, make sure to update comments where it is used.
     53 const int kStartupWaitSeconds = 60 * 5;
     54 
     55 // For sanity checking on update frequency - enforced in release mode only.
     56 #if defined(NDEBUG)
     57 const int kMinUpdateFrequencySeconds = 30;
     58 #endif
     59 const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7;  // 7 days
     60 
     61 // Require at least 5 seconds between consecutive non-succesful extension update
     62 // checks.
     63 const int kMinUpdateThrottleTime = 5;
     64 
     65 // When we've computed a days value, we want to make sure we don't send a
     66 // negative value (due to the system clock being set backwards, etc.), since -1
     67 // is a special sentinel value that means "never pinged", and other negative
     68 // values don't make sense.
     69 int SanitizeDays(int days) {
     70   if (days < 0)
     71     return 0;
     72   return days;
     73 }
     74 
     75 // Calculates the value to use for the ping days parameter.
     76 int CalculatePingDays(const Time& last_ping_day) {
     77   int days = extensions::ManifestFetchData::kNeverPinged;
     78   if (!last_ping_day.is_null()) {
     79     days = SanitizeDays((Time::Now() - last_ping_day).InDays());
     80   }
     81   return days;
     82 }
     83 
     84 int CalculateActivePingDays(const Time& last_active_ping_day,
     85                             bool hasActiveBit) {
     86   if (!hasActiveBit)
     87     return 0;
     88   if (last_active_ping_day.is_null())
     89     return extensions::ManifestFetchData::kNeverPinged;
     90   return SanitizeDays((Time::Now() - last_active_ping_day).InDays());
     91 }
     92 
     93 }  // namespace
     94 
     95 namespace extensions {
     96 
     97 ExtensionUpdater::CheckParams::CheckParams()
     98     : install_immediately(false) {}
     99 
    100 ExtensionUpdater::CheckParams::~CheckParams() {}
    101 
    102 ExtensionUpdater::FetchedCRXFile::FetchedCRXFile(
    103     const std::string& i,
    104     const base::FilePath& p,
    105     bool file_ownership_passed,
    106     const std::set<int>& request_ids)
    107     : extension_id(i),
    108       path(p),
    109       file_ownership_passed(file_ownership_passed),
    110       request_ids(request_ids) {}
    111 
    112 ExtensionUpdater::FetchedCRXFile::FetchedCRXFile()
    113     : path(), file_ownership_passed(true) {}
    114 
    115 ExtensionUpdater::FetchedCRXFile::~FetchedCRXFile() {}
    116 
    117 ExtensionUpdater::InProgressCheck::InProgressCheck()
    118     : install_immediately(false) {}
    119 
    120 ExtensionUpdater::InProgressCheck::~InProgressCheck() {}
    121 
    122 struct ExtensionUpdater::ThrottleInfo {
    123   ThrottleInfo()
    124       : in_progress(true),
    125         throttle_delay(kMinUpdateThrottleTime),
    126         check_start(Time::Now()) {}
    127 
    128   bool in_progress;
    129   int throttle_delay;
    130   Time check_start;
    131 };
    132 
    133 ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service,
    134                                    ExtensionPrefs* extension_prefs,
    135                                    PrefService* prefs,
    136                                    Profile* profile,
    137                                    int frequency_seconds,
    138                                    ExtensionCache* cache)
    139     : alive_(false),
    140       weak_ptr_factory_(this),
    141       service_(service), frequency_seconds_(frequency_seconds),
    142       will_check_soon_(false), extension_prefs_(extension_prefs),
    143       prefs_(prefs), profile_(profile),
    144       next_request_id_(0),
    145       crx_install_is_running_(false),
    146       extension_cache_(cache) {
    147   DCHECK_GE(frequency_seconds_, 5);
    148   DCHECK_LE(frequency_seconds_, kMaxUpdateFrequencySeconds);
    149 #if defined(NDEBUG)
    150   // In Release mode we enforce that update checks don't happen too often.
    151   frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds);
    152 #endif
    153   frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds);
    154 
    155   registrar_.Add(this,
    156                  chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED,
    157                  content::NotificationService::AllBrowserContextsAndSources());
    158 }
    159 
    160 ExtensionUpdater::~ExtensionUpdater() {
    161   Stop();
    162 }
    163 
    164 // The overall goal here is to balance keeping clients up to date while
    165 // avoiding a thundering herd against update servers.
    166 TimeDelta ExtensionUpdater::DetermineFirstCheckDelay() {
    167   DCHECK(alive_);
    168   // If someone's testing with a quick frequency, just allow it.
    169   if (frequency_seconds_ < kStartupWaitSeconds)
    170     return TimeDelta::FromSeconds(frequency_seconds_);
    171 
    172   // If we've never scheduled a check before, start at frequency_seconds_.
    173   if (!prefs_->HasPrefPath(pref_names::kNextUpdateCheck))
    174     return TimeDelta::FromSeconds(frequency_seconds_);
    175 
    176   // If it's been a long time since our last actual check, we want to do one
    177   // relatively soon.
    178   Time now = Time::Now();
    179   Time last = Time::FromInternalValue(prefs_->GetInt64(
    180       pref_names::kLastUpdateCheck));
    181   int days = (now - last).InDays();
    182   if (days >= 30) {
    183     // Wait 5-10 minutes.
    184     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
    185                                           kStartupWaitSeconds * 2));
    186   } else if (days >= 14) {
    187     // Wait 10-20 minutes.
    188     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 2,
    189                                           kStartupWaitSeconds * 4));
    190   } else if (days >= 3) {
    191     // Wait 20-40 minutes.
    192     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 4,
    193                                           kStartupWaitSeconds * 8));
    194   }
    195 
    196   // Read the persisted next check time, and use that if it isn't too soon
    197   // or too late. Otherwise pick something random.
    198   Time saved_next = Time::FromInternalValue(prefs_->GetInt64(
    199       pref_names::kNextUpdateCheck));
    200   Time earliest = now + TimeDelta::FromSeconds(kStartupWaitSeconds);
    201   Time latest = now + TimeDelta::FromSeconds(frequency_seconds_);
    202   if (saved_next >= earliest && saved_next <= latest) {
    203     return saved_next - now;
    204   } else {
    205     return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
    206                                           frequency_seconds_));
    207   }
    208 }
    209 
    210 void ExtensionUpdater::Start() {
    211   DCHECK(!alive_);
    212   // If these are NULL, then that means we've been called after Stop()
    213   // has been called.
    214   DCHECK(service_);
    215   DCHECK(extension_prefs_);
    216   DCHECK(prefs_);
    217   DCHECK(profile_);
    218   DCHECK(!weak_ptr_factory_.HasWeakPtrs());
    219   alive_ = true;
    220   // Make sure our prefs are registered, then schedule the first check.
    221   ScheduleNextCheck(DetermineFirstCheckDelay());
    222 }
    223 
    224 void ExtensionUpdater::Stop() {
    225   weak_ptr_factory_.InvalidateWeakPtrs();
    226   alive_ = false;
    227   service_ = NULL;
    228   extension_prefs_ = NULL;
    229   prefs_ = NULL;
    230   profile_ = NULL;
    231   timer_.Stop();
    232   will_check_soon_ = false;
    233   downloader_.reset();
    234 }
    235 
    236 void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) {
    237   DCHECK(alive_);
    238   DCHECK(!timer_.IsRunning());
    239   DCHECK(target_delay >= TimeDelta::FromSeconds(1));
    240 
    241   // Add +/- 10% random jitter.
    242   double delay_ms = target_delay.InMillisecondsF();
    243   double jitter_factor = (RandDouble() * .2) - 0.1;
    244   delay_ms += delay_ms * jitter_factor;
    245   TimeDelta actual_delay = TimeDelta::FromMilliseconds(
    246       static_cast<int64>(delay_ms));
    247 
    248   // Save the time of next check.
    249   Time next = Time::Now() + actual_delay;
    250   prefs_->SetInt64(pref_names::kNextUpdateCheck, next.ToInternalValue());
    251 
    252   timer_.Start(FROM_HERE, actual_delay, this, &ExtensionUpdater::TimerFired);
    253 }
    254 
    255 void ExtensionUpdater::TimerFired() {
    256   DCHECK(alive_);
    257   CheckNow(default_params_);
    258 
    259   // If the user has overridden the update frequency, don't bother reporting
    260   // this.
    261   if (frequency_seconds_ == extensions::kDefaultUpdateFrequencySeconds) {
    262     Time last = Time::FromInternalValue(prefs_->GetInt64(
    263         pref_names::kLastUpdateCheck));
    264     if (last.ToInternalValue() != 0) {
    265       // Use counts rather than time so we can use minutes rather than millis.
    266       UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.UpdateCheckGap",
    267           (Time::Now() - last).InMinutes(),
    268           TimeDelta::FromSeconds(kStartupWaitSeconds).InMinutes(),
    269           TimeDelta::FromDays(40).InMinutes(),
    270           50);  // 50 buckets seems to be the default.
    271     }
    272   }
    273 
    274   // Save the last check time, and schedule the next check.
    275   int64 now = Time::Now().ToInternalValue();
    276   prefs_->SetInt64(pref_names::kLastUpdateCheck, now);
    277   ScheduleNextCheck(TimeDelta::FromSeconds(frequency_seconds_));
    278 }
    279 
    280 void ExtensionUpdater::CheckSoon() {
    281   DCHECK(alive_);
    282   if (will_check_soon_)
    283     return;
    284   if (BrowserThread::PostTask(
    285           BrowserThread::UI, FROM_HERE,
    286           base::Bind(&ExtensionUpdater::DoCheckSoon,
    287                      weak_ptr_factory_.GetWeakPtr()))) {
    288     will_check_soon_ = true;
    289   } else {
    290     NOTREACHED();
    291   }
    292 }
    293 
    294 bool ExtensionUpdater::WillCheckSoon() const {
    295   return will_check_soon_;
    296 }
    297 
    298 void ExtensionUpdater::DoCheckSoon() {
    299   DCHECK(will_check_soon_);
    300   CheckNow(default_params_);
    301   will_check_soon_ = false;
    302 }
    303 
    304 void ExtensionUpdater::AddToDownloader(
    305     const ExtensionSet* extensions,
    306     const std::list<std::string>& pending_ids,
    307     int request_id) {
    308   InProgressCheck& request = requests_in_progress_[request_id];
    309   for (ExtensionSet::const_iterator extension_iter = extensions->begin();
    310        extension_iter != extensions->end(); ++extension_iter) {
    311     const Extension& extension = *extension_iter->get();
    312     if (!Manifest::IsAutoUpdateableLocation(extension.location())) {
    313       VLOG(2) << "Extension " << extension.id() << " is not auto updateable";
    314       continue;
    315     }
    316     // An extension might be overwritten by policy, and have its update url
    317     // changed. Make sure existing extensions aren't fetched again, if a
    318     // pending fetch for an extension with the same id already exists.
    319     std::list<std::string>::const_iterator pending_id_iter = std::find(
    320         pending_ids.begin(), pending_ids.end(), extension.id());
    321     if (pending_id_iter == pending_ids.end()) {
    322       if (downloader_->AddExtension(extension, request_id))
    323         request.in_progress_ids_.push_back(extension.id());
    324     }
    325   }
    326 }
    327 
    328 void ExtensionUpdater::CheckNow(const CheckParams& params) {
    329   int request_id = next_request_id_++;
    330 
    331   VLOG(2) << "Starting update check " << request_id;
    332   if (params.ids.empty())
    333     NotifyStarted();
    334 
    335   DCHECK(alive_);
    336 
    337   InProgressCheck& request = requests_in_progress_[request_id];
    338   request.callback = params.callback;
    339   request.install_immediately = params.install_immediately;
    340 
    341   if (!downloader_.get()) {
    342     downloader_.reset(
    343         new ExtensionDownloader(this, profile_->GetRequestContext()));
    344   }
    345 
    346   // Add fetch records for extensions that should be fetched by an update URL.
    347   // These extensions are not yet installed. They come from group policy
    348   // and external install sources.
    349   const PendingExtensionManager* pending_extension_manager =
    350       service_->pending_extension_manager();
    351 
    352   std::list<std::string> pending_ids;
    353 
    354   if (params.ids.empty()) {
    355     // If no extension ids are specified, check for updates for all extensions.
    356     pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_ids);
    357 
    358     std::list<std::string>::const_iterator iter;
    359     for (iter = pending_ids.begin(); iter != pending_ids.end(); ++iter) {
    360       const PendingExtensionInfo* info = pending_extension_manager->GetById(
    361           *iter);
    362       if (!Manifest::IsAutoUpdateableLocation(info->install_source())) {
    363         VLOG(2) << "Extension " << *iter << " is not auto updateable";
    364         continue;
    365       }
    366       if (downloader_->AddPendingExtension(*iter, info->update_url(),
    367                                            request_id))
    368         request.in_progress_ids_.push_back(*iter);
    369     }
    370 
    371     ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
    372     AddToDownloader(&registry->enabled_extensions(), pending_ids, request_id);
    373     AddToDownloader(&registry->disabled_extensions(), pending_ids, request_id);
    374   } else {
    375     for (std::list<std::string>::const_iterator it = params.ids.begin();
    376          it != params.ids.end(); ++it) {
    377       const Extension* extension = service_->GetExtensionById(*it, true);
    378       DCHECK(extension);
    379       if (downloader_->AddExtension(*extension, request_id))
    380         request.in_progress_ids_.push_back(extension->id());
    381     }
    382   }
    383 
    384   // StartAllPending() might call OnExtensionDownloadFailed/Finished before
    385   // it returns, which would cause NotifyIfFinished to incorrectly try to
    386   // send out a notification. So check before we call StartAllPending if any
    387   // extensions are going to be updated, and use that to figure out if
    388   // NotifyIfFinished should be called.
    389   bool noChecks = request.in_progress_ids_.empty();
    390 
    391   // StartAllPending() will call OnExtensionDownloadFailed or
    392   // OnExtensionDownloadFinished for each extension that was checked.
    393   downloader_->StartAllPending(extension_cache_);
    394 
    395   if (noChecks)
    396     NotifyIfFinished(request_id);
    397 }
    398 
    399 bool ExtensionUpdater::CheckExtensionSoon(const std::string& extension_id,
    400                                           const FinishedCallback& callback) {
    401   bool have_throttle_info = ContainsKey(throttle_info_, extension_id);
    402   ThrottleInfo& info = throttle_info_[extension_id];
    403   if (have_throttle_info) {
    404     // We already had a ThrottleInfo object for this extension, check if the
    405     // update check request should be allowed.
    406 
    407     // If another check is in progress, don't start a new check.
    408     if (info.in_progress)
    409       return false;
    410 
    411     Time now = Time::Now();
    412     Time last = info.check_start;
    413     // If somehow time moved back, we don't want to infinitely keep throttling.
    414     if (now < last) {
    415       last = now;
    416       info.check_start = now;
    417     }
    418     Time earliest = last + TimeDelta::FromSeconds(info.throttle_delay);
    419     // If check is too soon, throttle.
    420     if (now < earliest)
    421       return false;
    422 
    423     // TODO(mek): Somehow increase time between allowing checks when checks
    424     // are repeatedly throttled and don't result in updates being installed.
    425 
    426     // It's okay to start a check, update values.
    427     info.check_start = now;
    428     info.in_progress = true;
    429   }
    430 
    431   CheckParams params;
    432   params.ids.push_back(extension_id);
    433   params.callback = base::Bind(&ExtensionUpdater::ExtensionCheckFinished,
    434                                weak_ptr_factory_.GetWeakPtr(),
    435                                extension_id, callback);
    436   CheckNow(params);
    437   return true;
    438 }
    439 
    440 void ExtensionUpdater::ExtensionCheckFinished(
    441     const std::string& extension_id,
    442     const FinishedCallback& callback) {
    443   std::map<std::string, ThrottleInfo>::iterator it =
    444       throttle_info_.find(extension_id);
    445   if (it != throttle_info_.end()) {
    446     it->second.in_progress = false;
    447   }
    448   callback.Run();
    449 }
    450 
    451 void ExtensionUpdater::OnExtensionDownloadFailed(
    452     const std::string& id,
    453     Error error,
    454     const PingResult& ping,
    455     const std::set<int>& request_ids) {
    456   DCHECK(alive_);
    457   UpdatePingData(id, ping);
    458   bool install_immediately = false;
    459   for (std::set<int>::const_iterator it = request_ids.begin();
    460        it != request_ids.end(); ++it) {
    461     InProgressCheck& request = requests_in_progress_[*it];
    462     install_immediately |= request.install_immediately;
    463     request.in_progress_ids_.remove(id);
    464     NotifyIfFinished(*it);
    465   }
    466 
    467   // This method is called if no updates were found. However a previous update
    468   // check might have queued an update for this extension already. If a
    469   // current update check has |install_immediately| set the previously
    470   // queued update should be installed now.
    471   if (install_immediately && service_->GetPendingExtensionUpdate(id))
    472     service_->FinishDelayedInstallation(id);
    473 }
    474 
    475 void ExtensionUpdater::OnExtensionDownloadFinished(
    476     const std::string& id,
    477     const base::FilePath& path,
    478     bool file_ownership_passed,
    479     const GURL& download_url,
    480     const std::string& version,
    481     const PingResult& ping,
    482     const std::set<int>& request_ids) {
    483   DCHECK(alive_);
    484   UpdatePingData(id, ping);
    485 
    486   VLOG(2) << download_url << " written to " << path.value();
    487 
    488   FetchedCRXFile fetched(id, path, file_ownership_passed, request_ids);
    489   fetched_crx_files_.push(fetched);
    490 
    491   // MaybeInstallCRXFile() removes extensions from |in_progress_ids_| after
    492   // starting the crx installer.
    493   MaybeInstallCRXFile();
    494 }
    495 
    496 bool ExtensionUpdater::GetPingDataForExtension(
    497     const std::string& id,
    498     ManifestFetchData::PingData* ping_data) {
    499   DCHECK(alive_);
    500   ping_data->rollcall_days = CalculatePingDays(
    501       extension_prefs_->LastPingDay(id));
    502   ping_data->is_enabled = service_->IsExtensionEnabled(id);
    503   ping_data->active_days =
    504       CalculateActivePingDays(extension_prefs_->LastActivePingDay(id),
    505                               extension_prefs_->GetActiveBit(id));
    506   return true;
    507 }
    508 
    509 std::string ExtensionUpdater::GetUpdateUrlData(const std::string& id) {
    510   DCHECK(alive_);
    511   return extension::GetUpdateURLData(extension_prefs_, id);
    512 }
    513 
    514 bool ExtensionUpdater::IsExtensionPending(const std::string& id) {
    515   DCHECK(alive_);
    516   return service_->pending_extension_manager()->IsIdPending(id);
    517 }
    518 
    519 bool ExtensionUpdater::GetExtensionExistingVersion(const std::string& id,
    520                                                    std::string* version) {
    521   DCHECK(alive_);
    522   const Extension* extension = service_->GetExtensionById(id, true);
    523   if (!extension)
    524     return false;
    525   const Extension* update = service_->GetPendingExtensionUpdate(id);
    526   if (update)
    527     *version = update->VersionString();
    528   else
    529     *version = extension->VersionString();
    530   return true;
    531 }
    532 
    533 void ExtensionUpdater::UpdatePingData(const std::string& id,
    534                                       const PingResult& ping_result) {
    535   DCHECK(alive_);
    536   if (ping_result.did_ping)
    537     extension_prefs_->SetLastPingDay(id, ping_result.day_start);
    538   if (extension_prefs_->GetActiveBit(id)) {
    539     extension_prefs_->SetActiveBit(id, false);
    540     extension_prefs_->SetLastActivePingDay(id, ping_result.day_start);
    541   }
    542 }
    543 
    544 void ExtensionUpdater::MaybeInstallCRXFile() {
    545   if (crx_install_is_running_ || fetched_crx_files_.empty())
    546     return;
    547 
    548   std::set<int> request_ids;
    549 
    550   while (!fetched_crx_files_.empty() && !crx_install_is_running_) {
    551     const FetchedCRXFile& crx_file = fetched_crx_files_.top();
    552 
    553     VLOG(2) << "updating " << crx_file.extension_id
    554             << " with " << crx_file.path.value();
    555 
    556     // The ExtensionService is now responsible for cleaning up the temp file
    557     // at |crx_file.path|.
    558     CrxInstaller* installer = NULL;
    559     if (service_->UpdateExtension(crx_file.extension_id,
    560                                   crx_file.path,
    561                                   crx_file.file_ownership_passed,
    562                                   &installer)) {
    563       crx_install_is_running_ = true;
    564       current_crx_file_ = crx_file;
    565 
    566       for (std::set<int>::const_iterator it = crx_file.request_ids.begin();
    567           it != crx_file.request_ids.end(); ++it) {
    568         InProgressCheck& request = requests_in_progress_[*it];
    569         if (request.install_immediately) {
    570           installer->set_install_immediately(true);
    571           break;
    572         }
    573       }
    574 
    575       // Source parameter ensures that we only see the completion event for the
    576       // the installer we started.
    577       registrar_.Add(this,
    578                      chrome::NOTIFICATION_CRX_INSTALLER_DONE,
    579                      content::Source<CrxInstaller>(installer));
    580     } else {
    581       for (std::set<int>::const_iterator it = crx_file.request_ids.begin();
    582            it != crx_file.request_ids.end(); ++it) {
    583         InProgressCheck& request = requests_in_progress_[*it];
    584         request.in_progress_ids_.remove(crx_file.extension_id);
    585       }
    586       request_ids.insert(crx_file.request_ids.begin(),
    587                          crx_file.request_ids.end());
    588     }
    589     fetched_crx_files_.pop();
    590   }
    591 
    592   for (std::set<int>::const_iterator it = request_ids.begin();
    593        it != request_ids.end(); ++it) {
    594     NotifyIfFinished(*it);
    595   }
    596 }
    597 
    598 void ExtensionUpdater::Observe(int type,
    599                                const content::NotificationSource& source,
    600                                const content::NotificationDetails& details) {
    601   switch (type) {
    602     case chrome::NOTIFICATION_CRX_INSTALLER_DONE: {
    603       // No need to listen for CRX_INSTALLER_DONE anymore.
    604       registrar_.Remove(this,
    605                         chrome::NOTIFICATION_CRX_INSTALLER_DONE,
    606                         source);
    607       crx_install_is_running_ = false;
    608 
    609       const FetchedCRXFile& crx_file = current_crx_file_;
    610       for (std::set<int>::const_iterator it = crx_file.request_ids.begin();
    611           it != crx_file.request_ids.end(); ++it) {
    612         InProgressCheck& request = requests_in_progress_[*it];
    613         request.in_progress_ids_.remove(crx_file.extension_id);
    614         NotifyIfFinished(*it);
    615       }
    616 
    617       // If any files are available to update, start one.
    618       MaybeInstallCRXFile();
    619       break;
    620     }
    621     case chrome::NOTIFICATION_EXTENSION_INSTALLED_DEPRECATED: {
    622       const Extension* extension =
    623           content::Details<const InstalledExtensionInfo>(details)->extension;
    624       if (extension)
    625         throttle_info_.erase(extension->id());
    626       break;
    627     }
    628     default:
    629       NOTREACHED();
    630   }
    631 }
    632 
    633 void ExtensionUpdater::NotifyStarted() {
    634   content::NotificationService::current()->Notify(
    635       chrome::NOTIFICATION_EXTENSION_UPDATING_STARTED,
    636       content::Source<Profile>(profile_),
    637       content::NotificationService::NoDetails());
    638 }
    639 
    640 void ExtensionUpdater::NotifyIfFinished(int request_id) {
    641   DCHECK(ContainsKey(requests_in_progress_, request_id));
    642   const InProgressCheck& request = requests_in_progress_[request_id];
    643   if (request.in_progress_ids_.empty()) {
    644     VLOG(2) << "Finished update check " << request_id;
    645     if (!request.callback.is_null())
    646       request.callback.Run();
    647     requests_in_progress_.erase(request_id);
    648   }
    649 }
    650 
    651 }  // namespace extensions
    652