summaryrefslogtreecommitdiff
path: root/chromium/extensions/browser/api/alarms
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/extensions/browser/api/alarms')
-rw-r--r--chromium/extensions/browser/api/alarms/OWNERS1
-rw-r--r--chromium/extensions/browser/api/alarms/alarm_manager.cc473
-rw-r--r--chromium/extensions/browser/api/alarms/alarm_manager.h246
-rw-r--r--chromium/extensions/browser/api/alarms/alarms_api.cc204
-rw-r--r--chromium/extensions/browser/api/alarms/alarms_api.h93
-rw-r--r--chromium/extensions/browser/api/alarms/alarms_api_unittest.cc700
6 files changed, 1717 insertions, 0 deletions
diff --git a/chromium/extensions/browser/api/alarms/OWNERS b/chromium/extensions/browser/api/alarms/OWNERS
new file mode 100644
index 00000000000..39cb68cb187
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/OWNERS
@@ -0,0 +1 @@
+mpcomplete@chromium.org
diff --git a/chromium/extensions/browser/api/alarms/alarm_manager.cc b/chromium/extensions/browser/api/alarms/alarm_manager.cc
new file mode 100644
index 00000000000..0616481fc75
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarm_manager.cc
@@ -0,0 +1,473 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/alarms/alarm_manager.h"
+
+#include <stddef.h>
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/json/json_writer.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/time/time.h"
+#include "base/value_conversions.h"
+#include "base/values.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/state_store.h"
+#include "extensions/common/api/alarms.h"
+
+namespace extensions {
+
+namespace alarms = api::alarms;
+
+namespace {
+
+// A list of alarms that this extension has set.
+const char kRegisteredAlarms[] = "alarms";
+const char kAlarmGranularity[] = "granularity";
+
+// The minimum period between polling for alarms to run.
+const base::TimeDelta kDefaultMinPollPeriod() {
+ return base::TimeDelta::FromDays(1);
+}
+
+class DefaultAlarmDelegate : public AlarmManager::Delegate {
+ public:
+ explicit DefaultAlarmDelegate(content::BrowserContext* context)
+ : browser_context_(context) {}
+ ~DefaultAlarmDelegate() override {}
+
+ void OnAlarm(const std::string& extension_id, const Alarm& alarm) override {
+ scoped_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(alarm.js_alarm->ToValue().release());
+ scoped_ptr<Event> event(new Event(
+ events::ALARMS_ON_ALARM, alarms::OnAlarm::kEventName, std::move(args)));
+ EventRouter::Get(browser_context_)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+ }
+
+ private:
+ content::BrowserContext* browser_context_;
+};
+
+// Creates a TimeDelta from a delay as specified in the API.
+base::TimeDelta TimeDeltaFromDelay(double delay_in_minutes) {
+ return base::TimeDelta::FromMicroseconds(delay_in_minutes *
+ base::Time::kMicrosecondsPerMinute);
+}
+
+std::vector<Alarm> AlarmsFromValue(const base::ListValue* list) {
+ std::vector<Alarm> alarms;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ const base::DictionaryValue* alarm_dict = NULL;
+ Alarm alarm;
+ if (list->GetDictionary(i, &alarm_dict) &&
+ alarms::Alarm::Populate(*alarm_dict, alarm.js_alarm.get())) {
+ const base::Value* time_value = NULL;
+ if (alarm_dict->Get(kAlarmGranularity, &time_value))
+ base::GetValueAsTimeDelta(*time_value, &alarm.granularity);
+ alarms.push_back(alarm);
+ }
+ }
+ return alarms;
+}
+
+scoped_ptr<base::ListValue> AlarmsToValue(const std::vector<Alarm>& alarms) {
+ scoped_ptr<base::ListValue> list(new base::ListValue());
+ for (size_t i = 0; i < alarms.size(); ++i) {
+ scoped_ptr<base::DictionaryValue> alarm = alarms[i].js_alarm->ToValue();
+ alarm->Set(kAlarmGranularity,
+ base::CreateTimeDeltaValue(alarms[i].granularity));
+ list->Append(alarm.release());
+ }
+ return list;
+}
+
+} // namespace
+
+// AlarmManager
+
+AlarmManager::AlarmManager(content::BrowserContext* context)
+ : browser_context_(context),
+ clock_(new base::DefaultClock()),
+ delegate_(new DefaultAlarmDelegate(context)),
+ extension_registry_observer_(this) {
+ extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
+
+ StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
+ if (storage)
+ storage->RegisterKey(kRegisteredAlarms);
+}
+
+AlarmManager::~AlarmManager() {
+}
+
+void AlarmManager::AddAlarm(const std::string& extension_id,
+ const Alarm& alarm,
+ const AddAlarmCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::AddAlarmWhenReady,
+ AsWeakPtr(), alarm, callback));
+}
+
+void AlarmManager::GetAlarm(const std::string& extension_id,
+ const std::string& name,
+ const GetAlarmCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::GetAlarmWhenReady,
+ AsWeakPtr(), name, callback));
+}
+
+void AlarmManager::GetAllAlarms(const std::string& extension_id,
+ const GetAllAlarmsCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::GetAllAlarmsWhenReady,
+ AsWeakPtr(), callback));
+}
+
+void AlarmManager::RemoveAlarm(const std::string& extension_id,
+ const std::string& name,
+ const RemoveAlarmCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::RemoveAlarmWhenReady,
+ AsWeakPtr(), name, callback));
+}
+
+void AlarmManager::RemoveAllAlarms(const std::string& extension_id,
+ const RemoveAllAlarmsCallback& callback) {
+ RunWhenReady(extension_id, base::Bind(&AlarmManager::RemoveAllAlarmsWhenReady,
+ AsWeakPtr(), callback));
+}
+
+void AlarmManager::AddAlarmWhenReady(const Alarm& alarm,
+ const AddAlarmCallback& callback,
+ const std::string& extension_id) {
+ AddAlarmImpl(extension_id, alarm);
+ WriteToStorage(extension_id);
+ callback.Run();
+}
+
+void AlarmManager::GetAlarmWhenReady(const std::string& name,
+ const GetAlarmCallback& callback,
+ const std::string& extension_id) {
+ AlarmIterator it = GetAlarmIterator(extension_id, name);
+ callback.Run(it.first != alarms_.end() ? &*it.second : NULL);
+}
+
+void AlarmManager::GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback,
+ const std::string& extension_id) {
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ callback.Run(list != alarms_.end() ? &list->second : NULL);
+}
+
+void AlarmManager::RemoveAlarmWhenReady(const std::string& name,
+ const RemoveAlarmCallback& callback,
+ const std::string& extension_id) {
+ AlarmIterator it = GetAlarmIterator(extension_id, name);
+ if (it.first == alarms_.end()) {
+ callback.Run(false);
+ return;
+ }
+
+ RemoveAlarmIterator(it);
+ WriteToStorage(extension_id);
+ callback.Run(true);
+}
+
+void AlarmManager::RemoveAllAlarmsWhenReady(
+ const RemoveAllAlarmsCallback& callback,
+ const std::string& extension_id) {
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ if (list != alarms_.end()) {
+ // Note: I'm using indices rather than iterators here because
+ // RemoveAlarmIterator will delete the list when it becomes empty.
+ for (size_t i = 0, size = list->second.size(); i < size; ++i)
+ RemoveAlarmIterator(AlarmIterator(list, list->second.begin()));
+
+ CHECK(alarms_.find(extension_id) == alarms_.end());
+ WriteToStorage(extension_id);
+ }
+ callback.Run();
+}
+
+AlarmManager::AlarmIterator AlarmManager::GetAlarmIterator(
+ const std::string& extension_id,
+ const std::string& name) {
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ if (list == alarms_.end())
+ return make_pair(alarms_.end(), AlarmList::iterator());
+
+ for (AlarmList::iterator it = list->second.begin(); it != list->second.end();
+ ++it) {
+ if (it->js_alarm->name == name)
+ return make_pair(list, it);
+ }
+
+ return make_pair(alarms_.end(), AlarmList::iterator());
+}
+
+void AlarmManager::SetClockForTesting(base::Clock* clock) {
+ clock_.reset(clock);
+}
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<AlarmManager>>
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+// static
+BrowserContextKeyedAPIFactory<AlarmManager>*
+AlarmManager::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+AlarmManager* AlarmManager::Get(content::BrowserContext* browser_context) {
+ return BrowserContextKeyedAPIFactory<AlarmManager>::Get(browser_context);
+}
+
+void AlarmManager::RemoveAlarmIterator(const AlarmIterator& iter) {
+ AlarmList& list = iter.first->second;
+ list.erase(iter.second);
+ if (list.empty())
+ alarms_.erase(iter.first);
+
+ // Cancel the timer if there are no more alarms.
+ // We don't need to reschedule the poll otherwise, because in
+ // the worst case we would just poll one extra time.
+ if (alarms_.empty()) {
+ timer_.Stop();
+ next_poll_time_ = base::Time();
+ }
+}
+
+void AlarmManager::OnAlarm(AlarmIterator it) {
+ CHECK(it.first != alarms_.end());
+ Alarm& alarm = *it.second;
+ std::string extension_id_copy(it.first->first);
+ delegate_->OnAlarm(extension_id_copy, alarm);
+
+ // Update our scheduled time for the next alarm.
+ if (double* period_in_minutes = alarm.js_alarm->period_in_minutes.get()) {
+ // Get the timer's delay in JS time (i.e., convert it from minutes to
+ // milliseconds).
+ double period_in_js_time = *period_in_minutes *
+ base::Time::kMicrosecondsPerMinute /
+ base::Time::kMicrosecondsPerMillisecond;
+ // Find out how many periods have transpired since the alarm last went off
+ // (it's possible that we missed some).
+ int transpired_periods =
+ (last_poll_time_.ToJsTime() - alarm.js_alarm->scheduled_time) /
+ period_in_js_time;
+ // Schedule the alarm for the next period that is in-line with the original
+ // scheduling.
+ alarm.js_alarm->scheduled_time +=
+ period_in_js_time * (transpired_periods + 1);
+ } else {
+ RemoveAlarmIterator(it);
+ }
+ WriteToStorage(extension_id_copy);
+}
+
+void AlarmManager::AddAlarmImpl(const std::string& extension_id,
+ const Alarm& alarm) {
+ // Override any old alarm with the same name.
+ AlarmIterator old_alarm =
+ GetAlarmIterator(extension_id, alarm.js_alarm->name);
+ if (old_alarm.first != alarms_.end())
+ RemoveAlarmIterator(old_alarm);
+
+ alarms_[extension_id].push_back(alarm);
+ base::Time alarm_time =
+ base::Time::FromJsTime(alarm.js_alarm->scheduled_time);
+ if (next_poll_time_.is_null() || alarm_time < next_poll_time_)
+ SetNextPollTime(alarm_time);
+}
+
+void AlarmManager::WriteToStorage(const std::string& extension_id) {
+ StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
+ if (!storage)
+ return;
+
+ scoped_ptr<base::Value> alarms;
+ AlarmMap::iterator list = alarms_.find(extension_id);
+ if (list != alarms_.end())
+ alarms.reset(AlarmsToValue(list->second).release());
+ else
+ alarms.reset(AlarmsToValue(std::vector<Alarm>()).release());
+ storage->SetExtensionValue(extension_id, kRegisteredAlarms,
+ std::move(alarms));
+}
+
+void AlarmManager::ReadFromStorage(const std::string& extension_id,
+ scoped_ptr<base::Value> value) {
+ base::ListValue* list = NULL;
+ if (value.get() && value->GetAsList(&list)) {
+ std::vector<Alarm> alarm_states = AlarmsFromValue(list);
+ for (size_t i = 0; i < alarm_states.size(); ++i)
+ AddAlarmImpl(extension_id, alarm_states[i]);
+ }
+
+ ReadyQueue& extension_ready_queue = ready_actions_[extension_id];
+ while (!extension_ready_queue.empty()) {
+ extension_ready_queue.front().Run(extension_id);
+ extension_ready_queue.pop();
+ }
+ ready_actions_.erase(extension_id);
+}
+
+void AlarmManager::SetNextPollTime(const base::Time& time) {
+ next_poll_time_ = time;
+ timer_.Start(FROM_HERE,
+ std::max(base::TimeDelta::FromSeconds(0), time - clock_->Now()),
+ this, &AlarmManager::PollAlarms);
+}
+
+void AlarmManager::ScheduleNextPoll() {
+ // If there are no alarms, stop the timer.
+ if (alarms_.empty()) {
+ timer_.Stop();
+ next_poll_time_ = base::Time();
+ return;
+ }
+
+ // Find the soonest alarm that is scheduled to run and the smallest
+ // granularity of any alarm.
+ // alarms_ guarantees that none of its contained lists are empty.
+ base::Time soonest_alarm_time = base::Time::FromJsTime(
+ alarms_.begin()->second.begin()->js_alarm->scheduled_time);
+ base::TimeDelta min_granularity = kDefaultMinPollPeriod();
+ for (AlarmMap::const_iterator m_it = alarms_.begin(), m_end = alarms_.end();
+ m_it != m_end; ++m_it) {
+ for (AlarmList::const_iterator l_it = m_it->second.begin();
+ l_it != m_it->second.end(); ++l_it) {
+ base::Time cur_alarm_time =
+ base::Time::FromJsTime(l_it->js_alarm->scheduled_time);
+ if (cur_alarm_time < soonest_alarm_time)
+ soonest_alarm_time = cur_alarm_time;
+ if (l_it->granularity < min_granularity)
+ min_granularity = l_it->granularity;
+ base::TimeDelta cur_alarm_delta = cur_alarm_time - last_poll_time_;
+ if (cur_alarm_delta < l_it->minimum_granularity)
+ cur_alarm_delta = l_it->minimum_granularity;
+ if (cur_alarm_delta < min_granularity)
+ min_granularity = cur_alarm_delta;
+ }
+ }
+
+ base::Time next_poll(last_poll_time_ + min_granularity);
+ // If the next alarm is more than min_granularity in the future, wait for it.
+ // Otherwise, only poll as often as min_granularity.
+ // As a special case, if we've never checked for an alarm before
+ // (e.g. during startup), let alarms fire asap.
+ if (last_poll_time_.is_null() || next_poll < soonest_alarm_time)
+ next_poll = soonest_alarm_time;
+
+ // Schedule the poll.
+ SetNextPollTime(next_poll);
+}
+
+void AlarmManager::PollAlarms() {
+ last_poll_time_ = clock_->Now();
+
+ // Run any alarms scheduled in the past. OnAlarm uses vector::erase to remove
+ // elements from the AlarmList, and map::erase to remove AlarmLists from the
+ // AlarmMap.
+ for (AlarmMap::iterator m_it = alarms_.begin(), m_end = alarms_.end();
+ m_it != m_end;) {
+ AlarmMap::iterator cur_extension = m_it++;
+
+ // Iterate (a) backwards so that removing elements doesn't affect
+ // upcoming iterations, and (b) with indices so that if the last
+ // iteration destroys the AlarmList, I'm not about to use the end
+ // iterator that the destruction invalidates.
+ for (size_t i = cur_extension->second.size(); i > 0; --i) {
+ AlarmList::iterator cur_alarm = cur_extension->second.begin() + i - 1;
+ if (base::Time::FromJsTime(cur_alarm->js_alarm->scheduled_time) <=
+ last_poll_time_) {
+ OnAlarm(make_pair(cur_extension, cur_alarm));
+ }
+ }
+ }
+
+ ScheduleNextPoll();
+}
+
+static void RemoveAllOnUninstallCallback() {
+}
+
+void AlarmManager::RunWhenReady(const std::string& extension_id,
+ const ReadyAction& action) {
+ ReadyMap::iterator it = ready_actions_.find(extension_id);
+
+ if (it == ready_actions_.end())
+ action.Run(extension_id);
+ else
+ it->second.push(action);
+}
+
+void AlarmManager::OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) {
+ StateStore* storage = ExtensionSystem::Get(browser_context_)->state_store();
+ if (storage) {
+ ready_actions_.insert(ReadyMap::value_type(extension->id(), ReadyQueue()));
+ storage->GetExtensionValue(extension->id(), kRegisteredAlarms,
+ base::Bind(&AlarmManager::ReadFromStorage,
+ AsWeakPtr(), extension->id()));
+ }
+}
+
+void AlarmManager::OnExtensionUninstalled(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) {
+ RemoveAllAlarms(extension->id(), base::Bind(RemoveAllOnUninstallCallback));
+}
+
+// AlarmManager::Alarm
+
+Alarm::Alarm() : js_alarm(new alarms::Alarm()) {
+}
+
+Alarm::Alarm(const std::string& name,
+ const alarms::AlarmCreateInfo& create_info,
+ base::TimeDelta min_granularity,
+ base::Time now)
+ : js_alarm(new alarms::Alarm()) {
+ js_alarm->name = name;
+ minimum_granularity = min_granularity;
+
+ if (create_info.when.get()) {
+ // Absolute scheduling.
+ js_alarm->scheduled_time = *create_info.when;
+ granularity = base::Time::FromJsTime(js_alarm->scheduled_time) - now;
+ } else {
+ // Relative scheduling.
+ double* delay_in_minutes = create_info.delay_in_minutes.get();
+ if (delay_in_minutes == NULL)
+ delay_in_minutes = create_info.period_in_minutes.get();
+ CHECK(delay_in_minutes != NULL)
+ << "ValidateAlarmCreateInfo in alarms_api.cc should have "
+ << "prevented this call.";
+ base::TimeDelta delay = TimeDeltaFromDelay(*delay_in_minutes);
+ js_alarm->scheduled_time = (now + delay).ToJsTime();
+ granularity = delay;
+ }
+
+ if (granularity < min_granularity)
+ granularity = min_granularity;
+
+ // Check for repetition.
+ if (create_info.period_in_minutes.get()) {
+ js_alarm->period_in_minutes.reset(
+ new double(*create_info.period_in_minutes));
+ }
+}
+
+Alarm::Alarm(const Alarm& other) = default;
+
+Alarm::~Alarm() {
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/alarms/alarm_manager.h b/chromium/extensions/browser/api/alarms/alarm_manager.h
new file mode 100644
index 00000000000..66e5fb105d5
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarm_manager.h
@@ -0,0 +1,246 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_ALARMS_ALARM_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_ALARMS_ALARM_MANAGER_H_
+
+#include <map>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observer.h"
+#include "base/timer/timer.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/alarms.h"
+
+namespace base {
+class Clock;
+} // namespace base
+
+namespace content {
+class BrowserContext;
+} // namespace content
+
+namespace extensions {
+class ExtensionAlarmsSchedulingTest;
+class ExtensionRegistry;
+
+struct Alarm {
+ Alarm();
+ Alarm(const std::string& name,
+ const api::alarms::AlarmCreateInfo& create_info,
+ base::TimeDelta min_granularity,
+ base::Time now);
+ Alarm(const Alarm& other);
+ ~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;
+ // The minimum granularity is the minimum allowed polling rate. This stops
+ // alarms from polling too often.
+ base::TimeDelta minimum_granularity;
+};
+
+// Manages the currently pending alarms for every extension in a profile.
+// There is one manager per virtual Profile.
+class AlarmManager : public BrowserContextKeyedAPI,
+ public ExtensionRegistryObserver,
+ public base::SupportsWeakPtr<AlarmManager> {
+ public:
+ typedef std::vector<Alarm> AlarmList;
+
+ class Delegate {
+ public:
+ virtual ~Delegate() {}
+ // Called when an alarm fires.
+ virtual void OnAlarm(const std::string& extension_id,
+ const Alarm& alarm) = 0;
+ };
+
+ explicit AlarmManager(content::BrowserContext* context);
+ ~AlarmManager() override;
+
+ // Override the default delegate. Callee assumes onwership. Used for testing.
+ void set_delegate(Delegate* delegate) { delegate_.reset(delegate); }
+
+ typedef base::Callback<void()> AddAlarmCallback;
+ // Adds |alarm| for the given extension, and starts the timer. Invokes
+ // |callback| when done.
+ void AddAlarm(const std::string& extension_id,
+ const Alarm& alarm,
+ const AddAlarmCallback& callback);
+
+ typedef base::Callback<void(Alarm*)> GetAlarmCallback;
+ // Passes the alarm with the given name, or NULL if none exists, to
+ // |callback|.
+ void GetAlarm(const std::string& extension_id,
+ const std::string& name,
+ const GetAlarmCallback& callback);
+
+ typedef base::Callback<void(const AlarmList*)> GetAllAlarmsCallback;
+ // Passes the list of pending alarms for the given extension, or
+ // NULL if none exist, to |callback|.
+ void GetAllAlarms(const std::string& extension_id,
+ const GetAllAlarmsCallback& callback);
+
+ typedef base::Callback<void(bool)> RemoveAlarmCallback;
+ // Cancels and removes the alarm with the given name. Invokes |callback| when
+ // done.
+ void RemoveAlarm(const std::string& extension_id,
+ const std::string& name,
+ const RemoveAlarmCallback& callback);
+
+ typedef base::Callback<void()> RemoveAllAlarmsCallback;
+ // Cancels and removes all alarms for the given extension. Invokes |callback|
+ // when done.
+ void RemoveAllAlarms(const std::string& extension_id,
+ const RemoveAllAlarmsCallback& callback);
+
+ // Replaces AlarmManager's owned clock with |clock| and takes ownership of it.
+ void SetClockForTesting(base::Clock* clock);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<AlarmManager>* GetFactoryInstance();
+
+ // Convenience method to get the AlarmManager for a content::BrowserContext.
+ static AlarmManager* Get(content::BrowserContext* browser_context);
+
+ private:
+ friend void RunScheduleNextPoll(AlarmManager*);
+ friend class ExtensionAlarmsSchedulingTest;
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, PollScheduling);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest,
+ ReleasedExtensionPollsInfrequently);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, TimerRunning);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest, MinimumGranularity);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest,
+ DifferentMinimumGranularities);
+ FRIEND_TEST_ALL_PREFIXES(ExtensionAlarmsSchedulingTest,
+ RepeatingAlarmsScheduledPredictably);
+ friend class BrowserContextKeyedAPIFactory<AlarmManager>;
+
+ typedef std::string ExtensionId;
+ typedef std::map<ExtensionId, AlarmList> AlarmMap;
+
+ typedef base::Callback<void(const std::string&)> ReadyAction;
+ typedef std::queue<ReadyAction> ReadyQueue;
+ typedef std::map<ExtensionId, ReadyQueue> ReadyMap;
+
+ // Iterator used to identify a particular alarm within the Map/List pair.
+ // "Not found" is represented by <alarms_.end(), invalid_iterator>.
+ typedef std::pair<AlarmMap::iterator, AlarmList::iterator> AlarmIterator;
+
+ // Part of AddAlarm that is executed after alarms are loaded.
+ void AddAlarmWhenReady(const Alarm& alarm,
+ const AddAlarmCallback& callback,
+ const std::string& extension_id);
+
+ // Part of GetAlarm that is executed after alarms are loaded.
+ void GetAlarmWhenReady(const std::string& name,
+ const GetAlarmCallback& callback,
+ const std::string& extension_id);
+
+ // Part of GetAllAlarms that is executed after alarms are loaded.
+ void GetAllAlarmsWhenReady(const GetAllAlarmsCallback& callback,
+ const std::string& extension_id);
+
+ // Part of RemoveAlarm that is executed after alarms are loaded.
+ void RemoveAlarmWhenReady(const std::string& name,
+ const RemoveAlarmCallback& callback,
+ const std::string& extension_id);
+
+ // Part of RemoveAllAlarms that is executed after alarms are loaded.
+ void RemoveAllAlarmsWhenReady(const RemoveAllAlarmsCallback& callback,
+ const std::string& extension_id);
+
+ // 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.
+ AlarmIterator GetAlarmIterator(const std::string& extension_id,
+ const std::string& name);
+
+ // Helper to cancel and remove the alarm at the given iterator. The iterator
+ // must be valid.
+ void RemoveAlarmIterator(const AlarmIterator& iter);
+
+ // Callback for when an alarm fires.
+ 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 Alarm& alarm);
+
+ // Syncs our alarm data for the given extension to/from the state storage.
+ void WriteToStorage(const std::string& extension_id);
+ void ReadFromStorage(const std::string& extension_id,
+ scoped_ptr<base::Value> value);
+
+ // Set the timer to go off at the specified |time|, and set |next_poll_time|
+ // appropriately.
+ void SetNextPollTime(const base::Time& time);
+
+ // Schedules the next poll of alarms for when the next soonest alarm runs,
+ // but not more often than the minimum granularity of all alarms.
+ void ScheduleNextPoll();
+
+ // Polls the alarms, running any that have elapsed. After running them and
+ // rescheduling repeating alarms, schedule the next poll.
+ void PollAlarms();
+
+ // Executes |action| for given extension, making sure that the extension's
+ // alarm data has been synced from the storage.
+ void RunWhenReady(const std::string& extension_id, const ReadyAction& action);
+
+ // ExtensionRegistryObserver implementation.
+ void OnExtensionLoaded(content::BrowserContext* browser_context,
+ const Extension* extension) override;
+ void OnExtensionUninstalled(content::BrowserContext* browser_context,
+ const Extension* extension,
+ extensions::UninstallReason reason) override;
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "AlarmManager"; }
+ static const bool kServiceHasOwnInstanceInIncognito = true;
+
+ content::BrowserContext* const browser_context_;
+ scoped_ptr<base::Clock> clock_;
+ scoped_ptr<Delegate> delegate_;
+
+ // Listen to extension load notifications.
+ ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
+ extension_registry_observer_;
+
+ // The timer for this alarm manager.
+ base::OneShotTimer timer_;
+
+ // A map of our pending alarms, per extension.
+ // Invariant: None of the AlarmLists are empty.
+ AlarmMap alarms_;
+
+ // A map of actions waiting for alarm data to be synced from storage, per
+ // extension.
+ ReadyMap ready_actions_;
+
+ // The previous time that alarms were run.
+ base::Time last_poll_time_;
+
+ // Next poll's time.
+ base::Time next_poll_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(AlarmManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_ALARMS_ALARM_MANAGER_H_
diff --git a/chromium/extensions/browser/api/alarms/alarms_api.cc b/chromium/extensions/browser/api/alarms/alarms_api.cc
new file mode 100644
index 00000000000..102bce4f863
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarms_api.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/api/alarms/alarms_api.h"
+
+#include <stddef.h>
+
+#include "base/strings/string_number_conversions.h"
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
+#include "base/values.h"
+#include "extensions/browser/api/alarms/alarm_manager.h"
+#include "extensions/common/api/alarms.h"
+#include "extensions/common/error_utils.h"
+
+namespace extensions {
+
+namespace alarms = api::alarms;
+
+namespace {
+
+const char kDefaultAlarmName[] = "";
+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 = 1;
+const int kDevDelayMinimum = 0;
+
+bool ValidateAlarmCreateInfo(const std::string& alarm_name,
+ const alarms::AlarmCreateInfo& create_info,
+ const Extension* extension,
+ std::string* error,
+ std::vector<std::string>* warnings) {
+ 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 (*create_info.delay_in_minutes < kReleaseDelayMinimum) {
+ static_assert(kReleaseDelayMinimum == 1,
+ "warning message must be updated");
+ if (Manifest::IsUnpackedLocation(extension->location()))
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm delay is less than minimum of 1 minutes."
+ " In released .crx, alarm \"*\" will fire in approximately"
+ " 1 minutes.",
+ alarm_name));
+ else
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm delay is less than minimum of 1 minutes."
+ " Alarm \"*\" will fire in approximately 1 minutes.",
+ alarm_name));
+ }
+ }
+ if (create_info.period_in_minutes.get()) {
+ if (*create_info.period_in_minutes < kReleaseDelayMinimum) {
+ static_assert(kReleaseDelayMinimum == 1,
+ "warning message must be updated");
+ if (Manifest::IsUnpackedLocation(extension->location()))
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm period is less than minimum of 1 minutes."
+ " In released .crx, alarm \"*\" will fire approximately"
+ " every 1 minutes.",
+ alarm_name));
+ else
+ warnings->push_back(ErrorUtils::FormatErrorMessage(
+ "Alarm period is less than minimum of 1 minutes."
+ " Alarm \"*\" will fire approximately every 1 minutes.",
+ alarm_name));
+ }
+ }
+
+ return true;
+}
+
+} // namespace
+
+AlarmsCreateFunction::AlarmsCreateFunction()
+ : clock_(new base::DefaultClock()), owns_clock_(true) {
+}
+
+AlarmsCreateFunction::AlarmsCreateFunction(base::Clock* clock)
+ : clock_(clock), owns_clock_(false) {
+}
+
+AlarmsCreateFunction::~AlarmsCreateFunction() {
+ if (owns_clock_)
+ delete clock_;
+}
+
+bool AlarmsCreateFunction::RunAsync() {
+ scoped_ptr<alarms::Create::Params> params(
+ alarms::Create::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ const std::string& alarm_name =
+ params->name.get() ? *params->name : kDefaultAlarmName;
+ std::vector<std::string> warnings;
+ if (!ValidateAlarmCreateInfo(alarm_name, params->alarm_info, extension(),
+ &error_, &warnings)) {
+ return false;
+ }
+ for (std::vector<std::string>::const_iterator it = warnings.begin();
+ it != warnings.end(); ++it)
+ WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, *it);
+
+ Alarm alarm(alarm_name, params->alarm_info,
+ base::TimeDelta::FromMinutes(
+ Manifest::IsUnpackedLocation(extension()->location())
+ ? kDevDelayMinimum
+ : kReleaseDelayMinimum),
+ clock_->Now());
+ AlarmManager::Get(browser_context())
+ ->AddAlarm(extension_id(), alarm,
+ base::Bind(&AlarmsCreateFunction::Callback, this));
+
+ return true;
+}
+
+void AlarmsCreateFunction::Callback() {
+ SendResponse(true);
+}
+
+bool AlarmsGetFunction::RunAsync() {
+ scoped_ptr<alarms::Get::Params> params(alarms::Get::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ std::string name = params->name.get() ? *params->name : kDefaultAlarmName;
+ AlarmManager::Get(browser_context())
+ ->GetAlarm(extension_id(), name,
+ base::Bind(&AlarmsGetFunction::Callback, this, name));
+
+ return true;
+}
+
+void AlarmsGetFunction::Callback(const std::string& name,
+ extensions::Alarm* alarm) {
+ if (alarm) {
+ results_ = alarms::Get::Results::Create(*alarm->js_alarm);
+ }
+ SendResponse(true);
+}
+
+bool AlarmsGetAllFunction::RunAsync() {
+ AlarmManager::Get(browser_context())
+ ->GetAllAlarms(extension_id(),
+ base::Bind(&AlarmsGetAllFunction::Callback, this));
+ return true;
+}
+
+void AlarmsGetAllFunction::Callback(const AlarmList* alarms) {
+ scoped_ptr<base::ListValue> alarms_value(new base::ListValue());
+ if (alarms) {
+ for (const Alarm& alarm : *alarms)
+ alarms_value->Append(alarm.js_alarm->ToValue());
+ }
+ SetResult(std::move(alarms_value));
+ SendResponse(true);
+}
+
+bool AlarmsClearFunction::RunAsync() {
+ scoped_ptr<alarms::Clear::Params> params(
+ alarms::Clear::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ std::string name = params->name.get() ? *params->name : kDefaultAlarmName;
+ AlarmManager::Get(browser_context())
+ ->RemoveAlarm(extension_id(), name,
+ base::Bind(&AlarmsClearFunction::Callback, this, name));
+
+ return true;
+}
+
+void AlarmsClearFunction::Callback(const std::string& name, bool success) {
+ SetResult(new base::FundamentalValue(success));
+ SendResponse(true);
+}
+
+bool AlarmsClearAllFunction::RunAsync() {
+ AlarmManager::Get(browser_context())
+ ->RemoveAllAlarms(extension_id(),
+ base::Bind(&AlarmsClearAllFunction::Callback, this));
+ return true;
+}
+
+void AlarmsClearAllFunction::Callback() {
+ SetResult(new base::FundamentalValue(true));
+ SendResponse(true);
+}
+
+} // namespace extensions
diff --git a/chromium/extensions/browser/api/alarms/alarms_api.h b/chromium/extensions/browser/api/alarms/alarms_api.h
new file mode 100644
index 00000000000..837da909620
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarms_api.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef EXTENSIONS_BROWSER_API_ALARMS_ALARMS_API_H_
+#define EXTENSIONS_BROWSER_API_ALARMS_ALARMS_API_H_
+
+#include <vector>
+
+#include "extensions/browser/extension_function.h"
+
+namespace base {
+class Clock;
+} // namespace base
+
+namespace extensions {
+struct Alarm;
+typedef std::vector<Alarm> AlarmList;
+
+class AlarmsCreateFunction : public AsyncExtensionFunction {
+ public:
+ AlarmsCreateFunction();
+ // Use |clock| instead of the default clock. Does not take ownership
+ // of |clock|. Used for testing.
+ explicit AlarmsCreateFunction(base::Clock* clock);
+
+ protected:
+ ~AlarmsCreateFunction() override;
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+ DECLARE_EXTENSION_FUNCTION("alarms.create", ALARMS_CREATE)
+ private:
+ void Callback();
+
+ base::Clock* const clock_;
+ // Whether or not we own |clock_|. This is needed because we own it
+ // when we create it ourselves, but not when it's passed in for
+ // testing.
+ bool owns_clock_;
+};
+
+class AlarmsGetFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsGetFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback(const std::string& name, Alarm* alarm);
+ DECLARE_EXTENSION_FUNCTION("alarms.get", ALARMS_GET)
+};
+
+class AlarmsGetAllFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsGetAllFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback(const AlarmList* alarms);
+ DECLARE_EXTENSION_FUNCTION("alarms.getAll", ALARMS_GETALL)
+};
+
+class AlarmsClearFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsClearFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback(const std::string& name, bool success);
+ DECLARE_EXTENSION_FUNCTION("alarms.clear", ALARMS_CLEAR)
+};
+
+class AlarmsClearAllFunction : public AsyncExtensionFunction {
+ protected:
+ ~AlarmsClearAllFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ private:
+ void Callback();
+ DECLARE_EXTENSION_FUNCTION("alarms.clearAll", ALARMS_CLEARALL)
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_ALARMS_ALARMS_API_H_
diff --git a/chromium/extensions/browser/api/alarms/alarms_api_unittest.cc b/chromium/extensions/browser/api/alarms/alarms_api_unittest.cc
new file mode 100644
index 00000000000..b0626f2641c
--- /dev/null
+++ b/chromium/extensions/browser/api/alarms/alarms_api_unittest.cc
@@ -0,0 +1,700 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file tests the chrome.alarms extension API.
+
+#include <stddef.h>
+
+#include "base/test/simple_test_clock.h"
+#include "base/values.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/mock_render_process_host.h"
+#include "extensions/browser/api/alarms/alarm_manager.h"
+#include "extensions/browser/api/alarms/alarms_api.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/common/extension_messages.h"
+#include "ipc/ipc_test_sink.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+typedef extensions::api::alarms::Alarm JsAlarm;
+
+namespace extensions {
+
+namespace utils = api_test_utils;
+
+namespace {
+
+// Test delegate which quits the message loop when an alarm fires.
+class AlarmDelegate : public AlarmManager::Delegate {
+ public:
+ ~AlarmDelegate() override {}
+ void OnAlarm(const std::string& extension_id, const Alarm& alarm) override {
+ alarms_seen.push_back(alarm.js_alarm->name);
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->QuitWhenIdle();
+ }
+
+ std::vector<std::string> alarms_seen;
+};
+
+} // namespace
+
+void RunScheduleNextPoll(AlarmManager* alarm_manager) {
+ alarm_manager->ScheduleNextPoll();
+}
+
+class ExtensionAlarmsTest : public ApiUnitTest {
+ public:
+ using ApiUnitTest::RunFunction;
+
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+
+ test_clock_ = new base::SimpleTestClock();
+ alarm_manager_ = AlarmManager::Get(browser_context());
+ alarm_manager_->SetClockForTesting(test_clock_);
+
+ alarm_delegate_ = new AlarmDelegate();
+ alarm_manager_->set_delegate(alarm_delegate_);
+
+ // Make sure there's a RenderViewHost for alarms to warn into.
+ CreateBackgroundPage();
+
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+ }
+
+ void CreateAlarm(const std::string& args) {
+ RunFunction(new AlarmsCreateFunction(test_clock_), args);
+ }
+
+ // Takes a JSON result from a function and converts it to a vector of
+ // 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<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(JsAlarm::Populate(*alarm_value, alarm.get()));
+ list.push_back(alarm);
+ }
+ return list;
+ }
+
+ // Creates up to 3 alarms using the extension API.
+ void CreateAlarms(size_t num_alarms) {
+ CHECK_LE(num_alarms, 3U);
+
+ const char* const kCreateArgs[] = {
+ "[null, {\"periodInMinutes\": 0.001}]",
+ "[\"7\", {\"periodInMinutes\": 7}]",
+ "[\"0\", {\"delayInMinutes\": 0}]",
+ };
+ for (size_t i = 0; i < num_alarms; ++i) {
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
+ new AlarmsCreateFunction(test_clock_), kCreateArgs[i]));
+ EXPECT_FALSE(result.get());
+ }
+ }
+
+ base::SimpleTestClock* test_clock_;
+ AlarmManager* alarm_manager_;
+ AlarmDelegate* alarm_delegate_;
+};
+
+void ExtensionAlarmsTestGetAllAlarmsCallback(
+ const AlarmManager::AlarmList* alarms) {
+ // Ensure the alarm is gone.
+ ASSERT_FALSE(alarms);
+}
+
+void ExtensionAlarmsTestGetAlarmCallback(ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ 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.
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+
+ // Ensure the alarm is gone.
+ test->alarm_manager_->GetAllAlarms(
+ test->extension()->id(),
+ base::Bind(ExtensionAlarmsTestGetAllAlarmsCallback));
+}
+
+TEST_F(ExtensionAlarmsTest, Create) {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+ // Create 1 non-repeating alarm.
+ CreateAlarm("[null, {\"delayInMinutes\": 0}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestGetAlarmCallback, this));
+}
+
+void ExtensionAlarmsTestCreateRepeatingGetAlarmCallback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ 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)));
+
+ test->test_clock_->Advance(base::TimeDelta::FromSeconds(1));
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ test->test_clock_->Advance(base::TimeDelta::FromSeconds(1));
+ // Wait again, and ensure the alarm fires again.
+ RunScheduleNextPoll(test->alarm_manager_);
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(2u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+}
+
+TEST_F(ExtensionAlarmsTest, CreateRepeating) {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+
+ // Create 1 repeating alarm.
+ CreateAlarm("[null, {\"periodInMinutes\": 0.001}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestCreateRepeatingGetAlarmCallback, this));
+}
+
+void ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_FALSE(alarm);
+
+ ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+}
+
+void ExtensionAlarmsTestCreateAbsoluteGetAlarm1Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ 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());
+
+ test->test_clock_->SetNow(base::Time::FromDoubleT(10.1));
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ test->alarm_manager_->GetAlarm(
+ test->extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestCreateAbsoluteGetAlarm2Callback, test));
+}
+
+TEST_F(ExtensionAlarmsTest, CreateAbsolute) {
+ test_clock_->SetNow(base::Time::FromDoubleT(9.99));
+ CreateAlarm("[null, {\"when\": 10001}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(ExtensionAlarmsTestCreateAbsoluteGetAlarm1Callback, this));
+}
+
+void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm3Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_THAT(test->alarm_delegate_->alarms_seen, testing::ElementsAre("", ""));
+}
+
+void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ ASSERT_TRUE(alarm);
+ EXPECT_THAT(test->alarm_delegate_->alarms_seen, testing::ElementsAre(""));
+
+ test->test_clock_->SetNow(base::Time::FromDoubleT(10.7));
+ base::MessageLoop::current()->Run();
+
+ test->alarm_manager_->GetAlarm(
+ test->extension()->id(), std::string(),
+ base::Bind(
+ ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm3Callback,
+ test));
+}
+
+void ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm1Callback(
+ ExtensionAlarmsTest* test,
+ Alarm* alarm) {
+ 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)));
+
+ test->test_clock_->SetNow(base::Time::FromDoubleT(10.1));
+ // Now wait for the alarm to fire. Our test delegate will quit the
+ // MessageLoop when that happens.
+ base::MessageLoop::current()->Run();
+
+ test->alarm_manager_->GetAlarm(
+ test->extension()->id(), std::string(),
+ base::Bind(
+ ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm2Callback,
+ test));
+}
+
+TEST_F(ExtensionAlarmsTest, CreateRepeatingWithQuickFirstCall) {
+ test_clock_->SetNow(base::Time::FromDoubleT(9.99));
+ CreateAlarm("[null, {\"when\": 10001, \"periodInMinutes\": 0.001}]");
+
+ alarm_manager_->GetAlarm(
+ extension()->id(), std::string(),
+ base::Bind(
+ ExtensionAlarmsTestCreateRepeatingWithQuickFirstCallGetAlarm1Callback,
+ this));
+}
+
+void ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback(
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(1u, alarms->size());
+ EXPECT_DOUBLE_EQ(430000, (*alarms)[0].js_alarm->scheduled_time);
+}
+
+TEST_F(ExtensionAlarmsTest, CreateDupe) {
+ test_clock_->SetNow(base::Time::FromDoubleT(10));
+
+ // Create 2 duplicate alarms. The first should be overridden.
+ CreateAlarm("[\"dup\", {\"delayInMinutes\": 1}]");
+ CreateAlarm("[\"dup\", {\"delayInMinutes\": 7}]");
+
+ alarm_manager_->GetAllAlarms(
+ extension()->id(),
+ base::Bind(ExtensionAlarmsTestCreateDupeGetAllAlarmsCallback));
+}
+
+TEST_F(ExtensionAlarmsTest, CreateDelayBelowMinimum) {
+ // Create an alarm with delay below the minimum accepted value.
+ IPC::TestSink& sink =
+ static_cast<content::MockRenderProcessHost*>(
+ contents()->GetRenderViewHost()->GetProcess())->sink();
+ size_t initial_message_count = sink.message_count();
+ CreateAlarm("[\"negative\", {\"delayInMinutes\": -0.2}]");
+ // A new message should have been added.
+ ASSERT_GT(sink.message_count(), initial_message_count);
+
+ // All of this would be cleaner if we could read the message as a
+ // FrameMsg_AddMessageToConsole, but that would be a layering violation.
+ // Better yet would be an observer method for frames adding console messages,
+ // but it's not worth adding just for a test.
+ const IPC::Message* warning =
+ sink.GetMessageAt(initial_message_count /* 0-based */);
+ ASSERT_TRUE(warning);
+
+ int level = 0;
+ base::PickleIterator iter(*warning);
+ ASSERT_TRUE(iter.ReadInt(&level));
+ std::string message;
+ ASSERT_TRUE(iter.ReadString(&message));
+
+ EXPECT_EQ(content::CONSOLE_MESSAGE_LEVEL_WARNING,
+ static_cast<content::ConsoleMessageLevel>(level));
+ EXPECT_THAT(message, testing::HasSubstr("delay is less than minimum of 1"));
+}
+
+TEST_F(ExtensionAlarmsTest, Get) {
+ test_clock_->SetNow(base::Time::FromDoubleT(4));
+
+ // Create 2 alarms, and make sure we can query them.
+ CreateAlarms(2);
+
+ // Get the default one.
+ {
+ JsAlarm alarm;
+ scoped_ptr<base::DictionaryValue> result(
+ RunFunctionAndReturnDictionary(new AlarmsGetFunction(), "[null]"));
+ ASSERT_TRUE(result.get());
+ EXPECT_TRUE(JsAlarm::Populate(*result, &alarm));
+ EXPECT_EQ("", alarm.name);
+ EXPECT_DOUBLE_EQ(4060, alarm.scheduled_time);
+ EXPECT_THAT(alarm.period_in_minutes,
+ testing::Pointee(testing::DoubleEq(0.001)));
+ }
+
+ // Get "7".
+ {
+ JsAlarm alarm;
+ scoped_ptr<base::DictionaryValue> result(
+ RunFunctionAndReturnDictionary(new AlarmsGetFunction(), "[\"7\"]"));
+ ASSERT_TRUE(result.get());
+ EXPECT_TRUE(JsAlarm::Populate(*result, &alarm));
+ EXPECT_EQ("7", alarm.name);
+ EXPECT_EQ(424000, alarm.scheduled_time);
+ EXPECT_THAT(alarm.period_in_minutes, testing::Pointee(7));
+ }
+
+ // Get a non-existent one.
+ {
+ scoped_ptr<base::DictionaryValue> result(RunFunctionAndReturnDictionary(
+ new AlarmsGetFunction(), "[\"nobody\"]"));
+ ASSERT_FALSE(result.get());
+ }
+}
+
+TEST_F(ExtensionAlarmsTest, GetAll) {
+ // Test getAll with 0 alarms.
+ {
+ scoped_ptr<base::ListValue> result(
+ RunFunctionAndReturnList(new AlarmsGetAllFunction(), "[]"));
+ std::vector<linked_ptr<JsAlarm>> alarms = ToAlarmList(result.get());
+ EXPECT_EQ(0u, alarms.size());
+ }
+
+ // Create 2 alarms, and make sure we can query them.
+ CreateAlarms(2);
+
+ {
+ scoped_ptr<base::ListValue> result(
+ RunFunctionAndReturnList(new AlarmsGetAllFunction(), "[null]"));
+ std::vector<linked_ptr<JsAlarm>> alarms = ToAlarmList(result.get());
+ EXPECT_EQ(2u, alarms.size());
+
+ // Test the "7" alarm.
+ JsAlarm* alarm = alarms[0].get();
+ if (alarm->name != "7")
+ alarm = alarms[1].get();
+ EXPECT_EQ("7", alarm->name);
+ EXPECT_THAT(alarm->period_in_minutes, testing::Pointee(7));
+ }
+}
+
+void ExtensionAlarmsTestClearGetAllAlarms2Callback(
+ const AlarmManager::AlarmList* alarms) {
+ // Ensure the 0.001-minute alarm is still there, since it's repeating.
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(1u, alarms->size());
+ EXPECT_THAT((*alarms)[0].js_alarm->period_in_minutes,
+ testing::Pointee(0.001));
+}
+
+void ExtensionAlarmsTestClearGetAllAlarms1Callback(
+ ExtensionAlarmsTest* test,
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(1u, alarms->size());
+ 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
+ // fire.
+ test->test_clock_->Advance(base::TimeDelta::FromMilliseconds(60));
+ RunScheduleNextPoll(test->alarm_manager_);
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(1u, test->alarm_delegate_->alarms_seen.size());
+ EXPECT_EQ("", test->alarm_delegate_->alarms_seen[0]);
+
+ // Ensure the 0.001-minute alarm is still there, since it's repeating.
+ test->alarm_manager_->GetAllAlarms(
+ test->extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearGetAllAlarms2Callback));
+}
+
+TEST_F(ExtensionAlarmsTest, Clear) {
+ // Clear a non-existent one.
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"nobody\"]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_FALSE(copy_bool_result);
+ }
+
+ // Create 3 alarms.
+ CreateAlarms(3);
+
+ // Clear all but the 0.001-minute alarm.
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"7\"]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_TRUE(copy_bool_result);
+ }
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearFunction(), "[\"0\"]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_TRUE(copy_bool_result);
+ }
+
+ alarm_manager_->GetAllAlarms(
+ extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearGetAllAlarms1Callback, this));
+}
+
+void ExtensionAlarmsTestClearAllGetAllAlarms2Callback(
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_FALSE(alarms);
+}
+
+void ExtensionAlarmsTestClearAllGetAllAlarms1Callback(
+ ExtensionAlarmsTest* test,
+ const AlarmManager::AlarmList* alarms) {
+ ASSERT_TRUE(alarms);
+ EXPECT_EQ(3u, alarms->size());
+
+ // Clear them.
+ test->RunFunction(new AlarmsClearAllFunction(), "[]");
+ test->alarm_manager_->GetAllAlarms(
+ test->extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearAllGetAllAlarms2Callback));
+}
+
+TEST_F(ExtensionAlarmsTest, ClearAll) {
+ // ClearAll with no alarms set.
+ {
+ scoped_ptr<base::Value> result(
+ RunFunctionAndReturnValue(new AlarmsClearAllFunction(), "[]"));
+ bool copy_bool_result = false;
+ ASSERT_TRUE(result->GetAsBoolean(&copy_bool_result));
+ EXPECT_TRUE(copy_bool_result);
+ }
+
+ // Create 3 alarms.
+ CreateAlarms(3);
+ alarm_manager_->GetAllAlarms(
+ extension()->id(),
+ base::Bind(ExtensionAlarmsTestClearAllGetAllAlarms1Callback, this));
+}
+
+class ExtensionAlarmsSchedulingTest : public ExtensionAlarmsTest {
+ void GetAlarmCallback(Alarm* alarm) {
+ CHECK(alarm);
+ const base::Time scheduled_time =
+ base::Time::FromJsTime(alarm->js_alarm->scheduled_time);
+ EXPECT_EQ(scheduled_time, alarm_manager_->next_poll_time_);
+ }
+
+ static void RemoveAlarmCallback(bool success) { EXPECT_TRUE(success); }
+ static void RemoveAllAlarmsCallback() {}
+
+ public:
+ // Get the time that the alarm named is scheduled to run.
+ void VerifyScheduledTime(const std::string& alarm_name) {
+ alarm_manager_->GetAlarm(
+ extension()->id(), alarm_name,
+ base::Bind(&ExtensionAlarmsSchedulingTest::GetAlarmCallback,
+ base::Unretained(this)));
+ }
+
+ void RemoveAlarm(const std::string& name) {
+ alarm_manager_->RemoveAlarm(
+ extension()->id(), name,
+ base::Bind(&ExtensionAlarmsSchedulingTest::RemoveAlarmCallback));
+ }
+
+ void RemoveAllAlarms() {
+ alarm_manager_->RemoveAllAlarms(
+ extension()->id(),
+ base::Bind(&ExtensionAlarmsSchedulingTest::RemoveAllAlarmsCallback));
+ }
+};
+
+TEST_F(ExtensionAlarmsSchedulingTest, PollScheduling) {
+ {
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 6}]");
+ CreateAlarm("[\"bb\", {\"periodInMinutes\": 8}]");
+ VerifyScheduledTime("a");
+ RemoveAllAlarms();
+ }
+ {
+ CreateAlarm("[\"a\", {\"delayInMinutes\": 10}]");
+ CreateAlarm("[\"bb\", {\"delayInMinutes\": 21}]");
+ VerifyScheduledTime("a");
+ RemoveAllAlarms();
+ }
+ {
+ test_clock_->SetNow(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);
+ VerifyScheduledTime("a");
+ RemoveAllAlarms();
+ }
+ {
+ test_clock_->SetNow(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);
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(
+ base::Time::FromJsTime(3 * 60000) + base::TimeDelta::FromMinutes(3),
+ alarm_manager_->next_poll_time_);
+ RemoveAllAlarms();
+ }
+ {
+ test_clock_->SetNow(base::Time::FromDoubleT(4 * 60 + 1));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
+ RemoveAlarm("a");
+ 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);
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(
+ base::Time::FromJsTime(4 * 60000) + base::TimeDelta::FromMinutes(4),
+ alarm_manager_->next_poll_time_);
+ RemoveAllAlarms();
+ }
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, ReleasedExtensionPollsInfrequently) {
+ set_extension(
+ utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
+ test_clock_->SetNow(base::Time::FromJsTime(300000));
+ CreateAlarm("[\"a\", {\"when\": 300010}]");
+ CreateAlarm("[\"b\", {\"when\": 340000}]");
+
+ // On startup (when there's no "last poll"), we let alarms fire as
+ // soon as they're scheduled.
+ EXPECT_DOUBLE_EQ(300010, alarm_manager_->next_poll_time_.ToJsTime());
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(290000);
+ // In released extensions, we set the granularity to at least 1
+ // minute, which makes AddAlarm schedule the next poll after the
+ // extension requested.
+ alarm_manager_->ScheduleNextPoll();
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromMinutes(1)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, TimerRunning) {
+ EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
+ CreateAlarm("[\"a\", {\"delayInMinutes\": 0.001}]");
+ EXPECT_TRUE(alarm_manager_->timer_.IsRunning());
+ test_clock_->Advance(base::TimeDelta::FromMilliseconds(60));
+ base::MessageLoop::current()->Run();
+ EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
+ CreateAlarm("[\"bb\", {\"delayInMinutes\": 10}]");
+ EXPECT_TRUE(alarm_manager_->timer_.IsRunning());
+ RemoveAllAlarms();
+ EXPECT_FALSE(alarm_manager_->timer_.IsRunning());
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, MinimumGranularity) {
+ set_extension(
+ utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
+ test_clock_->SetNow(base::Time::FromJsTime(0));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
+ test_clock_->Advance(base::TimeDelta::FromSeconds(1));
+ CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
+ test_clock_->Advance(base::TimeDelta::FromMinutes(2));
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(2 * 60000);
+ // In released extensions, we set the granularity to at least 1
+ // minute, which makes scheduler set it to 1 minute, rather than
+ // 1 second later (when b is supposed to go off).
+ alarm_manager_->ScheduleNextPoll();
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromMinutes(1)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+TEST_F(ExtensionAlarmsSchedulingTest, DifferentMinimumGranularities) {
+ test_clock_->SetNow(base::Time::FromJsTime(0));
+ // Create an alarm to go off in 12 seconds. This uses the default, unpacked
+ // extension - so there is no minimum granularity.
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 0.2}]"); // 12 seconds.
+
+ // Create a new extension, which is packed, and has a granularity of 1 minute.
+ // CreateAlarm() uses extension_, so keep a ref of the old one around, and
+ // repopulate extension_.
+ scoped_refptr<Extension> extension2(extension_ref());
+ set_extension(
+ utils::CreateEmptyExtensionWithLocation(extensions::Manifest::INTERNAL));
+
+ CreateAlarm("[\"b\", {\"periodInMinutes\": 2}]");
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(0);
+ alarm_manager_->ScheduleNextPoll();
+
+ // The next poll time should be 12 seconds from now - the time at which the
+ // first alarm should go off.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(12)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+// Test that scheduled alarms go off at set intervals, even if their actual
+// trigger is off.
+TEST_F(ExtensionAlarmsSchedulingTest, RepeatingAlarmsScheduledPredictably) {
+ test_clock_->SetNow(base::Time::FromJsTime(0));
+ CreateAlarm("[\"a\", {\"periodInMinutes\": 2}]");
+
+ alarm_manager_->last_poll_time_ = base::Time::FromJsTime(0);
+ alarm_manager_->ScheduleNextPoll();
+
+ // We expect the first poll to happen two minutes from the start.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(120)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+
+ // Poll more than two minutes later.
+ test_clock_->Advance(base::TimeDelta::FromSeconds(125));
+ alarm_manager_->PollAlarms();
+
+ // The alarm should have triggered once.
+ EXPECT_EQ(1u, alarm_delegate_->alarms_seen.size());
+
+ // The next poll should still be scheduled for four minutes from the start,
+ // even though this is less than two minutes since the last alarm.
+ // Last poll was at 125 seconds; next poll should be at 240 seconds.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(115)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+
+ // Completely miss a scheduled trigger.
+ test_clock_->Advance(base::TimeDelta::FromSeconds(255)); // Total Time: 380s
+ alarm_manager_->PollAlarms();
+
+ // The alarm should have triggered again at this last poll.
+ EXPECT_EQ(2u, alarm_delegate_->alarms_seen.size());
+
+ // The next poll should be the first poll that hasn't happened and is in-line
+ // with the original scheduling.
+ // Last poll was at 380 seconds; next poll should be at 480 seconds.
+ EXPECT_DOUBLE_EQ((alarm_manager_->last_poll_time_ +
+ base::TimeDelta::FromSeconds(100)).ToJsTime(),
+ alarm_manager_->next_poll_time_.ToJsTime());
+}
+
+} // namespace extensions