diff options
-rw-r--r-- | base/value_conversions.cc | 6 | ||||
-rw-r--r-- | base/value_conversions.h | 6 | ||||
-rw-r--r-- | chrome/browser/extensions/api/alarms/alarm_manager.cc | 233 | ||||
-rw-r--r-- | chrome/browser/extensions/api/alarms/alarm_manager.h | 46 | ||||
-rw-r--r-- | chrome/browser/extensions/api/alarms/alarms_api.cc | 91 | ||||
-rw-r--r-- | chrome/browser/extensions/api/alarms/alarms_api.h | 6 | ||||
-rw-r--r-- | chrome/browser/extensions/api/alarms/alarms_api_unittest.cc | 258 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_system.cc | 3 | ||||
-rw-r--r-- | chrome/browser/extensions/test_extension_system.cc | 5 | ||||
-rw-r--r-- | chrome/browser/extensions/test_extension_system.h | 5 | ||||
-rw-r--r-- | chrome/common/extensions/api/alarms.idl | 51 |
11 files changed, 463 insertions, 247 deletions
diff --git a/base/value_conversions.cc b/base/value_conversions.cc index caf9d72..6bde1ae 100644 --- a/base/value_conversions.cc +++ b/base/value_conversions.cc @@ -28,18 +28,18 @@ bool GetValueAsFilePath(const Value& value, FilePath* file_path) { // |Value| does not support 64-bit integers, and doubles do not have enough // precision, so we store the 64-bit time value as a string instead. -StringValue* CreateTimeValue(const Time& time) { +StringValue* CreateTimeDeltaValue(const TimeDelta& time) { std::string string_value = base::Int64ToString(time.ToInternalValue()); return new StringValue(string_value); } -bool GetValueAsTime(const Value& value, Time* time) { +bool GetValueAsTimeDelta(const Value& value, TimeDelta* time) { std::string str; int64 int_value; if (!value.GetAsString(&str) || !base::StringToInt64(str, &int_value)) return false; if (time) - *time = base::Time::FromInternalValue(int_value); + *time = TimeDelta::FromInternalValue(int_value); return true; } diff --git a/base/value_conversions.h b/base/value_conversions.h index 7825bb1..cb6d55c 100644 --- a/base/value_conversions.h +++ b/base/value_conversions.h @@ -14,7 +14,7 @@ class FilePath; namespace base { -class Time; +class TimeDelta; class StringValue; class Value; @@ -22,8 +22,8 @@ class Value; BASE_EXPORT StringValue* CreateFilePathValue(const FilePath& in_value); BASE_EXPORT bool GetValueAsFilePath(const Value& value, FilePath* file_path); -BASE_EXPORT StringValue* CreateTimeValue(const Time& time); -BASE_EXPORT bool GetValueAsTime(const Value& value, Time* time); +BASE_EXPORT StringValue* CreateTimeDeltaValue(const TimeDelta& time); +BASE_EXPORT bool GetValueAsTimeDelta(const Value& value, TimeDelta* time); } // namespace diff --git a/chrome/browser/extensions/api/alarms/alarm_manager.cc b/chrome/browser/extensions/api/alarms/alarm_manager.cc index 1dc8ba5..cf0e31a1 100644 --- a/chrome/browser/extensions/api/alarms/alarm_manager.cc +++ b/chrome/browser/extensions/api/alarms/alarm_manager.cc @@ -26,7 +26,7 @@ const char kOnAlarmEvent[] = "alarms.onAlarm"; // A list of alarms that this extension has set. const char kRegisteredAlarms[] = "alarms"; -const char kAlarmScheduledRunTime[] = "scheduled_run_time"; +const char kAlarmGranularity[] = "granularity"; // The minimum period between polling for alarms to run. const base::TimeDelta kDefaultMinPollPeriod = base::TimeDelta::FromMinutes(5); @@ -37,10 +37,10 @@ class DefaultAlarmDelegate : public AlarmManager::Delegate { virtual ~DefaultAlarmDelegate() {} virtual void OnAlarm(const std::string& extension_id, - const AlarmManager::Alarm& alarm) { + const Alarm& alarm) { ListValue args; std::string json_args; - args.Append(alarm.ToValue().release()); + args.Append(alarm.js_alarm->ToValue().release()); base::JSONWriter::Write(&args, &json_args); ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension( extension_id, kOnAlarmEvent, json_args, NULL, GURL()); @@ -50,33 +50,22 @@ class DefaultAlarmDelegate : public AlarmManager::Delegate { Profile* profile_; }; -// Contains the state we persist for each alarm. -struct AlarmState { - linked_ptr<AlarmManager::Alarm> alarm; - base::Time scheduled_run_time; - - AlarmState() {} - ~AlarmState() {} -}; - // 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<AlarmState> AlarmsFromValue(const base::ListValue* list) { - typedef AlarmManager::Alarm Alarm; - std::vector<AlarmState> alarms; +std::vector<Alarm> AlarmsFromValue(const base::ListValue* list) { + std::vector<Alarm> alarms; for (size_t i = 0; i < list->GetSize(); ++i) { base::DictionaryValue* alarm_dict = NULL; - AlarmState alarm; - alarm.alarm.reset(new Alarm()); + Alarm alarm; if (list->GetDictionary(i, &alarm_dict) && - Alarm::Populate(*alarm_dict, alarm.alarm.get())) { + api::alarms::Alarm::Populate(*alarm_dict, alarm.js_alarm.get())) { base::Value* time_value = NULL; - if (alarm_dict->Get(kAlarmScheduledRunTime, &time_value)) - base::GetValueAsTime(*time_value, &alarm.scheduled_run_time); + if (alarm_dict->Get(kAlarmGranularity, &time_value)) + base::GetValueAsTimeDelta(*time_value, &alarm.granularity); alarms.push_back(alarm); } } @@ -84,12 +73,13 @@ std::vector<AlarmState> AlarmsFromValue(const base::ListValue* list) { } scoped_ptr<base::ListValue> AlarmsToValue( - const std::vector<AlarmState>& alarms) { + const std::vector<Alarm>& alarms) { scoped_ptr<base::ListValue> list(new ListValue()); for (size_t i = 0; i < alarms.size(); ++i) { - scoped_ptr<base::DictionaryValue> alarm = alarms[i].alarm->ToValue().Pass(); - alarm->Set(kAlarmScheduledRunTime, - base::CreateTimeValue(alarms[i].scheduled_run_time)); + scoped_ptr<base::DictionaryValue> alarm = + alarms[i].js_alarm->ToValue().Pass(); + alarm->Set(kAlarmGranularity, + base::CreateTimeDeltaValue(alarms[i].granularity)); list->Append(alarm.release()); } return list.Pass(); @@ -100,8 +90,9 @@ scoped_ptr<base::ListValue> AlarmsToValue( // AlarmManager -AlarmManager::AlarmManager(Profile* profile) +AlarmManager::AlarmManager(Profile* profile, TimeProvider now) : profile_(profile), + now_(now), delegate_(new DefaultAlarmDelegate(profile)), last_poll_time_(base::Time()) { registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, @@ -116,18 +107,17 @@ AlarmManager::~AlarmManager() { } void AlarmManager::AddAlarm(const std::string& extension_id, - const linked_ptr<Alarm>& alarm) { - base::TimeDelta alarm_time = TimeDeltaFromDelay(alarm->delay_in_minutes); - AddAlarmImpl(extension_id, alarm, alarm_time); + const Alarm& alarm) { + AddAlarmImpl(extension_id, alarm); WriteToStorage(extension_id); } -const AlarmManager::Alarm* AlarmManager::GetAlarm( +const Alarm* AlarmManager::GetAlarm( const std::string& extension_id, const std::string& name) { AlarmIterator it = GetAlarmIterator(extension_id, name); if (it.first == alarms_.end()) return NULL; - return it.second->get(); + return &*it.second; } const AlarmManager::AlarmList* AlarmManager::GetAllAlarms( @@ -146,7 +136,7 @@ AlarmManager::AlarmIterator AlarmManager::GetAlarmIterator( for (AlarmList::iterator it = list->second.begin(); it != list->second.end(); ++it) { - if ((*it)->name == name) + if (it->js_alarm->name == name) return make_pair(list, it); } @@ -179,51 +169,45 @@ void AlarmManager::RemoveAllAlarms(const std::string& extension_id) { } void AlarmManager::RemoveAlarmIterator(const AlarmIterator& iter) { - // 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. - scheduled_times_.erase(iter.second->get()); - if (scheduled_times_.empty()) - timer_.Stop(); - - // Clean up our alarm list. 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(); } -void AlarmManager::OnAlarm(const std::string& extension_id, - const std::string& name) { - AlarmIterator it = GetAlarmIterator(extension_id, name); +void AlarmManager::OnAlarm(AlarmIterator it) { CHECK(it.first != alarms_.end()); - const Alarm* alarm = it.second->get(); - delegate_->OnAlarm(extension_id, *alarm); - - std::string extension_id_copy(extension_id); - if (!alarm->repeating) { - RemoveAlarmIterator(it); + 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()) { + alarm.js_alarm->scheduled_time = + (last_poll_time_ + + TimeDeltaFromDelay(*period_in_minutes)).ToJsTime(); } else { - // Update our scheduled time for the next alarm. - scheduled_times_[alarm].time = - last_poll_time_ + TimeDeltaFromDelay(alarm->delay_in_minutes); + RemoveAlarmIterator(it); } WriteToStorage(extension_id_copy); } void AlarmManager::AddAlarmImpl(const std::string& extension_id, - const linked_ptr<Alarm>& alarm, - base::TimeDelta time_delay) { + const Alarm& alarm) { // Override any old alarm with the same name. - AlarmIterator old_alarm = GetAlarmIterator(extension_id, alarm->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); - AlarmRuntimeInfo info; - info.extension_id = extension_id; - info.time = base::Time::Now() + time_delay; - scheduled_times_[alarm.get()] = info; // TODO(yoz): Is 0 really sane? There could be thrashing. ScheduleNextPoll(base::TimeDelta::FromMinutes(0)); @@ -234,19 +218,12 @@ void AlarmManager::WriteToStorage(const std::string& extension_id) { if (!storage) return; - std::vector<AlarmState> alarm_states; + scoped_ptr<Value> alarms; AlarmMap::iterator list = alarms_.find(extension_id); - if (list != alarms_.end()) { - for (AlarmList::iterator it = list->second.begin(); - it != list->second.end(); ++it) { - AlarmState pref; - pref.alarm = *it; - pref.scheduled_run_time = scheduled_times_[it->get()].time; - alarm_states.push_back(pref); - } - } - - scoped_ptr<Value> alarms(AlarmsToValue(alarm_states).release()); + if (list != alarms_.end()) + alarms.reset(AlarmsToValue(list->second).release()); + else + alarms.reset(AlarmsToValue(std::vector<Alarm>()).release()); storage->SetExtensionValue(extension_id, kRegisteredAlarms, alarms.Pass()); } @@ -256,20 +233,15 @@ void AlarmManager::ReadFromStorage(const std::string& extension_id, if (!value.get() || !value->GetAsList(&list)) return; - std::vector<AlarmState> alarm_states = AlarmsFromValue(list); + std::vector<Alarm> alarm_states = AlarmsFromValue(list); for (size_t i = 0; i < alarm_states.size(); ++i) { - base::TimeDelta delay = - alarm_states[i].scheduled_run_time - base::Time::Now(); - if (delay < base::TimeDelta::FromSeconds(0)) - delay = base::TimeDelta::FromSeconds(0); - - AddAlarmImpl(extension_id, alarm_states[i].alarm, delay); + AddAlarmImpl(extension_id, alarm_states[i]); } } void AlarmManager::ScheduleNextPoll(base::TimeDelta min_period) { // 0. If there are no alarms, stop the timer. - if (scheduled_times_.empty()) { + if (alarms_.empty()) { timer_.Stop(); return; } @@ -280,13 +252,19 @@ void AlarmManager::ScheduleNextPoll(base::TimeDelta min_period) { base::Time next_poll(last_poll_time_ + min_period); // Find the soonest alarm that is scheduled to run. - AlarmRuntimeInfoMap::iterator min_it = scheduled_times_.begin(); - for (AlarmRuntimeInfoMap::iterator it = min_it; - it != scheduled_times_.end(); ++it) { - if (it->second.time < min_it->second.time) - min_it = it; + // 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); + 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; + } } - base::Time soonest_alarm_time(min_it->second.time); // If the next alarm is more than min_period in the future, wait for it. // Otherwise, only poll as often as min_period. @@ -297,7 +275,7 @@ void AlarmManager::ScheduleNextPoll(base::TimeDelta min_period) { // Schedule the poll. next_poll_time_ = next_poll; base::TimeDelta delay = std::max(base::TimeDelta::FromSeconds(0), - next_poll - base::Time::Now()); + next_poll - now_()); timer_.Start(FROM_HERE, delay, this, @@ -305,27 +283,39 @@ void AlarmManager::ScheduleNextPoll(base::TimeDelta min_period) { } void AlarmManager::PollAlarms() { - last_poll_time_ = base::Time::Now(); - - // Run any alarms scheduled in the past. Note that we could remove alarms - // during iteration if they are non-repeating. - AlarmRuntimeInfoMap::iterator iter = scheduled_times_.begin(); - while (iter != scheduled_times_.end()) { - AlarmRuntimeInfoMap::iterator it = iter; - ++iter; - if (it->second.time <= next_poll_time_) { - OnAlarm(it->second.extension_id, it->first->name); + last_poll_time_ = 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) <= + next_poll_time_) { + OnAlarm(make_pair(cur_extension, cur_alarm)); + } } } // Schedule the next poll. The soonest it may happen is after - // kDefaultMinPollPeriod or after the shortest scheduled delay of any alarm, + // kDefaultMinPollPeriod or after the shortest granularity of any alarm, // whichever comes sooner. base::TimeDelta min_poll_period = kDefaultMinPollPeriod; - for (AlarmRuntimeInfoMap::iterator it = scheduled_times_.begin(); - it != scheduled_times_.end(); ++it) { - min_poll_period = std::min(TimeDeltaFromDelay(it->first->delay_in_minutes), - min_poll_period); + 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) { + if (l_it->granularity < min_poll_period) + min_poll_period = l_it->granularity; + } } ScheduleNextPoll(min_poll_period); } @@ -352,4 +342,47 @@ void AlarmManager::Observe( } } +// AlarmManager::Alarm + +Alarm::Alarm() + : js_alarm(new api::alarms::Alarm()) { +} + +Alarm::Alarm(const std::string& name, + const api::alarms::AlarmCreateInfo& create_info, + base::TimeDelta min_granularity, + TimeProvider now) + : js_alarm(new api::alarms::Alarm()) { + js_alarm->name = name; + + 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 diff --git a/chrome/browser/extensions/api/alarms/alarm_manager.h b/chrome/browser/extensions/api/alarms/alarm_manager.h index 6930998..2c46f9f 100644 --- a/chrome/browser/extensions/api/alarms/alarm_manager.h +++ b/chrome/browser/extensions/api/alarms/alarm_manager.h @@ -23,14 +23,33 @@ namespace extensions { class ExtensionAlarmsSchedulingTest; +struct Alarm { + typedef base::Time (*TimeProvider)(); + + Alarm(); + Alarm(const std::string& name, + const api::alarms::AlarmCreateInfo& create_info, + base::TimeDelta min_granularity, + TimeProvider now); + ~Alarm(); + + linked_ptr<api::alarms::Alarm> js_alarm; + // The granularity isn't exposed to the extension's javascript, but we poll at + // least as often as the shortest alarm's granularity. It's initialized as + // the relative delay requested in creation, even if creation uses an absolute + // time. This will always be at least as large as the min_granularity + // constructor argument. + base::TimeDelta granularity; +}; + // Manages the currently pending alarms for every extension in a profile. // There is one manager per virtual Profile. class AlarmManager : public content::NotificationObserver, public base::SupportsWeakPtr<AlarmManager> { public: - typedef extensions::api::alarms::Alarm Alarm; - typedef std::vector<linked_ptr<Alarm> > AlarmList; + typedef base::Time (*TimeProvider)(); + typedef std::vector<Alarm> AlarmList; class Delegate { public: @@ -40,7 +59,8 @@ class AlarmManager const Alarm& alarm) = 0; }; - explicit AlarmManager(Profile* profile); + // 'now' is usually &base::Time::Now. + explicit AlarmManager(Profile* profile, TimeProvider now); virtual ~AlarmManager(); // Override the default delegate. Callee assumes onwership. Used for testing. @@ -48,7 +68,7 @@ class AlarmManager // Adds |alarm| for the given extension, and starts the timer. void AddAlarm(const std::string& extension_id, - const linked_ptr<Alarm>& alarm); + const Alarm& alarm); // Returns the alarm with the given name, or NULL if none exists. const Alarm* GetAlarm(const std::string& extension_id, @@ -70,6 +90,8 @@ class AlarmManager FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsTest, Clear); friend class ExtensionAlarmsSchedulingTest; FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, PollScheduling); + FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, + ReleasedExtensionPollsInfrequently); FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, TimerRunning); typedef std::string ExtensionId; @@ -79,12 +101,6 @@ class AlarmManager // "Not found" is represented by <alarms_.end(), invalid_iterator>. typedef std::pair<AlarmMap::iterator, AlarmList::iterator> AlarmIterator; - struct AlarmRuntimeInfo { - std::string extension_id; - base::Time time; - }; - typedef std::map<const Alarm*, AlarmRuntimeInfo> AlarmRuntimeInfoMap; - // Helper to return the iterators within the AlarmMap and AlarmList for the // matching alarm, or an iterator to the end of the AlarmMap if none were // found. @@ -96,12 +112,11 @@ class AlarmManager void RemoveAlarmIterator(const AlarmIterator& iter); // Callback for when an alarm fires. - void OnAlarm(const std::string& extension_id, const std::string& name); + void OnAlarm(AlarmIterator iter); // Internal helper to add an alarm and start the timer with the given delay. void AddAlarmImpl(const std::string& extension_id, - const linked_ptr<Alarm>& alarm, - base::TimeDelta time_delay); + const Alarm& alarm); // Syncs our alarm data for the given extension to/from the state storage. void WriteToStorage(const std::string& extension_id); @@ -122,6 +137,7 @@ class AlarmManager const content::NotificationDetails& details) OVERRIDE; Profile* profile_; + const TimeProvider now_; content::NotificationRegistrar registrar_; scoped_ptr<Delegate> delegate_; @@ -129,11 +145,9 @@ class AlarmManager base::OneShotTimer<AlarmManager> timer_; // A map of our pending alarms, per extension. + // Invariant: None of the AlarmLists are empty. AlarmMap alarms_; - // A map of the next scheduled times associated with each alarm. - AlarmRuntimeInfoMap scheduled_times_; - // The previous and next time that alarms were and will be run. base::Time last_poll_time_; base::Time next_poll_time_; diff --git a/chrome/browser/extensions/api/alarms/alarms_api.cc b/chrome/browser/extensions/api/alarms/alarms_api.cc index 8591f01..72101b6 100644 --- a/chrome/browser/extensions/api/alarms/alarms_api.cc +++ b/chrome/browser/extensions/api/alarms/alarms_api.cc @@ -19,21 +19,27 @@ namespace { const char kDefaultAlarmName[] = ""; const char kAlarmNotFound[] = "No alarm named '*' exists."; -const char kDelayLessThanMinimum[] = "Delay is less than minimum of * minutes."; -const char kDelayIsNonInteger[] = "Delay is not an integer value."; - +const char kDelayLessThanMinimum[] = "* is less than minimum of * minutes."; +const char kDelayIsNonInteger[] = "* is not an integer value."; +const char kBothRelativeAndAbsoluteTime[] = + "Cannot set both when and delayInMinutes."; +const char kNoScheduledTime[] = + "Must set at least one of when, delayInMinutes, or periodInMinutes."; const int kReleaseDelayMinimum = 5; const int kDevDelayMinimum = 0; -bool ValidateDelayTime(double delay_in_minutes, - const Extension* extension, - std::string* error) { +bool ValidateDelay(double delay_in_minutes, + const Extension* extension, + const std::string& delay_or_period, + std::string* error) { double delay_minimum = kDevDelayMinimum; if (extension->location() != Extension::LOAD) { // In release mode we check for integer delay values and a stricter delay // minimum. if (delay_in_minutes != static_cast<int>(delay_in_minutes)) { - *error = kDelayIsNonInteger; + *error = ExtensionErrorUtils::FormatErrorMessage( + kDelayIsNonInteger, + delay_or_period); return false; } delay_minimum = kReleaseDelayMinimum; @@ -41,29 +47,71 @@ bool ValidateDelayTime(double delay_in_minutes, // Validate against our found delay minimum. if (delay_in_minutes < delay_minimum) { - *error = ExtensionErrorUtils::FormatErrorMessage(kDelayLessThanMinimum, + *error = ExtensionErrorUtils::FormatErrorMessage( + kDelayLessThanMinimum, + delay_or_period, base::DoubleToString(delay_minimum)); return false; } + + return true; +} + +bool ValidateAlarmCreateInfo(const alarms::AlarmCreateInfo& create_info, + const Extension* extension, + std::string* error) { + if (create_info.delay_in_minutes.get() && + create_info.when.get()) { + *error = kBothRelativeAndAbsoluteTime; + return false; + } + if (create_info.delay_in_minutes == NULL && + create_info.when == NULL && + create_info.period_in_minutes == NULL) { + *error = kNoScheduledTime; + return false; + } + + // Users can always use an absolute timeout to request an arbitrarily-short or + // negative delay. We won't honor the short timeout, but we can't check it + // and warn the user because it would introduce race conditions (say they + // compute a long-enough timeout, but then the call into the alarms interface + // gets delayed past the boundary). However, it's still worth warning about + // relative delays that are shorter than we'll honor. + if (create_info.delay_in_minutes.get()) { + if (!ValidateDelay(*create_info.delay_in_minutes, + extension, "Delay", error)) + return false; + } + if (create_info.period_in_minutes.get()) { + if (!ValidateDelay(*create_info.period_in_minutes, + extension, "Period", error)) + return false; + } + return true; } } // namespace +AlarmsCreateFunction::AlarmsCreateFunction() + : now_(&base::Time::Now) { +} + bool AlarmsCreateFunction::RunImpl() { scoped_ptr<alarms::Create::Params> params( alarms::Create::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params.get()); - - double delay_in_minutes = params->alarm_info.delay_in_minutes; - if (!ValidateDelayTime(delay_in_minutes, GetExtension(), &error_)) + if (!ValidateAlarmCreateInfo( + params->alarm_info, GetExtension(), &error_)) return false; - linked_ptr<AlarmManager::Alarm> alarm(new AlarmManager::Alarm()); - alarm->name = params->name.get() ? *params->name : kDefaultAlarmName; - alarm->delay_in_minutes = params->alarm_info.delay_in_minutes; - alarm->repeating = params->alarm_info.repeating.get() ? - *params->alarm_info.repeating : false; + Alarm alarm(params->name.get() ? *params->name : kDefaultAlarmName, + params->alarm_info, + base::TimeDelta::FromMinutes( + GetExtension()->location() == Extension::LOAD ? + kDevDelayMinimum : kReleaseDelayMinimum), + now_); ExtensionSystem::Get(profile())->alarm_manager()->AddAlarm( extension_id(), alarm); @@ -76,7 +124,7 @@ bool AlarmsGetFunction::RunImpl() { EXTENSION_FUNCTION_VALIDATE(params.get()); std::string name = params->name.get() ? *params->name : kDefaultAlarmName; - const AlarmManager::Alarm* alarm = + const Alarm* alarm = ExtensionSystem::Get(profile())->alarm_manager()->GetAlarm( extension_id(), name); @@ -85,7 +133,7 @@ bool AlarmsGetFunction::RunImpl() { return false; } - result_.reset(alarms::Get::Result::Create(*alarm)); + result_.reset(alarms::Get::Result::Create(*alarm->js_alarm)); return true; } @@ -94,7 +142,12 @@ bool AlarmsGetAllFunction::RunImpl() { ExtensionSystem::Get(profile())->alarm_manager()->GetAllAlarms( extension_id()); if (alarms) { - result_.reset(alarms::GetAll::Result::Create(*alarms)); + std::vector<linked_ptr<extensions::api::alarms::Alarm> > create_arg; + create_arg.reserve(alarms->size()); + for (size_t i = 0, size = alarms->size(); i < size; ++i) { + create_arg.push_back((*alarms)[i].js_alarm); + } + result_.reset(alarms::GetAll::Result::Create(create_arg)); } else { result_.reset(new base::ListValue()); } diff --git a/chrome/browser/extensions/api/alarms/alarms_api.h b/chrome/browser/extensions/api/alarms/alarms_api.h index eefe4b3..809c890 100644 --- a/chrome/browser/extensions/api/alarms/alarms_api.h +++ b/chrome/browser/extensions/api/alarms/alarms_api.h @@ -11,12 +11,18 @@ namespace extensions { class AlarmsCreateFunction : public SyncExtensionFunction { + typedef base::Time (*TimeProvider)(); + public: + AlarmsCreateFunction(); + explicit AlarmsCreateFunction(TimeProvider now) : now_(now) {} protected: virtual ~AlarmsCreateFunction() {} // ExtensionFunction: virtual bool RunImpl() OVERRIDE; DECLARE_EXTENSION_FUNCTION_NAME("alarms.create"); + private: + TimeProvider now_; }; class AlarmsGetFunction : public SyncExtensionFunction { diff --git a/chrome/browser/extensions/api/alarms/alarms_api_unittest.cc b/chrome/browser/extensions/api/alarms/alarms_api_unittest.cc index 1bc3b06..51409fd 100644 --- a/chrome/browser/extensions/api/alarms/alarms_api_unittest.cc +++ b/chrome/browser/extensions/api/alarms/alarms_api_unittest.cc @@ -5,6 +5,7 @@ // This file tests the chrome.alarms extension API. #include "base/values.h" +#include "base/test/mock_time_provider.h" #include "chrome/browser/browser_process_impl.h" #include "chrome/browser/extensions/api/alarms/alarm_manager.h" #include "chrome/browser/extensions/api/alarms/alarms_api.h" @@ -13,8 +14,11 @@ #include "chrome/browser/profiles/profile_manager.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "chrome/test/base/testing_browser_process.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +typedef extensions::api::alarms::Alarm JsAlarm; + namespace utils = extension_function_test_utils; namespace extensions { @@ -26,8 +30,8 @@ class AlarmDelegate : public AlarmManager::Delegate { public: virtual ~AlarmDelegate() {} virtual void OnAlarm(const std::string& extension_id, - const AlarmManager::Alarm& alarm) { - alarms_seen.push_back(alarm.name); + const Alarm& alarm) { + alarms_seen.push_back(alarm.js_alarm->name); MessageLoop::current()->Quit(); } @@ -43,7 +47,7 @@ class ExtensionAlarmsTest : public BrowserWithTestWindowTest { TestExtensionSystem* system = static_cast<TestExtensionSystem*>( ExtensionSystem::Get(browser()->profile())); - system->CreateAlarmManager(); + system->CreateAlarmManager(&base::MockTimeProvider::StaticNow); alarm_manager_ = system->alarm_manager(); alarm_delegate_ = new AlarmDelegate(); @@ -51,6 +55,10 @@ class ExtensionAlarmsTest : public BrowserWithTestWindowTest { extension_ = utils::CreateEmptyExtensionWithLocation( extensions::Extension::LOAD); + + current_time_ = base::Time::FromDoubleT(10); + ON_CALL(mock_time_, Now()) + .WillByDefault(testing::ReturnPointee(¤t_time_)); } base::Value* RunFunctionWithExtension( @@ -82,18 +90,23 @@ class ExtensionAlarmsTest : public BrowserWithTestWindowTest { return utils::RunFunctionAndReturnError(function, args, browser()); } + void CreateAlarm(const std::string& args) { + RunFunction(new AlarmsCreateFunction(&base::MockTimeProvider::StaticNow), + args); + } + // Takes a JSON result from a function and converts it to a vector of - // Alarms. - AlarmManager::AlarmList ToAlarmList(base::ListValue* value) { - AlarmManager::AlarmList list; + // JsAlarms. + std::vector<linked_ptr<JsAlarm> > ToAlarmList(base::ListValue* value) { + std::vector<linked_ptr<JsAlarm> > list; for (size_t i = 0; i < value->GetSize(); ++i) { - linked_ptr<AlarmManager::Alarm> alarm(new AlarmManager::Alarm); + linked_ptr<JsAlarm> alarm(new JsAlarm); base::DictionaryValue* alarm_value; if (!value->GetDictionary(i, &alarm_value)) { ADD_FAILURE() << "Expected a list of Alarm objects."; return list; } - EXPECT_TRUE(AlarmManager::Alarm::Populate(*alarm_value, alarm.get())); + EXPECT_TRUE(JsAlarm::Populate(*alarm_value, alarm.get())); list.push_back(alarm); } return list; @@ -104,33 +117,37 @@ class ExtensionAlarmsTest : public BrowserWithTestWindowTest { CHECK(num_alarms <= 3); const char* kCreateArgs[] = { - "[null, {\"delayInMinutes\": 0.001, \"repeating\": true}]", - "[\"7\", {\"delayInMinutes\": 7, \"repeating\": true}]", - "[\"0\", {\"delayInMinutes\": 0}]" + "[null, {\"periodInMinutes\": 0.001}]", + "[\"7\", {\"periodInMinutes\": 7}]", + "[\"0\", {\"delayInMinutes\": 0}]", }; for (size_t i = 0; i < num_alarms; ++i) { scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDict( - new AlarmsCreateFunction(), kCreateArgs[i])); + new AlarmsCreateFunction(&base::MockTimeProvider::StaticNow), + kCreateArgs[i])); EXPECT_FALSE(result.get()); } } protected: + base::Time current_time_; + testing::NiceMock<base::MockTimeProvider> mock_time_; AlarmManager* alarm_manager_; AlarmDelegate* alarm_delegate_; scoped_refptr<extensions::Extension> extension_; }; TEST_F(ExtensionAlarmsTest, Create) { + current_time_ = base::Time::FromDoubleT(10); // Create 1 non-repeating alarm. - RunFunction(new AlarmsCreateFunction(), "[null, {\"delayInMinutes\": 0}]"); + CreateAlarm("[null, {\"delayInMinutes\": 0}]"); - const AlarmManager::Alarm* alarm = + const Alarm* alarm = alarm_manager_->GetAlarm(extension_->id(), ""); ASSERT_TRUE(alarm); - EXPECT_EQ("", alarm->name); - EXPECT_EQ(0, alarm->delay_in_minutes); - EXPECT_FALSE(alarm->repeating); + EXPECT_EQ("", alarm->js_alarm->name); + EXPECT_DOUBLE_EQ(10000, alarm->js_alarm->scheduled_time); + EXPECT_FALSE(alarm->js_alarm->period_in_minutes.get()); // Now wait for the alarm to fire. Our test delegate will quit the // MessageLoop when that happens. @@ -148,21 +165,25 @@ TEST_F(ExtensionAlarmsTest, Create) { } TEST_F(ExtensionAlarmsTest, CreateRepeating) { + current_time_ = base::Time::FromDoubleT(10); + // Create 1 repeating alarm. - RunFunction(new AlarmsCreateFunction(), - "[null, {\"delayInMinutes\": 0.001, \"repeating\": true}]"); + CreateAlarm("[null, {\"periodInMinutes\": 0.001}]"); - const AlarmManager::Alarm* alarm = + const Alarm* alarm = alarm_manager_->GetAlarm(extension_->id(), ""); ASSERT_TRUE(alarm); - EXPECT_EQ("", alarm->name); - EXPECT_EQ(0.001, alarm->delay_in_minutes); - EXPECT_TRUE(alarm->repeating); + EXPECT_EQ("", alarm->js_alarm->name); + EXPECT_DOUBLE_EQ(10060, alarm->js_alarm->scheduled_time); + EXPECT_THAT(alarm->js_alarm->period_in_minutes, + testing::Pointee(testing::DoubleEq(0.001))); + current_time_ += base::TimeDelta::FromSeconds(1); // Now wait for the alarm to fire. Our test delegate will quit the // MessageLoop when that happens. MessageLoop::current()->Run(); + current_time_ += base::TimeDelta::FromSeconds(1); // Wait again, and ensure the alarm fires again. alarm_manager_->ScheduleNextPoll(base::TimeDelta::FromSeconds(0)); MessageLoop::current()->Run(); @@ -171,53 +192,109 @@ TEST_F(ExtensionAlarmsTest, CreateRepeating) { EXPECT_EQ("", alarm_delegate_->alarms_seen[0]); } +TEST_F(ExtensionAlarmsTest, CreateAbsolute) { + current_time_ = base::Time::FromDoubleT(9.99); + CreateAlarm("[null, {\"when\": 10001}]"); + + const Alarm* alarm = + alarm_manager_->GetAlarm(extension_->id(), ""); + ASSERT_TRUE(alarm); + EXPECT_EQ("", alarm->js_alarm->name); + EXPECT_DOUBLE_EQ(10001, alarm->js_alarm->scheduled_time); + EXPECT_THAT(alarm->js_alarm->period_in_minutes, + testing::IsNull()); + + current_time_ = base::Time::FromDoubleT(10.1); + // Now wait for the alarm to fire. Our test delegate will quit the + // MessageLoop when that happens. + MessageLoop::current()->Run(); + + ASSERT_FALSE(alarm_manager_->GetAlarm(extension_->id(), "")); + + ASSERT_EQ(1u, alarm_delegate_->alarms_seen.size()); + EXPECT_EQ("", alarm_delegate_->alarms_seen[0]); +} + +TEST_F(ExtensionAlarmsTest, CreateRepeatingWithQuickFirstCall) { + current_time_ = base::Time::FromDoubleT(9.99); + CreateAlarm("[null, {\"when\": 10001, \"periodInMinutes\": 0.001}]"); + + const Alarm* alarm = + alarm_manager_->GetAlarm(extension_->id(), ""); + ASSERT_TRUE(alarm); + EXPECT_EQ("", alarm->js_alarm->name); + EXPECT_DOUBLE_EQ(10001, alarm->js_alarm->scheduled_time); + EXPECT_THAT(alarm->js_alarm->period_in_minutes, + testing::Pointee(testing::DoubleEq(0.001))); + + current_time_ = base::Time::FromDoubleT(10.1); + // Now wait for the alarm to fire. Our test delegate will quit the + // MessageLoop when that happens. + MessageLoop::current()->Run(); + + ASSERT_TRUE(alarm_manager_->GetAlarm(extension_->id(), "")); + EXPECT_THAT(alarm_delegate_->alarms_seen, testing::ElementsAre("")); + + current_time_ = base::Time::FromDoubleT(10.7); + MessageLoop::current()->Run(); + + ASSERT_TRUE(alarm_manager_->GetAlarm(extension_->id(), "")); + EXPECT_THAT(alarm_delegate_->alarms_seen, testing::ElementsAre("", "")); +} + TEST_F(ExtensionAlarmsTest, CreateDupe) { + current_time_ = base::Time::FromDoubleT(10); + // Create 2 duplicate alarms. The first should be overridden. - RunFunction(new AlarmsCreateFunction(), "[\"dup\", {\"delayInMinutes\": 1}]"); - RunFunction(new AlarmsCreateFunction(), "[\"dup\", {\"delayInMinutes\": 7}]"); + CreateAlarm("[\"dup\", {\"delayInMinutes\": 1}]"); + CreateAlarm("[\"dup\", {\"delayInMinutes\": 7}]"); { const AlarmManager::AlarmList* alarms = alarm_manager_->GetAllAlarms(extension_->id()); ASSERT_TRUE(alarms); EXPECT_EQ(1u, alarms->size()); - EXPECT_EQ(7, (*alarms)[0]->delay_in_minutes); + EXPECT_DOUBLE_EQ(430000, (*alarms)[0].js_alarm->scheduled_time); } } TEST_F(ExtensionAlarmsTest, CreateDelayBelowMinimum) { // Create an alarm with delay below the minimum accepted value. - std::string error = RunFunctionAndReturnError(new AlarmsCreateFunction(), + std::string error = RunFunctionAndReturnError( + new AlarmsCreateFunction(&base::MockTimeProvider::StaticNow), "[\"negative\", {\"delayInMinutes\": -0.2}]"); EXPECT_FALSE(error.empty()); } TEST_F(ExtensionAlarmsTest, Get) { + current_time_ = base::Time::FromDoubleT(4); + // Create 2 alarms, and make sure we can query them. CreateAlarms(2); // Get the default one. { - AlarmManager::Alarm alarm; + JsAlarm alarm; scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDict( new AlarmsGetFunction(), "[null]")); ASSERT_TRUE(result.get()); - EXPECT_TRUE(AlarmManager::Alarm::Populate(*result, &alarm)); + EXPECT_TRUE(JsAlarm::Populate(*result, &alarm)); EXPECT_EQ("", alarm.name); - EXPECT_EQ(0.001, alarm.delay_in_minutes); - EXPECT_TRUE(alarm.repeating); + EXPECT_DOUBLE_EQ(4060, alarm.scheduled_time); + EXPECT_THAT(alarm.period_in_minutes, + testing::Pointee(testing::DoubleEq(0.001))); } // Get "7". { - AlarmManager::Alarm alarm; + JsAlarm alarm; scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDict( new AlarmsGetFunction(), "[\"7\"]")); ASSERT_TRUE(result.get()); - EXPECT_TRUE(AlarmManager::Alarm::Populate(*result, &alarm)); + EXPECT_TRUE(JsAlarm::Populate(*result, &alarm)); EXPECT_EQ("7", alarm.name); - EXPECT_EQ(7, alarm.delay_in_minutes); - EXPECT_TRUE(alarm.repeating); + EXPECT_EQ(424000, alarm.scheduled_time); + EXPECT_THAT(alarm.period_in_minutes, testing::Pointee(7)); } // Get a non-existent one. @@ -233,7 +310,7 @@ TEST_F(ExtensionAlarmsTest, GetAll) { { scoped_ptr<base::ListValue> result(RunFunctionAndReturnList( new AlarmsGetAllFunction(), "[]")); - AlarmManager::AlarmList alarms = ToAlarmList(result.get()); + std::vector<linked_ptr<JsAlarm> > alarms = ToAlarmList(result.get()); EXPECT_EQ(0u, alarms.size()); } @@ -243,16 +320,15 @@ TEST_F(ExtensionAlarmsTest, GetAll) { { scoped_ptr<base::ListValue> result(RunFunctionAndReturnList( new AlarmsGetAllFunction(), "[null]")); - AlarmManager::AlarmList alarms = ToAlarmList(result.get()); + std::vector<linked_ptr<JsAlarm> > alarms = ToAlarmList(result.get()); EXPECT_EQ(2u, alarms.size()); // Test the "7" alarm. - AlarmManager::Alarm* alarm = alarms[0].get(); + JsAlarm* alarm = alarms[0].get(); if (alarm->name != "7") alarm = alarms[1].get(); EXPECT_EQ("7", alarm->name); - EXPECT_EQ(7, alarm->delay_in_minutes); - EXPECT_TRUE(alarm->repeating); + EXPECT_THAT(alarm->period_in_minutes, testing::Pointee(7)); } } @@ -276,7 +352,8 @@ TEST_F(ExtensionAlarmsTest, Clear) { alarm_manager_->GetAllAlarms(extension_->id()); ASSERT_TRUE(alarms); EXPECT_EQ(1u, alarms->size()); - EXPECT_EQ(0.001, (*alarms)[0]->delay_in_minutes); + EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes, + testing::Pointee(0.001)); } // Now wait for the alarms to fire, and ensure the cancelled alarms don't @@ -293,7 +370,8 @@ TEST_F(ExtensionAlarmsTest, Clear) { alarm_manager_->GetAllAlarms(extension_->id()); ASSERT_TRUE(alarms); EXPECT_EQ(1u, alarms->size()); - EXPECT_EQ(0.001, (*alarms)[0]->delay_in_minutes); + EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes, + testing::Pointee(0.001)); } } @@ -327,70 +405,64 @@ class ExtensionAlarmsSchedulingTest : public ExtensionAlarmsTest { public: // Get the time that the alarm named is scheduled to run. base::Time GetScheduledTime(const std::string& alarm_name) { - const extensions::AlarmManager::Alarm* alarm = + const extensions::Alarm* alarm = alarm_manager_->GetAlarm(extension_->id(), alarm_name); CHECK(alarm); - return alarm_manager_->scheduled_times_[alarm].time; + return base::Time::FromJsTime(alarm->js_alarm->scheduled_time); } }; TEST_F(ExtensionAlarmsSchedulingTest, PollScheduling) { { - RunFunction(new AlarmsCreateFunction(), - "[\"a\", {\"delayInMinutes\": 6, \"repeating\": true}]"); - RunFunction(new AlarmsCreateFunction(), - "[\"bb\", {\"delayInMinutes\": 8, \"repeating\": true}]"); + CreateAlarm("[\"a\", {\"periodInMinutes\": 6}]"); + CreateAlarm("[\"bb\", {\"periodInMinutes\": 8}]"); EXPECT_EQ(GetScheduledTime("a"), alarm_manager_->next_poll_time_); alarm_manager_->RemoveAllAlarms(extension_->id()); } { - RunFunction(new AlarmsCreateFunction(), - "[\"a\", {\"delayInMinutes\": 10}]"); - RunFunction(new AlarmsCreateFunction(), - "[\"bb\", {\"delayInMinutes\": 21}]"); + CreateAlarm("[\"a\", {\"delayInMinutes\": 10}]"); + CreateAlarm("[\"bb\", {\"delayInMinutes\": 21}]"); EXPECT_EQ(GetScheduledTime("a"), alarm_manager_->next_poll_time_); alarm_manager_->RemoveAllAlarms(extension_->id()); } { - RunFunction(new AlarmsCreateFunction(), - "[\"a\", {\"delayInMinutes\": 10, \"repeating\": true}]"); - linked_ptr<AlarmManager::Alarm> alarm(new AlarmManager::Alarm()); - alarm->name = "bb"; - alarm->delay_in_minutes = 30; - alarm->repeating = true; - alarm_manager_->AddAlarmImpl(extension_->id(), alarm, - base::TimeDelta::FromMinutes(3)); - EXPECT_EQ(GetScheduledTime("bb"), alarm_manager_->next_poll_time_); + current_time_ = base::Time::FromDoubleT(10); + CreateAlarm("[\"a\", {\"periodInMinutes\": 10}]"); + Alarm alarm; + alarm.js_alarm->name = "bb"; + alarm.js_alarm->scheduled_time = 30 * 60000; + alarm.js_alarm->period_in_minutes.reset(new double(30)); + alarm_manager_->AddAlarmImpl(extension_->id(), alarm); + EXPECT_DOUBLE_EQ(GetScheduledTime("a").ToDoubleT(), + alarm_manager_->next_poll_time_.ToDoubleT()); alarm_manager_->RemoveAllAlarms(extension_->id()); } { - linked_ptr<AlarmManager::Alarm> alarm(new AlarmManager::Alarm()); - alarm->name = "bb"; - alarm->delay_in_minutes = 3; - alarm->repeating = true; - alarm_manager_->AddAlarmImpl(extension_->id(), alarm, - base::TimeDelta::FromSeconds(0)); + current_time_ = base::Time::FromDoubleT(3 * 60 + 1); + Alarm alarm; + alarm.js_alarm->name = "bb"; + alarm.js_alarm->scheduled_time = 3 * 60000; + alarm.js_alarm->period_in_minutes.reset(new double(3)); + alarm_manager_->AddAlarmImpl(extension_->id(), alarm); MessageLoop::current()->Run(); EXPECT_EQ(alarm_manager_->last_poll_time_ + base::TimeDelta::FromMinutes(3), alarm_manager_->next_poll_time_); alarm_manager_->RemoveAllAlarms(extension_->id()); } { - RunFunction(new AlarmsCreateFunction(), - "[\"a\", {\"delayInMinutes\": 2, \"repeating\": true}]"); + current_time_ = base::Time::FromDoubleT(4 * 60 + 1); + CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]"); alarm_manager_->RemoveAlarm(extension_->id(), "a"); - linked_ptr<AlarmManager::Alarm> alarm2(new AlarmManager::Alarm()); - alarm2->name = "bb"; - alarm2->delay_in_minutes = 4; - alarm2->repeating = true; - alarm_manager_->AddAlarmImpl(extension_->id(), alarm2, - base::TimeDelta::FromMinutes(3)); - linked_ptr<AlarmManager::Alarm> alarm3(new AlarmManager::Alarm()); - alarm3->name = "ccc"; - alarm3->delay_in_minutes = 25; - alarm3->repeating = true; - alarm_manager_->AddAlarmImpl(extension_->id(), alarm3, - base::TimeDelta::FromSeconds(0)); + Alarm alarm2; + alarm2.js_alarm->name = "bb"; + alarm2.js_alarm->scheduled_time = 4 * 60000; + alarm2.js_alarm->period_in_minutes.reset(new double(4)); + alarm_manager_->AddAlarmImpl(extension_->id(), alarm2); + Alarm alarm3; + alarm3.js_alarm->name = "ccc"; + alarm3.js_alarm->scheduled_time = 25 * 60000; + alarm3.js_alarm->period_in_minutes.reset(new double(25)); + alarm_manager_->AddAlarmImpl(extension_->id(), alarm3); MessageLoop::current()->Run(); EXPECT_EQ(alarm_manager_->last_poll_time_ + base::TimeDelta::FromMinutes(4), alarm_manager_->next_poll_time_); @@ -398,15 +470,31 @@ TEST_F(ExtensionAlarmsSchedulingTest, PollScheduling) { } } +TEST_F(ExtensionAlarmsSchedulingTest, ReleasedExtensionPollsInfrequently) { + extension_ = utils::CreateEmptyExtensionWithLocation( + extensions::Extension::INTERNAL); + current_time_ = base::Time::FromJsTime(300000); + CreateAlarm("[\"a\", {\"when\": 300010}]"); + CreateAlarm("[\"b\", {\"when\": 360000}]"); + + // In released extensions, we set the granularity to at least 5 + // minutes, but AddAlarm schedules its next poll precisely. + EXPECT_DOUBLE_EQ(300010, alarm_manager_->next_poll_time_.ToJsTime()); + + // Run an iteration to see the effect of the granularity. + current_time_ = base::Time::FromJsTime(300020); + MessageLoop::current()->Run(); + EXPECT_DOUBLE_EQ(300020, alarm_manager_->last_poll_time_.ToJsTime()); + EXPECT_DOUBLE_EQ(600020, alarm_manager_->next_poll_time_.ToJsTime()); +} + TEST_F(ExtensionAlarmsSchedulingTest, TimerRunning) { EXPECT_FALSE(alarm_manager_->timer_.IsRunning()); - RunFunction(new AlarmsCreateFunction(), - "[\"a\", {\"delayInMinutes\": 0.001}]"); + CreateAlarm("[\"a\", {\"delayInMinutes\": 0.001}]"); EXPECT_TRUE(alarm_manager_->timer_.IsRunning()); MessageLoop::current()->Run(); EXPECT_FALSE(alarm_manager_->timer_.IsRunning()); - RunFunction(new AlarmsCreateFunction(), - "[\"bb\", {\"delayInMinutes\": 10}]"); + CreateAlarm("[\"bb\", {\"delayInMinutes\": 10}]"); EXPECT_TRUE(alarm_manager_->timer_.IsRunning()); alarm_manager_->RemoveAllAlarms(extension_->id()); EXPECT_FALSE(alarm_manager_->timer_.IsRunning()); diff --git a/chrome/browser/extensions/extension_system.cc b/chrome/browser/extensions/extension_system.cc index a5793e4..5637588 100644 --- a/chrome/browser/extensions/extension_system.cc +++ b/chrome/browser/extensions/extension_system.cc @@ -276,7 +276,8 @@ void ExtensionSystemImpl::Init(bool extensions_enabled) { shared_->InitInfoMap(); extension_process_manager_.reset(ExtensionProcessManager::Create(profile_)); - alarm_manager_.reset(new extensions::AlarmManager(profile_)); + alarm_manager_.reset(new extensions::AlarmManager(profile_, + &base::Time::Now)); shared_->Init(extensions_enabled); } diff --git a/chrome/browser/extensions/test_extension_system.cc b/chrome/browser/extensions/test_extension_system.cc index 493ee24..8fbb699 100644 --- a/chrome/browser/extensions/test_extension_system.cc +++ b/chrome/browser/extensions/test_extension_system.cc @@ -36,8 +36,9 @@ void TestExtensionSystem::CreateExtensionProcessManager() { extension_process_manager_.reset(ExtensionProcessManager::Create(profile_)); } -void TestExtensionSystem::CreateAlarmManager() { - alarm_manager_.reset(new extensions::AlarmManager(profile_)); +void TestExtensionSystem::CreateAlarmManager( + extensions::AlarmManager::TimeProvider now) { + alarm_manager_.reset(new extensions::AlarmManager(profile_, now)); } ExtensionService* TestExtensionSystem::CreateExtensionService( diff --git a/chrome/browser/extensions/test_extension_system.h b/chrome/browser/extensions/test_extension_system.h index eaab4ed..630e857 100644 --- a/chrome/browser/extensions/test_extension_system.h +++ b/chrome/browser/extensions/test_extension_system.h @@ -10,6 +10,9 @@ class CommandLine; class FilePath; +namespace base { +class Time; +} // Test ExtensionSystem, for use with TestingProfile. class TestExtensionSystem : public ExtensionSystem { @@ -35,7 +38,7 @@ class TestExtensionSystem : public ExtensionSystem { void CreateExtensionProcessManager(); // Creates an AlarmManager. Will be NULL otherwise. - void CreateAlarmManager(); + void CreateAlarmManager(base::Time (*now)()); virtual void Init(bool extensions_enabled) OVERRIDE {} virtual ExtensionService* extension_service() OVERRIDE; diff --git a/chrome/common/extensions/api/alarms.idl b/chrome/common/extensions/api/alarms.idl index cd61d86..92771b6 100644 --- a/chrome/common/extensions/api/alarms.idl +++ b/chrome/common/extensions/api/alarms.idl @@ -10,40 +10,57 @@ namespace alarms { // Name of this alarm. DOMString name; - // Original length of time in minutes after which the onAlarm event should - // fire. - // TODO: need minimum=0 - double delayInMinutes; + // Time at which this alarm was scheduled to fire, in milliseconds past the + // epoch (e.g. Date.now() + n). For performance reasons, the alarm may have + // been delayed an arbitrary amount beyond this. + double scheduledTime; - // True if the alarm repeatedly fires at regular intervals, false if it - // only fires once. - boolean repeating; + // If not null, the alarm is a repeating alarm and will fire again in + // 'periodInMinutes' minutes. + double? periodInMinutes; }; // TODO(mpcomplete): rename to CreateInfo when http://crbug.com/123073 is // fixed. dictionary AlarmCreateInfo { + // Time at which the alarm should fire, in milliseconds past the epoch + // (e.g. Date.now() + n). + double? when; + // Length of time in minutes after which the onAlarm event should fire. - // Note that granularity is not guaranteed: this value is more of a hint to - // the browser. For performance reasons, alarms may be delayed an arbitrary - // amount of time before firing. + // // TODO: need minimum=0 - double delayInMinutes; + double? delayInMinutes; - // True if the alarm should repeatedly fire at regular intervals. Defaults - // to false. - boolean? repeating; + // If set, the onAlarm event should fire every |periodInMinutes| minutes + // after the initial event specified by |when| or |delayInMinutes|. If not + // set, the alarm will only fire once. + // + // TODO: need minimum=0 + double? periodInMinutes; }; callback AlarmCallback = void (Alarm alarm); callback AlarmListCallback = void (Alarm[] alarms); interface Functions { - // Creates an alarm. After the delay is elapsed, the onAlarm event is - // fired. If there is another alarm with the same name (or no name if none - // is specified), it will be cancelled and replaced by this alarm. + // Creates an alarm. Near the time(s) specified by 'alarmInfo', the onAlarm + // event is fired. If there is another alarm with the same name (or no name + // if none is specified), it will be cancelled and replaced by this alarm. + // + // Note that granularity is not guaranteed: times are more of a hint to the + // browser. For performance reasons, alarms may be delayed an arbitrary + // amount of time before firing. + // // |name|: Optional name to identify this alarm. Defaults to the empty // string. + // + // |alarmInfo|: Describes when the alarm should fire. The initial time must + // be specified by either |when| or |delayInMinutes| (but not both). If + // |periodInMinutes| is set, the alarm will repeat every |periodInMinutes| + // minutes after the initial event. If neither |when| or |delayInMinutes| + // is set for a repeating alarm, |periodInMinutes| is used as the default + // for |delayInMinutes|. static void create(optional DOMString name, AlarmCreateInfo alarmInfo); // Retrieves details about the specified alarm. |