summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/value_conversions.cc6
-rw-r--r--base/value_conversions.h6
-rw-r--r--chrome/browser/extensions/api/alarms/alarm_manager.cc233
-rw-r--r--chrome/browser/extensions/api/alarms/alarm_manager.h46
-rw-r--r--chrome/browser/extensions/api/alarms/alarms_api.cc91
-rw-r--r--chrome/browser/extensions/api/alarms/alarms_api.h6
-rw-r--r--chrome/browser/extensions/api/alarms/alarms_api_unittest.cc258
-rw-r--r--chrome/browser/extensions/extension_system.cc3
-rw-r--r--chrome/browser/extensions/test_extension_system.cc5
-rw-r--r--chrome/browser/extensions/test_extension_system.h5
-rw-r--r--chrome/common/extensions/api/alarms.idl51
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(&current_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.