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