// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/browser/api/alarms/alarm_manager.h" #include "base/bind.h" #include "base/json/json_writer.h" #include "base/lazy_instance.h" #include "base/message_loop/message_loop.h" #include "base/time/clock.h" #include "base/time/default_clock.h" #include "base/time/time.h" #include "base/value_conversions.h" #include "base/values.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/state_store.h" #include "extensions/common/api/alarms.h" namespace extensions { namespace alarms = core_api::alarms; namespace { // A list of alarms that this extension has set. const char kRegisteredAlarms[] = "alarms"; const char kAlarmGranularity[] = "granularity"; // The minimum period between polling for alarms to run. const base::TimeDelta kDefaultMinPollPeriod() { return base::TimeDelta::FromDays(1); } class DefaultAlarmDelegate : public AlarmManager::Delegate { public: explicit DefaultAlarmDelegate(content::BrowserContext* context) : browser_context_(context) {} ~DefaultAlarmDelegate() override {} void OnAlarm(const std::string& extension_id, const Alarm& alarm) override { scoped_ptr args(new base::ListValue()); args->Append(alarm.js_alarm->ToValue().release()); scoped_ptr event( new Event(alarms::OnAlarm::kEventName, args.Pass())); EventRouter::Get(browser_context_) ->DispatchEventToExtension(extension_id, event.Pass()); } private: content::BrowserContext* browser_context_; }; // Creates a TimeDelta from a delay as specified in the API. base::TimeDelta TimeDeltaFromDelay(double delay_in_minutes) { return base::TimeDelta::FromMicroseconds(delay_in_minutes * base::Time::kMicrosecondsPerMinute); } std::vector AlarmsFromValue(const base::ListValue* list) { std::vector alarms; for (size_t i = 0; i < list->GetSize(); ++i) { const base::DictionaryValue* alarm_dict = NULL; Alarm alarm; if (list->GetDictionary(i, &alarm_dict) && alarms::Alarm::Populate(*alarm_dict, alarm.js_alarm.get())) { const base::Value* time_value = NULL; if (alarm_dict->Get(kAlarmGranularity, &time_value)) base::GetValueAsTimeDelta(*time_value, &alarm.granularity); alarms.push_back(alarm); } } return alarms; } scoped_ptr AlarmsToValue(const std::vector& alarms) { scoped_ptr list(new base::ListValue()); for (size_t i = 0; i < alarms.size(); ++i) { scoped_ptr alarm = alarms[i].js_alarm->ToValue().Pass(); alarm->Set(kAlarmGranularity, base::CreateTimeDeltaValue(alarms[i].granularity)); list->Append(alarm.release()); } return list.Pass(); } } // namespace // AlarmManager AlarmManager::AlarmManager(content::BrowserContext* context) : browser_context_(context), clock_(new base::DefaultClock()), delegate_(new DefaultAlarmDelegate(context)), extension_registry_observer_(this) { extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store(); if (storage) storage->RegisterKey(kRegisteredAlarms); } AlarmManager::~AlarmManager() { } void AlarmManager::AddAlarm(const std::string& extension_id, const Alarm& alarm, const AddAlarmCallback& callback) { RunWhenReady(extension_id, base::Bind(&AlarmManager::AddAlarmWhenReady, AsWeakPtr(), alarm, callback)); } void AlarmManager::GetAlarm(const std::string& extension_id, const std::string& name, const GetAlarmCallback& callback) { RunWhenReady(extension_id, base::Bind(&AlarmManager::GetAlarmWhenReady, AsWeakPtr(), name, callback)); } void AlarmManager::GetAllAlarms(const std::string& extension_id, const GetAllAlarmsCallback& callback) { RunWhenReady(extension_id, base::Bind(&AlarmManager::GetAllAlarmsWhenReady, AsWeakPtr(), callback)); } void AlarmManager::RemoveAlarm(const std::string& extension_id, const std::string& name, const RemoveAlarmCallback& callback) { RunWhenReady(extension_id, base::Bind(&AlarmManager::RemoveAlarmWhenReady, AsWeakPtr(), name, callback)); } void AlarmManager::RemoveAllAlarms(const std::string& extension_id, const RemoveAllAlarmsCallback& callback) { RunWhenReady(extension_id, base::Bind(&AlarmManager::RemoveAllAlarmsWhenReady, AsWeakPtr(), callback)); } void AlarmManager::AddAlarmWhenReady(const Alarm& alarm, const AddAlarmCallback& callback, const std::string& extension_id) { AddAlarmImpl(extension_id, alarm); WriteToStorage(extension_id); callback.Run(); } void AlarmManager::GetAlarmWhenReady(const std::string& name, const GetAlarmCallback& callback, const std::string& extension_id) { AlarmIterator it = GetAlarmIterator(extension_id, name); callback.Run(it.first != alarms_.end() ? &*it.second : NULL); } void AlarmManager::GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback, const std::string& extension_id) { AlarmMap::iterator list = alarms_.find(extension_id); callback.Run(list != alarms_.end() ? &list->second : NULL); } void AlarmManager::RemoveAlarmWhenReady(const std::string& name, const RemoveAlarmCallback& callback, const std::string& extension_id) { AlarmIterator it = GetAlarmIterator(extension_id, name); if (it.first == alarms_.end()) { callback.Run(false); return; } RemoveAlarmIterator(it); WriteToStorage(extension_id); callback.Run(true); } void AlarmManager::RemoveAllAlarmsWhenReady( const RemoveAllAlarmsCallback& callback, const std::string& extension_id) { AlarmMap::iterator list = alarms_.find(extension_id); if (list != alarms_.end()) { // Note: I'm using indices rather than iterators here because // RemoveAlarmIterator will delete the list when it becomes empty. for (size_t i = 0, size = list->second.size(); i < size; ++i) RemoveAlarmIterator(AlarmIterator(list, list->second.begin())); CHECK(alarms_.find(extension_id) == alarms_.end()); WriteToStorage(extension_id); } callback.Run(); } AlarmManager::AlarmIterator AlarmManager::GetAlarmIterator( const std::string& extension_id, const std::string& name) { AlarmMap::iterator list = alarms_.find(extension_id); if (list == alarms_.end()) return make_pair(alarms_.end(), AlarmList::iterator()); for (AlarmList::iterator it = list->second.begin(); it != list->second.end(); ++it) { if (it->js_alarm->name == name) return make_pair(list, it); } return make_pair(alarms_.end(), AlarmList::iterator()); } void AlarmManager::SetClockForTesting(base::Clock* clock) { clock_.reset(clock); } static base::LazyInstance> g_factory = LAZY_INSTANCE_INITIALIZER; // static BrowserContextKeyedAPIFactory* AlarmManager::GetFactoryInstance() { return g_factory.Pointer(); } // static AlarmManager* AlarmManager::Get(content::BrowserContext* browser_context) { return BrowserContextKeyedAPIFactory::Get(browser_context); } void AlarmManager::RemoveAlarmIterator(const AlarmIterator& iter) { AlarmList& list = iter.first->second; list.erase(iter.second); if (list.empty()) alarms_.erase(iter.first); // Cancel the timer if there are no more alarms. // We don't need to reschedule the poll otherwise, because in // the worst case we would just poll one extra time. if (alarms_.empty()) { timer_.Stop(); next_poll_time_ = base::Time(); } } void AlarmManager::OnAlarm(AlarmIterator it) { CHECK(it.first != alarms_.end()); Alarm& alarm = *it.second; std::string extension_id_copy(it.first->first); delegate_->OnAlarm(extension_id_copy, alarm); // Update our scheduled time for the next alarm. if (double* period_in_minutes = alarm.js_alarm->period_in_minutes.get()) { // Get the timer's delay in JS time (i.e., convert it from minutes to // milliseconds). double period_in_js_time = *period_in_minutes * base::Time::kMicrosecondsPerMinute / base::Time::kMicrosecondsPerMillisecond; // Find out how many periods have transpired since the alarm last went off // (it's possible that we missed some). int transpired_periods = (last_poll_time_.ToJsTime() - alarm.js_alarm->scheduled_time) / period_in_js_time; // Schedule the alarm for the next period that is in-line with the original // scheduling. alarm.js_alarm->scheduled_time += period_in_js_time * (transpired_periods + 1); } else { RemoveAlarmIterator(it); } WriteToStorage(extension_id_copy); } void AlarmManager::AddAlarmImpl(const std::string& extension_id, const Alarm& alarm) { // Override any old alarm with the same name. AlarmIterator old_alarm = GetAlarmIterator(extension_id, alarm.js_alarm->name); if (old_alarm.first != alarms_.end()) RemoveAlarmIterator(old_alarm); alarms_[extension_id].push_back(alarm); base::Time alarm_time = base::Time::FromJsTime(alarm.js_alarm->scheduled_time); if (next_poll_time_.is_null() || alarm_time < next_poll_time_) SetNextPollTime(alarm_time); } void AlarmManager::WriteToStorage(const std::string& extension_id) { StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store(); if (!storage) return; scoped_ptr alarms; AlarmMap::iterator list = alarms_.find(extension_id); if (list != alarms_.end()) alarms.reset(AlarmsToValue(list->second).release()); else alarms.reset(AlarmsToValue(std::vector()).release()); storage->SetExtensionValue(extension_id, kRegisteredAlarms, alarms.Pass()); } void AlarmManager::ReadFromStorage(const std::string& extension_id, scoped_ptr value) { base::ListValue* list = NULL; if (value.get() && value->GetAsList(&list)) { std::vector alarm_states = AlarmsFromValue(list); for (size_t i = 0; i < alarm_states.size(); ++i) AddAlarmImpl(extension_id, alarm_states[i]); } ReadyQueue& extension_ready_queue = ready_actions_[extension_id]; while (!extension_ready_queue.empty()) { extension_ready_queue.front().Run(extension_id); extension_ready_queue.pop(); } ready_actions_.erase(extension_id); } void AlarmManager::SetNextPollTime(const base::Time& time) { next_poll_time_ = time; timer_.Start(FROM_HERE, std::max(base::TimeDelta::FromSeconds(0), time - clock_->Now()), this, &AlarmManager::PollAlarms); } void AlarmManager::ScheduleNextPoll() { // If there are no alarms, stop the timer. if (alarms_.empty()) { timer_.Stop(); next_poll_time_ = base::Time(); return; } // Find the soonest alarm that is scheduled to run and the smallest // granularity of any alarm. // alarms_ guarantees that none of its contained lists are empty. base::Time soonest_alarm_time = base::Time::FromJsTime( alarms_.begin()->second.begin()->js_alarm->scheduled_time); base::TimeDelta min_granularity = kDefaultMinPollPeriod(); for (AlarmMap::const_iterator m_it = alarms_.begin(), m_end = alarms_.end(); m_it != m_end; ++m_it) { for (AlarmList::const_iterator l_it = m_it->second.begin(); l_it != m_it->second.end(); ++l_it) { base::Time cur_alarm_time = base::Time::FromJsTime(l_it->js_alarm->scheduled_time); if (cur_alarm_time < soonest_alarm_time) soonest_alarm_time = cur_alarm_time; if (l_it->granularity < min_granularity) min_granularity = l_it->granularity; base::TimeDelta cur_alarm_delta = cur_alarm_time - last_poll_time_; if (cur_alarm_delta < l_it->minimum_granularity) cur_alarm_delta = l_it->minimum_granularity; if (cur_alarm_delta < min_granularity) min_granularity = cur_alarm_delta; } } base::Time next_poll(last_poll_time_ + min_granularity); // If the next alarm is more than min_granularity in the future, wait for it. // Otherwise, only poll as often as min_granularity. // As a special case, if we've never checked for an alarm before // (e.g. during startup), let alarms fire asap. if (last_poll_time_.is_null() || next_poll < soonest_alarm_time) next_poll = soonest_alarm_time; // Schedule the poll. SetNextPollTime(next_poll); } void AlarmManager::PollAlarms() { last_poll_time_ = clock_->Now(); // Run any alarms scheduled in the past. OnAlarm uses vector::erase to remove // elements from the AlarmList, and map::erase to remove AlarmLists from the // AlarmMap. for (AlarmMap::iterator m_it = alarms_.begin(), m_end = alarms_.end(); m_it != m_end;) { AlarmMap::iterator cur_extension = m_it++; // Iterate (a) backwards so that removing elements doesn't affect // upcoming iterations, and (b) with indices so that if the last // iteration destroys the AlarmList, I'm not about to use the end // iterator that the destruction invalidates. for (size_t i = cur_extension->second.size(); i > 0; --i) { AlarmList::iterator cur_alarm = cur_extension->second.begin() + i - 1; if (base::Time::FromJsTime(cur_alarm->js_alarm->scheduled_time) <= last_poll_time_) { OnAlarm(make_pair(cur_extension, cur_alarm)); } } } ScheduleNextPoll(); } static void RemoveAllOnUninstallCallback() { } void AlarmManager::RunWhenReady(const std::string& extension_id, const ReadyAction& action) { ReadyMap::iterator it = ready_actions_.find(extension_id); if (it == ready_actions_.end()) action.Run(extension_id); else it->second.push(action); } void AlarmManager::OnExtensionLoaded(content::BrowserContext* browser_context, const Extension* extension) { StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store(); if (storage) { ready_actions_.insert(ReadyMap::value_type(extension->id(), ReadyQueue())); storage->GetExtensionValue(extension->id(), kRegisteredAlarms, base::Bind(&AlarmManager::ReadFromStorage, AsWeakPtr(), extension->id())); } } void AlarmManager::OnExtensionUninstalled( content::BrowserContext* browser_context, const Extension* extension, extensions::UninstallReason reason) { RemoveAllAlarms(extension->id(), base::Bind(RemoveAllOnUninstallCallback)); } // AlarmManager::Alarm Alarm::Alarm() : js_alarm(new alarms::Alarm()) { } Alarm::Alarm(const std::string& name, const alarms::AlarmCreateInfo& create_info, base::TimeDelta min_granularity, base::Time now) : js_alarm(new alarms::Alarm()) { js_alarm->name = name; minimum_granularity = min_granularity; if (create_info.when.get()) { // Absolute scheduling. js_alarm->scheduled_time = *create_info.when; granularity = base::Time::FromJsTime(js_alarm->scheduled_time) - now; } else { // Relative scheduling. double* delay_in_minutes = create_info.delay_in_minutes.get(); if (delay_in_minutes == NULL) delay_in_minutes = create_info.period_in_minutes.get(); CHECK(delay_in_minutes != NULL) << "ValidateAlarmCreateInfo in alarms_api.cc should have " << "prevented this call."; base::TimeDelta delay = TimeDeltaFromDelay(*delay_in_minutes); js_alarm->scheduled_time = (now + delay).ToJsTime(); granularity = delay; } if (granularity < min_granularity) granularity = min_granularity; // Check for repetition. if (create_info.period_in_minutes.get()) { js_alarm->period_in_minutes.reset( new double(*create_info.period_in_minutes)); } } Alarm::~Alarm() { } } // namespace extensions