diff options
Diffstat (limited to 'chromium/extensions/browser/api/alarms/alarm_manager.cc')
-rw-r--r-- | chromium/extensions/browser/api/alarms/alarm_manager.cc | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/chromium/extensions/browser/api/alarms/alarm_manager.cc b/chromium/extensions/browser/api/alarms/alarm_manager.cc new file mode 100644 index 00000000000..0616481fc75 --- /dev/null +++ b/chromium/extensions/browser/api/alarms/alarm_manager.cc @@ -0,0 +1,473 @@ +// 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 <stddef.h> + +#include <utility> + +#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 = 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<base::ListValue> args(new base::ListValue()); + args->Append(alarm.js_alarm->ToValue().release()); + scoped_ptr<Event> event(new Event( + events::ALARMS_ON_ALARM, alarms::OnAlarm::kEventName, std::move(args))); + EventRouter::Get(browser_context_) + ->DispatchEventToExtension(extension_id, std::move(event)); + } + + 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<Alarm> AlarmsFromValue(const base::ListValue* list) { + std::vector<Alarm> 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<base::ListValue> AlarmsToValue(const std::vector<Alarm>& alarms) { + scoped_ptr<base::ListValue> list(new base::ListValue()); + for (size_t i = 0; i < alarms.size(); ++i) { + scoped_ptr<base::DictionaryValue> alarm = alarms[i].js_alarm->ToValue(); + alarm->Set(kAlarmGranularity, + base::CreateTimeDeltaValue(alarms[i].granularity)); + list->Append(alarm.release()); + } + return list; +} + +} // 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<BrowserContextKeyedAPIFactory<AlarmManager>> + g_factory = LAZY_INSTANCE_INITIALIZER; + +// static +BrowserContextKeyedAPIFactory<AlarmManager>* +AlarmManager::GetFactoryInstance() { + return g_factory.Pointer(); +} + +// static +AlarmManager* AlarmManager::Get(content::BrowserContext* browser_context) { + return BrowserContextKeyedAPIFactory<AlarmManager>::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<base::Value> alarms; + AlarmMap::iterator list = alarms_.find(extension_id); + if (list != alarms_.end()) + alarms.reset(AlarmsToValue(list->second).release()); + else + alarms.reset(AlarmsToValue(std::vector<Alarm>()).release()); + storage->SetExtensionValue(extension_id, kRegisteredAlarms, + std::move(alarms)); +} + +void AlarmManager::ReadFromStorage(const std::string& extension_id, + scoped_ptr<base::Value> value) { + base::ListValue* list = NULL; + if (value.get() && value->GetAsList(&list)) { + std::vector<Alarm> 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(const Alarm& other) = default; + +Alarm::~Alarm() { +} + +} // namespace extensions |