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