Upstream version 8.37.180.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_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