- add sources.
[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/extensions/extension.h"
27 #include "chrome/common/extensions/extension_set.h"
28 #include "chrome/common/pref_names.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/notification_details.h"
31 #include "content/public/browser/notification_service.h"
32 #include "content/public/browser/notification_source.h"
33 #include "crypto/sha2.h"
34 #include "extensions/common/constants.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