• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/api/alarms/alarm_manager.h"
6 
7 #include "base/bind.h"
8 #include "base/json/json_writer.h"
9 #include "base/lazy_instance.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/time/clock.h"
12 #include "base/time/default_clock.h"
13 #include "base/time/time.h"
14 #include "base/value_conversions.h"
15 #include "base/values.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/state_store.h"
18 #include "chrome/common/extensions/api/alarms.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/extension_system.h"
22 
23 namespace extensions {
24 
25 namespace alarms = api::alarms;
26 
27 namespace {
28 
29 // A list of alarms that this extension has set.
30 const char kRegisteredAlarms[] = "alarms";
31 const char kAlarmGranularity[] = "granularity";
32 
33 // The minimum period between polling for alarms to run.
kDefaultMinPollPeriod()34 const base::TimeDelta kDefaultMinPollPeriod() {
35   return base::TimeDelta::FromDays(1);
36 }
37 
38 class DefaultAlarmDelegate : public AlarmManager::Delegate {
39  public:
DefaultAlarmDelegate(content::BrowserContext * context)40   explicit DefaultAlarmDelegate(content::BrowserContext* context)
41       : browser_context_(context) {}
~DefaultAlarmDelegate()42   virtual ~DefaultAlarmDelegate() {}
43 
OnAlarm(const std::string & extension_id,const Alarm & alarm)44   virtual void OnAlarm(const std::string& extension_id,
45                        const Alarm& alarm) OVERRIDE {
46     scoped_ptr<base::ListValue> args(new base::ListValue());
47     args->Append(alarm.js_alarm->ToValue().release());
48     scoped_ptr<Event> event(new Event(alarms::OnAlarm::kEventName,
49                                       args.Pass()));
50     EventRouter::Get(browser_context_)
51         ->DispatchEventToExtension(extension_id, event.Pass());
52   }
53 
54  private:
55   content::BrowserContext* browser_context_;
56 };
57 
58 // Creates a TimeDelta from a delay as specified in the API.
TimeDeltaFromDelay(double delay_in_minutes)59 base::TimeDelta TimeDeltaFromDelay(double delay_in_minutes) {
60   return base::TimeDelta::FromMicroseconds(
61       delay_in_minutes * base::Time::kMicrosecondsPerMinute);
62 }
63 
AlarmsFromValue(const base::ListValue * list)64 std::vector<Alarm> AlarmsFromValue(const base::ListValue* list) {
65   std::vector<Alarm> alarms;
66   for (size_t i = 0; i < list->GetSize(); ++i) {
67     const base::DictionaryValue* alarm_dict = NULL;
68     Alarm alarm;
69     if (list->GetDictionary(i, &alarm_dict) &&
70         api::alarms::Alarm::Populate(*alarm_dict, alarm.js_alarm.get())) {
71       const base::Value* time_value = NULL;
72       if (alarm_dict->Get(kAlarmGranularity, &time_value))
73         base::GetValueAsTimeDelta(*time_value, &alarm.granularity);
74       alarms.push_back(alarm);
75     }
76   }
77   return alarms;
78 }
79 
AlarmsToValue(const std::vector<Alarm> & alarms)80 scoped_ptr<base::ListValue> AlarmsToValue(const std::vector<Alarm>& alarms) {
81   scoped_ptr<base::ListValue> list(new base::ListValue());
82   for (size_t i = 0; i < alarms.size(); ++i) {
83     scoped_ptr<base::DictionaryValue> alarm =
84         alarms[i].js_alarm->ToValue().Pass();
85     alarm->Set(kAlarmGranularity,
86                base::CreateTimeDeltaValue(alarms[i].granularity));
87     list->Append(alarm.release());
88   }
89   return list.Pass();
90 }
91 
92 }  // namespace
93 
94 // AlarmManager
95 
AlarmManager(content::BrowserContext * context)96 AlarmManager::AlarmManager(content::BrowserContext* context)
97     : browser_context_(context),
98       clock_(new base::DefaultClock()),
99       delegate_(new DefaultAlarmDelegate(context)),
100       extension_registry_observer_(this) {
101   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
102 
103   StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
104   if (storage)
105     storage->RegisterKey(kRegisteredAlarms);
106 }
107 
~AlarmManager()108 AlarmManager::~AlarmManager() {
109 }
110 
AddAlarm(const std::string & extension_id,const Alarm & alarm,const AddAlarmCallback & callback)111 void AlarmManager::AddAlarm(const std::string& extension_id,
112                             const Alarm& alarm,
113                             const AddAlarmCallback& callback) {
114   RunWhenReady(extension_id, base::Bind(
115       &AlarmManager::AddAlarmWhenReady, AsWeakPtr(), alarm, callback));
116 }
117 
GetAlarm(const std::string & extension_id,const std::string & name,const GetAlarmCallback & callback)118 void AlarmManager::GetAlarm(const std::string& extension_id,
119                             const std::string& name,
120                             const GetAlarmCallback& callback) {
121   RunWhenReady(extension_id, base::Bind(
122       &AlarmManager::GetAlarmWhenReady, AsWeakPtr(), name, callback));
123 }
124 
GetAllAlarms(const std::string & extension_id,const GetAllAlarmsCallback & callback)125 void AlarmManager::GetAllAlarms(
126     const std::string& extension_id, const GetAllAlarmsCallback& callback) {
127   RunWhenReady(extension_id, base::Bind(
128       &AlarmManager::GetAllAlarmsWhenReady, AsWeakPtr(), callback));
129 }
130 
RemoveAlarm(const std::string & extension_id,const std::string & name,const RemoveAlarmCallback & callback)131 void AlarmManager::RemoveAlarm(const std::string& extension_id,
132                                const std::string& name,
133                                const RemoveAlarmCallback& callback) {
134   RunWhenReady(extension_id, base::Bind(
135       &AlarmManager::RemoveAlarmWhenReady, AsWeakPtr(), name, callback));
136 }
137 
RemoveAllAlarms(const std::string & extension_id,const RemoveAllAlarmsCallback & callback)138 void AlarmManager::RemoveAllAlarms(const std::string& extension_id,
139                                    const RemoveAllAlarmsCallback& callback) {
140   RunWhenReady(extension_id, base::Bind(
141       &AlarmManager::RemoveAllAlarmsWhenReady, AsWeakPtr(), callback));
142 }
143 
AddAlarmWhenReady(const Alarm & alarm,const AddAlarmCallback & callback,const std::string & extension_id)144 void AlarmManager::AddAlarmWhenReady(const Alarm& alarm,
145                                      const AddAlarmCallback& callback,
146                                      const std::string& extension_id) {
147   AddAlarmImpl(extension_id, alarm);
148   WriteToStorage(extension_id);
149   callback.Run();
150 }
151 
GetAlarmWhenReady(const std::string & name,const GetAlarmCallback & callback,const std::string & extension_id)152 void AlarmManager::GetAlarmWhenReady(const std::string& name,
153                                      const GetAlarmCallback& callback,
154                                      const std::string& extension_id) {
155   AlarmIterator it = GetAlarmIterator(extension_id, name);
156   callback.Run(it.first != alarms_.end() ? &*it.second : NULL);
157 }
158 
GetAllAlarmsWhenReady(const GetAllAlarmsCallback & callback,const std::string & extension_id)159 void AlarmManager::GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback,
160                                          const std::string& extension_id) {
161   AlarmMap::iterator list = alarms_.find(extension_id);
162   callback.Run(list != alarms_.end() ? &list->second : NULL);
163 }
164 
RemoveAlarmWhenReady(const std::string & name,const RemoveAlarmCallback & callback,const std::string & extension_id)165 void AlarmManager::RemoveAlarmWhenReady(const std::string& name,
166                                         const RemoveAlarmCallback& callback,
167                                         const std::string& extension_id) {
168   AlarmIterator it = GetAlarmIterator(extension_id, name);
169   if (it.first == alarms_.end()) {
170     callback.Run(false);
171     return;
172   }
173 
174   RemoveAlarmIterator(it);
175   WriteToStorage(extension_id);
176   callback.Run(true);
177 }
178 
RemoveAllAlarmsWhenReady(const RemoveAllAlarmsCallback & callback,const std::string & extension_id)179 void AlarmManager::RemoveAllAlarmsWhenReady(
180     const RemoveAllAlarmsCallback& callback, const std::string& extension_id) {
181   AlarmMap::iterator list = alarms_.find(extension_id);
182   if (list != alarms_.end()) {
183     // Note: I'm using indices rather than iterators here because
184     // RemoveAlarmIterator will delete the list when it becomes empty.
185     for (size_t i = 0, size = list->second.size(); i < size; ++i)
186       RemoveAlarmIterator(AlarmIterator(list, list->second.begin()));
187 
188     CHECK(alarms_.find(extension_id) == alarms_.end());
189     WriteToStorage(extension_id);
190   }
191   callback.Run();
192 }
193 
GetAlarmIterator(const std::string & extension_id,const std::string & name)194 AlarmManager::AlarmIterator AlarmManager::GetAlarmIterator(
195     const std::string& extension_id, const std::string& name) {
196   AlarmMap::iterator list = alarms_.find(extension_id);
197   if (list == alarms_.end())
198     return make_pair(alarms_.end(), AlarmList::iterator());
199 
200   for (AlarmList::iterator it = list->second.begin();
201        it != list->second.end(); ++it) {
202     if (it->js_alarm->name == name)
203       return make_pair(list, it);
204   }
205 
206   return make_pair(alarms_.end(), AlarmList::iterator());
207 }
208 
SetClockForTesting(base::Clock * clock)209 void AlarmManager::SetClockForTesting(base::Clock* clock) {
210   clock_.reset(clock);
211 }
212 
213 static base::LazyInstance<BrowserContextKeyedAPIFactory<AlarmManager> >
214     g_factory = LAZY_INSTANCE_INITIALIZER;
215 
216 // static
217 BrowserContextKeyedAPIFactory<AlarmManager>*
GetFactoryInstance()218 AlarmManager::GetFactoryInstance() {
219   return g_factory.Pointer();
220 }
221 
222 // static
Get(content::BrowserContext * browser_context)223 AlarmManager* AlarmManager::Get(content::BrowserContext* browser_context) {
224   return BrowserContextKeyedAPIFactory<AlarmManager>::Get(browser_context);
225 }
226 
RemoveAlarmIterator(const AlarmIterator & iter)227 void AlarmManager::RemoveAlarmIterator(const AlarmIterator& iter) {
228   AlarmList& list = iter.first->second;
229   list.erase(iter.second);
230   if (list.empty())
231     alarms_.erase(iter.first);
232 
233   // Cancel the timer if there are no more alarms.
234   // We don't need to reschedule the poll otherwise, because in
235   // the worst case we would just poll one extra time.
236   if (alarms_.empty()) {
237     timer_.Stop();
238     next_poll_time_ = base::Time();
239   }
240 }
241 
OnAlarm(AlarmIterator it)242 void AlarmManager::OnAlarm(AlarmIterator it) {
243   CHECK(it.first != alarms_.end());
244   Alarm& alarm = *it.second;
245   std::string extension_id_copy(it.first->first);
246   delegate_->OnAlarm(extension_id_copy, alarm);
247 
248   // Update our scheduled time for the next alarm.
249   if (double* period_in_minutes =
250       alarm.js_alarm->period_in_minutes.get()) {
251     // Get the timer's delay in JS time (i.e., convert it from minutes to
252     // milliseconds).
253     double period_in_js_time =
254         *period_in_minutes * base::Time::kMicrosecondsPerMinute /
255         base::Time::kMicrosecondsPerMillisecond;
256     // Find out how many periods have transpired since the alarm last went off
257     // (it's possible that we missed some).
258     int transpired_periods =
259         (last_poll_time_.ToJsTime() - alarm.js_alarm->scheduled_time) /
260         period_in_js_time;
261     // Schedule the alarm for the next period that is in-line with the original
262     // scheduling.
263     alarm.js_alarm->scheduled_time +=
264         period_in_js_time * (transpired_periods + 1);
265   } else {
266     RemoveAlarmIterator(it);
267   }
268   WriteToStorage(extension_id_copy);
269 }
270 
AddAlarmImpl(const std::string & extension_id,const Alarm & alarm)271 void AlarmManager::AddAlarmImpl(const std::string& extension_id,
272                                 const Alarm& alarm) {
273   // Override any old alarm with the same name.
274   AlarmIterator old_alarm = GetAlarmIterator(extension_id,
275                                              alarm.js_alarm->name);
276   if (old_alarm.first != alarms_.end())
277     RemoveAlarmIterator(old_alarm);
278 
279   alarms_[extension_id].push_back(alarm);
280   base::Time alarm_time =
281       base::Time::FromJsTime(alarm.js_alarm->scheduled_time);
282   if (next_poll_time_.is_null() || alarm_time < next_poll_time_)
283     SetNextPollTime(alarm_time);
284 }
285 
WriteToStorage(const std::string & extension_id)286 void AlarmManager::WriteToStorage(const std::string& extension_id) {
287   StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
288   if (!storage)
289     return;
290 
291   scoped_ptr<base::Value> alarms;
292   AlarmMap::iterator list = alarms_.find(extension_id);
293   if (list != alarms_.end())
294     alarms.reset(AlarmsToValue(list->second).release());
295   else
296     alarms.reset(AlarmsToValue(std::vector<Alarm>()).release());
297   storage->SetExtensionValue(extension_id, kRegisteredAlarms, alarms.Pass());
298 }
299 
ReadFromStorage(const std::string & extension_id,scoped_ptr<base::Value> value)300 void AlarmManager::ReadFromStorage(const std::string& extension_id,
301                                    scoped_ptr<base::Value> value) {
302   base::ListValue* list = NULL;
303   if (value.get() && value->GetAsList(&list)) {
304     std::vector<Alarm> alarm_states = AlarmsFromValue(list);
305     for (size_t i = 0; i < alarm_states.size(); ++i)
306       AddAlarmImpl(extension_id, alarm_states[i]);
307   }
308 
309   ReadyQueue& extension_ready_queue = ready_actions_[extension_id];
310   while (!extension_ready_queue.empty()) {
311     extension_ready_queue.front().Run(extension_id);
312     extension_ready_queue.pop();
313   }
314   ready_actions_.erase(extension_id);
315 }
316 
SetNextPollTime(const base::Time & time)317 void AlarmManager::SetNextPollTime(const base::Time& time) {
318   next_poll_time_ = time;
319   timer_.Start(FROM_HERE,
320                std::max(base::TimeDelta::FromSeconds(0), time - clock_->Now()),
321                this,
322                &AlarmManager::PollAlarms);
323 }
324 
ScheduleNextPoll()325 void AlarmManager::ScheduleNextPoll() {
326   // If there are no alarms, stop the timer.
327   if (alarms_.empty()) {
328     timer_.Stop();
329     next_poll_time_ = base::Time();
330     return;
331   }
332 
333   // Find the soonest alarm that is scheduled to run and the smallest
334   // granularity of any alarm.
335   // alarms_ guarantees that none of its contained lists are empty.
336   base::Time soonest_alarm_time = base::Time::FromJsTime(
337       alarms_.begin()->second.begin()->js_alarm->scheduled_time);
338   base::TimeDelta min_granularity = kDefaultMinPollPeriod();
339   for (AlarmMap::const_iterator m_it = alarms_.begin(), m_end = alarms_.end();
340        m_it != m_end; ++m_it) {
341     for (AlarmList::const_iterator l_it = m_it->second.begin();
342          l_it != m_it->second.end(); ++l_it) {
343       base::Time cur_alarm_time =
344           base::Time::FromJsTime(l_it->js_alarm->scheduled_time);
345       if (cur_alarm_time < soonest_alarm_time)
346         soonest_alarm_time = cur_alarm_time;
347       if (l_it->granularity < min_granularity)
348         min_granularity = l_it->granularity;
349       base::TimeDelta cur_alarm_delta = cur_alarm_time - last_poll_time_;
350       if (cur_alarm_delta < l_it->minimum_granularity)
351         cur_alarm_delta = l_it->minimum_granularity;
352       if (cur_alarm_delta < min_granularity)
353         min_granularity = cur_alarm_delta;
354     }
355   }
356 
357   base::Time next_poll(last_poll_time_ + min_granularity);
358   // If the next alarm is more than min_granularity in the future, wait for it.
359   // Otherwise, only poll as often as min_granularity.
360   // As a special case, if we've never checked for an alarm before
361   // (e.g. during startup), let alarms fire asap.
362   if (last_poll_time_.is_null() || next_poll < soonest_alarm_time)
363     next_poll = soonest_alarm_time;
364 
365   // Schedule the poll.
366   SetNextPollTime(next_poll);
367 }
368 
PollAlarms()369 void AlarmManager::PollAlarms() {
370   last_poll_time_ = clock_->Now();
371 
372   // Run any alarms scheduled in the past. OnAlarm uses vector::erase to remove
373   // elements from the AlarmList, and map::erase to remove AlarmLists from the
374   // AlarmMap.
375   for (AlarmMap::iterator m_it = alarms_.begin(), m_end = alarms_.end();
376        m_it != m_end;) {
377     AlarmMap::iterator cur_extension = m_it++;
378 
379     // Iterate (a) backwards so that removing elements doesn't affect
380     // upcoming iterations, and (b) with indices so that if the last
381     // iteration destroys the AlarmList, I'm not about to use the end
382     // iterator that the destruction invalidates.
383     for (size_t i = cur_extension->second.size(); i > 0; --i) {
384       AlarmList::iterator cur_alarm = cur_extension->second.begin() + i - 1;
385       if (base::Time::FromJsTime(cur_alarm->js_alarm->scheduled_time) <=
386           last_poll_time_) {
387         OnAlarm(make_pair(cur_extension, cur_alarm));
388       }
389     }
390   }
391 
392   ScheduleNextPoll();
393 }
394 
RemoveAllOnUninstallCallback()395 static void RemoveAllOnUninstallCallback() {}
396 
RunWhenReady(const std::string & extension_id,const ReadyAction & action)397 void AlarmManager::RunWhenReady(
398     const std::string& extension_id, const ReadyAction& action) {
399   ReadyMap::iterator it = ready_actions_.find(extension_id);
400 
401   if (it == ready_actions_.end())
402     action.Run(extension_id);
403   else
404     it->second.push(action);
405 }
406 
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)407 void AlarmManager::OnExtensionLoaded(content::BrowserContext* browser_context,
408                                      const Extension* extension) {
409   StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
410   if (storage) {
411     ready_actions_.insert(ReadyMap::value_type(extension->id(), ReadyQueue()));
412     storage->GetExtensionValue(
413         extension->id(),
414         kRegisteredAlarms,
415         base::Bind(
416             &AlarmManager::ReadFromStorage, AsWeakPtr(), extension->id()));
417   }
418 }
419 
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension)420 void AlarmManager::OnExtensionUninstalled(
421     content::BrowserContext* browser_context,
422     const Extension* extension) {
423   RemoveAllAlarms(extension->id(), base::Bind(RemoveAllOnUninstallCallback));
424 }
425 
426 // AlarmManager::Alarm
427 
Alarm()428 Alarm::Alarm()
429     : js_alarm(new api::alarms::Alarm()) {
430 }
431 
Alarm(const std::string & name,const api::alarms::AlarmCreateInfo & create_info,base::TimeDelta min_granularity,base::Time now)432 Alarm::Alarm(const std::string& name,
433              const api::alarms::AlarmCreateInfo& create_info,
434              base::TimeDelta min_granularity,
435              base::Time now)
436     : js_alarm(new api::alarms::Alarm()) {
437   js_alarm->name = name;
438   minimum_granularity = min_granularity;
439 
440   if (create_info.when.get()) {
441     // Absolute scheduling.
442     js_alarm->scheduled_time = *create_info.when;
443     granularity = base::Time::FromJsTime(js_alarm->scheduled_time) - now;
444   } else {
445     // Relative scheduling.
446     double* delay_in_minutes = create_info.delay_in_minutes.get();
447     if (delay_in_minutes == NULL)
448       delay_in_minutes = create_info.period_in_minutes.get();
449     CHECK(delay_in_minutes != NULL)
450         << "ValidateAlarmCreateInfo in alarms_api.cc should have "
451         << "prevented this call.";
452     base::TimeDelta delay = TimeDeltaFromDelay(*delay_in_minutes);
453     js_alarm->scheduled_time = (now + delay).ToJsTime();
454     granularity = delay;
455   }
456 
457   if (granularity < min_granularity)
458     granularity = min_granularity;
459 
460   // Check for repetition.
461   if (create_info.period_in_minutes.get()) {
462     js_alarm->period_in_minutes.reset(
463         new double(*create_info.period_in_minutes));
464   }
465 }
466 
~Alarm()467 Alarm::~Alarm() {
468 }
469 
470 }  // namespace extensions
471