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