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